Лучше ли определять внешние ключи в базе данных или в части кода приложения?
Лучше ли определять внешние ключи в базе данных или в части кода приложения?
Ответы:
Поместите внешние ключи в базу данных. Даже если вы проверяете данные в приложении перед тем, как сохранить их, FK являются хорошей резервной копией для обеспечения качества. В первом приближении приложения всегда имеют проблемы с данными. Если оставить такие элементы управления вне системы, то просто вызовет режимы отказа, когда данные будут молча повреждены.
Нет ничего лучше, чем работать в хранилищах данных несколько лет, чтобы увидеть это в действии. Вы тратите свое время на то, чтобы разобраться с ошибками разработчиков приложений, которые думали, что могут обеспечить целостность данных в коде приложения. Потратьте любое время на это, и вы придете к выводу, что целостность данных, управляемая приложением, - это не более, чем тщеславие.
Кроме того, оптимизатор запросов может использовать внешние ключи для вывода информации о соединениях таблиц, поэтому FK приведет к более эффективным планам запросов.
Есть много других преимуществ для внешних ключей. Сделайте всем одолжение - поместите ФК в базу данных.
Ссылочная целостность должна обрабатываться на самом низком уровне, который будет лежать в основе базы данных. Системы управления реляционными базами данных оптимизированы для этого. Не имеет смысла изобретать колесо пословиц.
Допустимо определять доменную логику в коде приложения, чтобы оператор DML даже не вызывал исключение RI, но это не следует рассматривать как замену отношений внешнего ключа в базе данных.
Я собираюсь выйти на конечность здесь, полностью ожидая, что за это проголосуют, так как это группа, ориентированная на DBA.
Я согласен, что использование строгих внешних ключей - лучшее решение в большинстве сценариев. Однако в некоторых случаях внешние ключи вызывают больше проблем, чем решают.
Когда вы работаете в среде с высокой степенью параллелизма, такой как веб-приложение с высоким трафиком, и используете хорошо зарекомендовавший себя надежный ORM, внешние ключи могут вызвать проблемы с блокировкой, которые затрудняют масштабирование и обслуживание сервера. При обновлении строк в дочерней таблице родительская строка также блокируется. Во многих сценариях это может резко ограничить параллелизм из-за конкуренции за блокировку. Кроме того, иногда вам приходится выполнять обслуживание отдельных таблиц, например, процессов архивирования, когда вам может потребоваться (намеренно) нарушить правила ссылочной целостности, по крайней мере, временно. При наличии внешних ключей это может быть невероятно сложно, и в некоторых СУБД отключение ограничений по внешнему ключу может привести к перестройке таблицы, что занимает много времени и может потребовать значительных простоев.
Поймите, что я включаю предостережение о том, что вы должны использовать надежную структуру, способную понимать ссылочную целостность, внешнюю по отношению к базе данных. Тем не менее, вы, скорее всего, столкнетесь с некоторыми проблемами ссылочной целостности. Тем не менее, во многих случаях не так уж важно иметь потерянные строки или незначительные нарушения ссылочной целостности. Я бы сказал, что большинство веб-приложений подпадают под эту категорию.
При этом никто не начинает как Facebook. Начните с определения внешних ключей в вашей базе данных. Монитор. Если у вас возникнут проблемы, помните, что вам может потребоваться снять некоторые из этих ограничений для масштабирования.
В заключение: большинство баз данных должны иметь внешние ключи. Среды с высоким уровнем параллелизма могут быть лучше без внешних ключей. Если вы достигнете этой точки, вам, возможно, придется рассмотреть вопрос о снятии этих ограничений.
Я собираюсь пойти надеть мой огнестойкий костюм сейчас.
РЕДАКТИРОВАТЬ 2012-03-23 7:00 AM
Размышляя о последствиях блокировки внешних ключей, я не упомянул стоимость всех дополнительных поисков строк, которые неявно генерируются внутри, что увеличивает нагрузку на сервер.
В конечном счете, я хочу сказать, что внешние ключи не являются бесплатными. Во многих случаях стоимость того стоит, но есть сценарии, в которых эта стоимость превышает их выгоду.
РЕДАКТИРОВАТЬ 2012-03-23 7:38 AM
Давайте будем конкретными. В этом примере я выбираю MySQL / InnoDB, который не очень уважают за поведение внешнего ключа, но это то, с чем я больше всего знаком и, вероятно, наиболее часто используемая веб-база данных. Я не уверен, что другая база данных будет лучше с примером, который я собираюсь показать.
Рассмотрим дочернюю таблицу с внешним ключом, ссылающимся на родительский. В качестве примера посмотрите таблицы film и film_actor в примере базы данных sakila в 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
Соответствующее ограничение для моего примера - film_actor (fk_film_actor_film).
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
Обратите внимание, что мне не удалось обновить несвязанное поле в родительской строке при вставке в дочернюю таблицу. Это происходит потому, что InnoDB удерживает общую блокировку в строке, где film.film_id = 508, из-за ограничения FK для film_actor, поэтому UPDATE для этой строки не может получить требуемую эксклюзивную блокировку. Если вы отмените эту операцию и сначала запустите ОБНОВЛЕНИЕ, у вас будет такое же поведение, но ВСТАВКА заблокирована.
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
Рассмотрим users
таблицу в веб-приложении, где часто встречаются десятки связанных таблиц. По сути, любая операция в любой связанной строке предотвращает обновление родительской строки. Это может быть сложной проблемой, когда у вас несколько взаимосвязей между внешними ключами и много параллелизма.
Ограничения FK также могут усложнить обходные пути для обслуживания таблиц. У Питера Зайцева из Percona есть пост в блоге об этом, который объясняет это лучше, чем я: угон иностранных ключей от Innodb .
Рекомендуется использовать внешний ключ в базе данных. Помогает-
ON DELETE CASCADE