Restricciones de clave externa: cuándo usar ON UPDATE y ON DELETE

196

Estoy diseñando mi esquema de base de datos usando MySQL Workbench, lo cual es bastante bueno porque puedes hacer diagramas y los convierte: P

De todos modos, he decidido usar InnoDB debido a su soporte de clave externa. Sin embargo, una cosa que noté es que le permite configurar las opciones Al actualizar y Eliminar para las claves externas. ¿Alguien puede explicar dónde "Restringir", "Cascada" y establecer nulo podrían usarse en un ejemplo simple?

Por ejemplo, digamos que tengo una usertabla que incluye a userID. Y digamos que tengo una tabla de mensajes messageque es de muchos a muchos que tiene 2 claves foráneas (que hacen referencia a la misma clave primaria, userIDen la usertabla). ¿Es útil configurar las opciones Al actualizar y Al eliminar en este caso? Si es así, ¿cuál elijo? Si este no es un buen ejemplo, ¿podría dar un buen ejemplo para ilustrar cómo podrían ser útiles?

Gracias

meltuhamy
fuente

Respuestas:

485

No dude en poner restricciones en la base de datos. Asegúrese de tener una base de datos coherente, y esa es una de las buenas razones para usar una base de datos. Especialmente si tiene varias aplicaciones que lo solicitan (o solo una aplicación pero con un modo directo y un modo por lotes utilizando diferentes fuentes).

Con MySQL no tiene restricciones avanzadas como las que tendría en postgreSQL, pero al menos las restricciones de clave externa son bastante avanzadas.

Tomaremos un ejemplo, una tabla de empresa con una tabla de usuario que contiene personas de la compañía de tesis

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;

Veamos la cláusula ON UPDATE :

  • RESTRICTO DE ACTUALIZACIÓN : el valor predeterminado : si intenta actualizar un company_id en la tabla EMPRESA, el motor rechazará la operación si un USUARIO al menos se vincula a esta empresa.
  • ACTUALIZACIÓN SIN ACCIÓN : igual que RESTRICT.
  • EN ACTUALIZACIÓN EN CASCADA : el mejor generalmente : si actualiza un company_id en una fila de la tabla EMPRESA, el motor lo actualizará en consecuencia en todas las filas de USUARIO que hacen referencia a esta EMPRESA (pero no hay activadores activados en la tabla USUARIO, advertencia). El motor hará un seguimiento de los cambios por usted, es bueno.
  • ON UPDATE SET NULL : si actualiza un company_id en una fila de la tabla COMPANY, el motor establecerá USERs relacionados company_id en NULL (debe estar disponible en el campo USER company_id). No puedo ver nada interesante que hacer con eso en una actualización, pero puedo estar equivocado.

Y ahora en el lado ON DELETE :

  • AL BORRAR RESTRICCIÓN : el valor predeterminado : si intenta eliminar un ID de company_id en la tabla EMPRESA, el motor rechazará la operación si un USUARIO al menos enlaza con esta empresa, puede salvarle la vida.
  • AL BORRAR SIN ACCIÓN : igual que RESTRICT
  • EN ELIMINAR CASCADA : peligroso : si elimina una fila de la empresa en la tabla EMPRESA, el motor eliminará también a los USUARIOS relacionados. Esto es peligroso, pero puede usarse para realizar limpiezas automáticas en tablas secundarias (por lo que puede ser algo que desee, pero ciertamente no para un ejemplo de COMPAÑÍA <-> USUARIO)
  • AL ELIMINAR SET NULL : puñado : si elimina una fila de la EMPRESA, los USUARIOS relacionados tendrán automáticamente la relación con NULL. Si Null es su valor para los usuarios sin compañía, este puede ser un buen comportamiento, por ejemplo, tal vez necesite mantener a los usuarios en su aplicación, como autores de algún contenido, pero eliminar la compañía no es un problema para usted.

generalmente mi valor predeterminado es: AL BORRAR RESTRICCIÓN AL ACTUALIZAR CASCADA . con algunas ON DELETE CASCADEpara tablas de seguimiento (registros, no todos los registros, cosas así) y ON DELETE SET NULLcuando la tabla maestra es un "atributo simple" para la tabla que contiene la clave externa, como una tabla de TRABAJO para la tabla USER.

Editar

Ha pasado mucho tiempo desde que escribí eso. Ahora creo que debería agregar una advertencia importante. MySQL tiene una gran limitación documentada con cascadas. Las cascadas no son disparadores . Entonces, si tenía suficiente confianza en ese motor para usar disparadores, debería evitar las restricciones en cascada.

Los activadores de MySQL se activan solo para los cambios realizados en las tablas mediante declaraciones SQL. No se activan por cambios en las vistas, ni por cambios en las tablas hechas por API que no transmiten sentencias SQL al servidor MySQL

