Vincoli di chiave esterna: quando utilizzare ON UPDATE e ON DELETE


196

Sto progettando il mio schema di database usando MySQL Workbench, che è piuttosto interessante perché puoi fare diagrammi e li converte: P

Ad ogni modo, ho deciso di usare InnoDB per via del suo supporto di chiave esterna. Una cosa che ho notato è che ti consente di impostare su Opzioni di aggiornamento e su Elimina per le chiavi esterne. Qualcuno può spiegare dove "Restrict", "Cascade" e set null potrebbero essere usati in un semplice esempio?

Ad esempio, supponiamo che io abbia una usertabella che include a userID. E dire che ho una tabella di messaggi messageche è un molti-a-molti che ha 2 chiavi esterne (che fanno riferimento alla stessa chiave primaria, userIDnella usertabella). L'impostazione delle opzioni All'aggiornamento e All'eliminazione è utile in questo caso? In tal caso, quale scelgo? Se questo non è un buon esempio, potresti fornire un buon esempio per illustrare come questi potrebbero essere utili?

Grazie

Risposte:


485

Non esitate a mettere vincoli nel database. Avrai la certezza di avere un database coerente, e questo è uno dei buoni motivi per usare un database. Soprattutto se hai diverse applicazioni che lo richiedono (o solo un'applicazione ma con una modalità diretta e una modalità batch che utilizza fonti diverse).

Con MySQL non hai vincoli avanzati come in postgreSQL, ma almeno i vincoli di chiave esterna sono abbastanza avanzati.

Faremo un esempio, una tabella di società con una tabella di utenti che contiene persone di queste società

CREATE TABLE COMPANY (
     company_id INT NOT NULL,
     company_name VARCHAR(50),
     PRIMARY KEY (company_id)
) ENGINE=INNODB;

CREATE TABLE USER (
     user_id INT, 
     user_name VARCHAR(50), 
     company_id INT,
     INDEX company_id_idx (company_id),
     FOREIGN KEY (company_id) REFERENCES COMPANY (company_id) ON...
) ENGINE=INNODB;

Diamo un'occhiata alla clausola ON UPDATE :

  • IN AGGIORNAMENTO LIMITATO : impostazione predefinita : se si tenta di aggiornare un company_id nella tabella COMPANY, il motore rifiuterà l'operazione se almeno un UTENTE si collega a questa azienda.
  • IN AGGIORNAMENTO SENZA AZIONE : uguale a RESTRICT.
  • IN AGGIORNAMENTO CASCADE : il migliore di solito : se aggiorni un company_id in una riga della tabella COMPANY, il motore lo aggiornerà di conseguenza su tutte le righe USER che fanno riferimento a questa COMPANY (ma nessun trigger attivato sulla tabella USER, avviso). Il motore seguirà le modifiche per te, va bene.
  • IN AGGIORNAMENTO SET NULL : se aggiorni un company_id in una riga della tabella COMPANY, il motore imposterà gli USER company_id correlati su NULL (dovrebbe essere disponibile nel campo USER company_id). Non riesco a vedere nulla di interessante da fare su un aggiornamento, ma potrei sbagliarmi.

E ora sul lato ON DELETE :

  • ON DELETE RESTRICT : il valore predefinito : se si tenta di eliminare un ID company_id nella tabella COMPANY, il motore rifiuterà l'operazione se un USER almeno link su questa azienda, può salvarti la vita.
  • ON DELETE NO AZIONE : uguale a RESTRICT
  • ALLA CANCELLAZIONE DELLA CASCATA : pericoloso : se si elimina una riga dell'azienda nella tabella AZIENDA, il motore eliminerà anche gli UTENTI correlati. Questo è pericoloso ma può essere utilizzato per eseguire pulizie automatiche su tabelle secondarie (quindi può essere qualcosa che desideri, ma sicuramente non per un esempio di UTENTE <-> UTENTE)
  • ON DELETE SET NULL : handful : se si elimina una riga COMPANY, gli USER correlati avranno automaticamente la relazione con NULL. Se Null è il tuo valore per gli utenti senza società, questo può essere un buon comportamento, ad esempio forse devi tenere gli utenti nella tua applicazione, come autori di alcuni contenuti, ma rimuovere la società non è un problema per te.

di solito il mio default è: ON ELIMINA RESTRETTO SU AGGIORNAMENTO CASCADE . con alcuni ON DELETE CASCADEper le tabelle di traccia (registri - non tutti i registri--, cose del genere) e ON DELETE SET NULLquando la tabella principale è un "attributo semplice" per la tabella contenente la chiave esterna, come una tabella JOB per la tabella USER.

modificare

È passato molto tempo da quando l'ho scritto. Ora penso che dovrei aggiungere un avvertimento importante. MySQL ha una grande limitazione documentata con le cascate. Le cascate non attivano i grilletti . Quindi, se eri abbastanza fiducioso in quel motore da usare i trigger, dovresti evitare i vincoli a cascata.

I trigger MySQL si attivano solo per le modifiche apportate alle tabelle dalle istruzioni SQL. Non si attivano per le modifiche alle viste, né per le modifiche alle tabelle effettuate dalle API che non trasmettono le istruzioni SQL al server MySQL

==> Vedi sotto l'ultima modifica, le cose si stanno muovendo su questo dominio

I trigger non sono attivati ​​da azioni con chiave esterna.

E non credo che questo verrà risolto un giorno. I vincoli di chiave esterna sono gestiti dall'archiviazione InnoDb e i trigger sono gestiti dal motore SQL MySQL. Entrambi sono separati. Innodb è l'unico archivio con gestione dei vincoli, forse un giorno aggiungeranno trigger direttamente nel motore di archiviazione, forse no.

Ma ho la mia opinione su quale elemento dovresti scegliere tra la scarsa implementazione del trigger e l'utilissimo supporto dei vincoli di chiavi esterne. E una volta che ti abituerai alla coerenza del database, amerai PostgreSQL.

12/2017-Aggiornamento di questa modifica su MySQL:

come affermato da @IstiaqueAhmed nei commenti, la situazione è cambiata su questo argomento. Quindi segui il link e controlla la situazione attuale (che potrebbe cambiare di nuovo in futuro).


8
ON DELETE CASCADE : dangerous- prendere con un pizzico di sale.
giorno

3
Dovrai stare attento al collegamento a cascata, può bloccare il tuo sistema se è necessario modificare molti record. L'eliminazione a cascata dovrebbe essere attentamente esaminata prima dell'uso, spesso si desidera davvero che l'eliminazione non si verifichi se ci sono record figlio. Non desidero che un cliente elimini per cancellare i dati finanziari per i committenti che aveva in precedenza. A volte è meglio assicurarsi che il cacading non sia attivo e fornire un modo per rendere i record amrk inattivi.
HLGEM,

1
In termini di logica aziendale, c'è un caso che potrebbe essere interessante SET NULLin un ON UPDATE: l'aggiornamento di una società rappresenta un distacco della relazione Società> Utente. Ad esempio: se una società cambia il suo tipo di attività, gli utenti precedenti potrebbero non essere più collegati a tale attività, quindi NULLpotrebbe essere preferibile per questo indice.
CPHPython,

1
@regilero, sembra che il contenuto del tuo primo link ( dev.mysql.com/doc/refman/5.6/en/triggers.html ) al sito mysql sia cambiato. Dice This includes changes to base tables that underlie updatable viewsinvece di ciò che hai incollato, cioèThey do not activate for changes in views
Istiaque Ahmed il

6
"Non vorrei che un cliente eliminasse per cancellare i dati finanziari per gli ordini che aveva in precedenza." In una situazione del genere, probabilmente avrai comunque bisogno dei dati del cliente. Il progetto dovrebbe probabilmente contrassegnare il cliente come inattivo, non eliminare la sua riga dal database. In pratica, è stata la mia esperienza professionale che in realtà molto raramente si desidera eliminare qualsiasi cosa , preferendo contrassegnare inattivo per impostazione predefinita. Nei casi in cui l'eliminazione permanente è corretta, in CASCADE DELETEgenere va anche bene, anche preferita. Non lo considero particolarmente pericoloso.
GrandOpener

3

Aggiunta alla risposta di @MarkR - una cosa da notare sarebbe che molti framework PHP con ORM non riconoscono o utilizzano impostazioni DB avanzate (chiavi esterne, eliminazione a cascata, vincoli univoci) e ciò può comportare comportamenti imprevisti.

Ad esempio, se si elimina un record utilizzando ORM e DELETE CASCADEsi elimineranno i record nelle tabelle correlate, il tentativo di ORM di eliminare questi record correlati (spesso automatici) comporterà un errore.


11
Ciò costituirebbe un motivo per non utilizzare quel particolare ORM. Qualsiasi strumento che è così scarso nel supporto del database non è affidabile. Le chiavi esterne e le eliminazioni o gli aggiornamenti a cascata sono concetti di base di db non concetti avanzati e nessun database realtional dovrebbe mai essere progettato senza vincoli di chiave esterna!
HLGEM,

Il problema è che generano errori. È possibile RESTRICT ELIMINA ma il motore non genera errori ma mantiene comunque la semantica? Vorrei che il mio programma continuasse, proteggendo allo stesso tempo altri dati dall'eliminazione.
TheRealChx101,

2

Dovrai considerare questo nel contesto dell'applicazione. In generale, è necessario progettare un'applicazione, non un database (il database fa semplicemente parte dell'applicazione).

Considera come la tua domanda dovrebbe rispondere a vari casi.

L'azione predefinita è limitare (cioè non consentire) l'operazione, che è normalmente ciò che si desidera in quanto impedisce stupidi errori di programmazione. Tuttavia, su ELIMINA CASCATA può anche essere utile. Dipende molto dalla tua applicazione e da come intendi eliminare determinati oggetti.

Personalmente, userei InnoDB perché non trasporta i tuoi dati (vedi MyISAM, che lo fa), piuttosto che perché ha vincoli FK.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.