¿Es mejor definir claves externas en la base de datos o en la parte del código de una aplicación?
¿Es mejor definir claves externas en la base de datos o en la parte del código de una aplicación?
Respuestas:
Ponga las claves foráneas en la base de datos. Incluso si valida los datos en la aplicación antes de guardarlos, los FK son una buena copia de seguridad de control de calidad. Para una primera aproximación, las aplicaciones siempre tienen problemas de datos. Dejar controles como este fuera del sistema solo invita a modos de falla donde los datos se corrompen silenciosamente.
No hay nada como trabajar en el almacenamiento de datos durante unos años para ver esto en acción. Pasas tu tiempo recogiendo las piezas después de errores asquerosos de los desarrolladores de aplicaciones que pensaron que podrían hacer cumplir la integridad de los datos en el código de la aplicación. Pase todo el tiempo haciendo esto y concluirá que la integridad de los datos administrados por la aplicación es poco más que una presunción.
Además, el optimizador de consultas puede usar claves externas para inferir cosas sobre las uniones de tablas, por lo que los FK darán como resultado planes de consulta más eficientes.
También hay muchos otros beneficios para las claves externas. Hazle un favor a todos: coloca los FK en la base de datos.
La integridad referencial debe manejarse en el nivel más bajo posible, que sería la base de datos subyacente. Los sistemas de gestión de bases de datos relacionales están optimizados para manejar esto. No tiene sentido reinventar la rueda proverbial.
Es aceptable definir la lógica de dominio en el código de la aplicación para evitar que la instrucción DML provoque incluso una excepción de RI, pero esto no debe verse como un reemplazo de las relaciones de clave externa en la base de datos.
Voy a arriesgarme aquí esperando que esto sea rechazado ya que este es un grupo centrado en DBA.
Estoy de acuerdo en que usar claves externas estrictas es la mejor decisión en la mayoría de los escenarios. Sin embargo, hay algunos casos en los que las claves externas causan más problemas de los que resuelven.
Cuando se trata de un entorno muy concurrente, como una aplicación web de alto tráfico, y está utilizando un ORM robusto y bien establecido, las claves externas pueden causar problemas de bloqueo que dificultan la ampliación y el mantenimiento de un servidor. Al actualizar filas en una tabla secundaria, la fila principal también se bloquea. En muchos escenarios, esto puede limitar drásticamente la concurrencia debido a la contención de bloqueo. Además, a veces tiene que realizar tareas de mantenimiento en tablas individuales, como procesos de archivo en los que puede necesitar (intencionalmente) romper las reglas de integridad referencial, al menos temporalmente. Con las claves externas en su lugar, esto puede ser increíblemente difícil y en algunos RDBMS deshabilitar las restricciones de clave externa provocará una reconstrucción de la tabla, un proceso que requiere mucho tiempo y puede requerir un tiempo de inactividad considerable.
Comprenda que incluyo la advertencia de que debe usar un marco robusto que sea capaz de comprender la integridad referencial externa a la base de datos. Aún así, probablemente terminará con algunos problemas de integridad referencial. Sin embargo, hay muchos casos en los que no es tan importante tener filas huérfanas o violaciones menores de integridad referencial. Yo diría que la mayoría de las aplicaciones web entran en esta categoría.
Dicho esto, nadie comienza como Facebook. Comience definiendo claves foráneas en su base de datos. Monitor. Si termina teniendo problemas, comprenda que es posible que deba eliminar algunas de esas restricciones para escalar.
En conclusión: la mayoría de las bases de datos deberían tener claves foráneas. Los entornos altamente concurrentes podrían estar mejor sin claves foráneas. Si llega a ese punto, es posible que deba considerar eliminar esas restricciones.
Voy a ponerme mi traje ignífugo ahora.
EDITAR 2012-03-23 7:00 AM
Al pensar en las consecuencias de bloqueo de las claves externas, no mencioné el costo de todas las búsquedas de filas adicionales que se generan implícitamente internamente, lo que aumenta la carga del servidor.
En última instancia, mi punto es que las claves externas no son gratuitas. En muchos casos, el costo lo vale, pero hay escenarios en los que ese costo excede su beneficio.
EDITAR 2012-03-23 7:38 AM
Seamos concretos. Estoy eligiendo MySQL / InnoDB en este ejemplo, que no es muy respetado por su comportamiento de clave externa, pero es con lo que estoy más familiarizado y es probable que sea la base de datos web más utilizada. No estoy seguro de que a otra base de datos le vaya mejor con el ejemplo que estoy a punto de mostrar.
Considere una tabla secundaria con una clave foránea que haga referencia al padre. Como ejemplo, vea las tablas film y film_actor en la base de datos de muestra sakila en MySQL:
CREATE TABLE `film` (
`film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text,
`release_year` year(4) DEFAULT NULL,
`language_id` tinyint(3) unsigned NOT NULL,
`original_language_id` tinyint(3) unsigned DEFAULT NULL,
`rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
`rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
`length` smallint(5) unsigned DEFAULT NULL,
`replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
`rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
`special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`film_id`),
KEY `idx_title` (`title`),
KEY `idx_fk_language_id` (`language_id`),
KEY `idx_fk_original_language_id` (`original_language_id`),
CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
CREATE TABLE `film_actor` (
`actor_id` smallint(5) unsigned NOT NULL,
`film_id` smallint(5) unsigned NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`actor_id`,`film_id`),
KEY `idx_fk_film_id` (`film_id`),
CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES `actor` (`actor_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_film_actor_film` FOREIGN KEY (`film_id`) REFERENCES `film` (`film_id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
La restricción relevante es film_actor (fk_film_actor_film) para mi ejemplo.
session1> BEGIN;
session1> INSERT INTO film_actor (actor_id, film_id) VALUES (156, 508);
Query OK, 1 row affected (0.00 sec)
session2> BEGIN;
session2> UPDATE film SET release_year = 2005 WHERE film_id = 508;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Tenga en cuenta que no pude actualizar un campo no relacionado en la fila principal al insertar en la tabla secundaria. Esto sucede porque InnoDB mantiene un bloqueo compartido en la fila donde film.film_id = 508 debido a la restricción FK en film_actor, por lo tanto, la ACTUALIZACIÓN de esa fila no puede obtener el bloqueo exclusivo requerido. Si invierte esa operación y ejecuta la ACTUALIZACIÓN primero, tiene el mismo comportamiento, pero el INSERT está bloqueado.
session1> BEGIN;
session1> UPDATE film SET release_year = 2005 WHERE film_id = 508;
Query OK, 1 row affected (0.00 sec)
session2> BEGIN;
session2> INSERT INTO film_actor (actor_id, film_id) VALUES (156, 508);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Considere una users
tabla en una aplicación web donde a menudo hay docenas de tablas relacionadas. Esencialmente, cualquier operación en cualquier fila relacionada impide una actualización de la fila principal. Eso puede ser un problema desafiante cuando tienes múltiples relaciones de clave externa y mucha concurrencia.
Las restricciones de FK también pueden dificultar las soluciones para el mantenimiento de la tabla. Peter Zaitsev de Percona tiene una publicación de blog sobre esto que lo explica mejor que yo: Secuestro de llaves extranjeras de Innodb .
Es una buena práctica utilizar la clave foránea en la base de datos. Ayuda-
ON DELETE CASCADE