==> Vea a continuación la última edición, las cosas se están moviendo en este dominio

Los disparadores no se activan mediante acciones de clave externa.

Y no creo que esto se arregle algún día. El almacenamiento de InnoDb gestiona las restricciones de clave externa y el motor MySQL SQL gestiona los disparadores. Ambos están separados. Innodb es el único almacenamiento con gestión de restricciones, quizás algún día agregarán disparadores directamente en el motor de almacenamiento, quizás no.

Pero tengo mi propia opinión sobre qué elemento debe elegir entre la implementación de activación deficiente y el muy útil soporte de restricciones de claves externas. Y una vez que se acostumbre a la consistencia de la base de datos, le encantará PostgreSQL.

12/2017-Actualización de esta edición sobre MySQL:

como lo indicó @IstiaqueAhmed en los comentarios, la situación ha cambiado en este tema. Por lo tanto, siga el enlace y verifique la situación actual actualizada (que puede cambiar nuevamente en el futuro).

regilero
fuente
8
ON DELETE CASCADE : dangerous- Tomar con una pizca de sal.
cuando el
3
Tendrá que tener cuidado con la conexión en cascada, puede bloquear su sistema si es necesario cambiar muchos registros. La eliminación de Cascde debe considerarse especialmente antes de usar, a menudo realmente desea que la eliminación no se produzca si hay registros secundarios. No quisiera que un cliente elimine para borrar los datos financieros de los proveedores que tenía anteriormente. A veces es mejor asegurarse de que la función de almacenamiento en cascada no esté activada y proporcionar una forma de activar los registros como inactivos.
HLGEM
1
En términos de la lógica de negocio, hay un caso que podría ser interesante SET NULLen una ON UPDATE: la actualización de una empresa representa un destacamento de la Compañía> relación del usuario. Por ejemplo: si una empresa cambia su tipo de negocio, los usuarios anteriores ya no pueden estar relacionados con ese negocio, por NULLlo tanto, puede ser preferible para este índice.
CPHPython
1
@regilero, parece que el contenido en su primer enlace ( dev.mysql.com/doc/refman/5.6/en/triggers.html ) al sitio mysql ha cambiado. Dice en This includes changes to base tables that underlie updatable viewslugar de lo que They do not activate for changes in views
pegaste
66
"No quisiera que un cliente elimine para borrar los datos financieros de los pedidos que tenía anteriormente". En una situación como esa, probablemente aún necesite los datos del cliente de todos modos. Su diseño probablemente debería marcar al cliente como inactivo, no eliminar su fila de la base de datos. En la práctica, ha sido mi experiencia profesional que en realidad muy rara vez se quiere borrar nada , prefiriendo para marcar inactiva por defecto. En los casos en que la eliminación permanente está bien, CASCADE DELETEgeneralmente también está bien, incluso se prefiere. No lo considero particularmente peligroso.
GrandOpener
3

Además de la respuesta @MarkR: una cosa a tener en cuenta sería que muchos marcos PHP con ORM no reconocerían ni usarían la configuración avanzada de la base de datos (claves foráneas, eliminación en cascada, restricciones únicas), y esto puede provocar un comportamiento inesperado.

Por ejemplo, si elimina un registro utilizando ORM, y DELETE CASCADEeliminará registros en tablas relacionadas, el intento de ORM de eliminar estos registros relacionados (a menudo automáticos) dará como resultado un error.

lxa
fuente
11
Esa sería una razón para no usar ese ORM en particular. Cualquier herramienta que sea tan pobre en el soporte de la base de datos no es confiable. ¡Las claves externas y las eliminaciones o actualizaciones en cascada son conceptos básicos de db, no conceptos avanzados y ninguna base de datos real debería diseñarse sin restricciones de clave externa!
HLGEM
El problema es que arrojan errores. ¿Es posible RESTRINGIR ELIMINACIONES pero el motor no genera errores pero mantiene la semántica? Me gustaría que mi programa continúe y al mismo tiempo proteja la eliminación de otros datos.
TheRealChx101
2

Tendrá que considerar esto en el contexto de la aplicación. En general, debe diseñar una aplicación, no una base de datos (la base de datos simplemente forma parte de la aplicación).

Considere cómo su aplicación debería responder a varios casos.

La acción predeterminada es restringir (es decir, no permitir) la operación, que normalmente es lo que desea, ya que evita errores estúpidos de programación. Sin embargo, en DELETE CASCADE también puede ser útil. Realmente depende de su aplicación y de cómo piensa eliminar objetos particulares.

Personalmente, usaría InnoDB porque no destruye sus datos (consulte MyISAM, que lo hace), en lugar de porque tiene restricciones FK.

MarkR
fuente