Хотя я живу в Копенгагене и в основном перемещаюсь по городу на велосипеде, у меня есть и машина, чтобы добираться до других частей страны. В Дании автомобили через каждые два года проходят обязательный официальный осмотр; я проходил его за свою жизнь несколько раз. Несколько лет назад механик, выполнявший осмотр, сообщил мне о том, что у машины неправильный номер кузова.
Я немного занервничал, потому что покупал машину с рук, и внезапно задался вопросом, действительно ли всё так, как я думал. Неужели я непреднамеренно купил краденную машину?
Но механик просто подошёл к своему компьютеру, чтобы исправить ошибку. И тогда у меня возникло совершенно другое опасение. Когда программируешь больше десятка лет, то учишься предвидеть разные типичные режимы отказа. Так как номер кузова — очевидный кандидат на должность естественного ключа, я заранее предвидел, что изменение этого номера будет или невозможно, или приведёт ко всевозможным каскадным эффектам, и в конечном итоге к уничтожению официальных записей, больше не признающих, что машина принадлежит мне.
Но оказалось, что писавший программное обеспечение знал своё дело, потому что механик просто поменял номер, и на этом всё закончилось. Это было пять или шесть лет назад, я по-прежнему владею этой машиной, и у меня никогда не возникало проблем с официальными записями о владении.
Уникальность
Я рассказал эту историю потому, что сейчас наблюдаю за студенческим курсом по базам данных и информационным системам. Так как этот курс предназначен для студентов, не имеющих опыта работы, он движется педагогически поступательно. Чтобы обучить понятию ключей баз данных, он начинает с естественных ключей. С точки зрения обучения это логично, но в результате этого молодёжь, с которой я работаю, предлагает архитектуры баз данных с естественными ключами.
Я не пытаюсь никого обвинить: прежде чем научиться ходить, нужно научиться ползать.
Тем не менее, эта ситуация заставила меня задуматься над таким вопросом: бывают ли хоть когда-нибудь естественные ключи хорошим выбором?
Давайте рассмотрим пример. Для нашего небольшого проекта мы создали базу данных из 50 лучших ресторанов мира. Мои коллеги-студенты предложили следующую структуру таблицы:
CREATE TABLE Restaurants (
year TEXT NOT NULL,
rank TEXT NOT NULL,
restaurantName TEXT NOT NULL,
cityName TEXT NOT NULL
);
Да, на этом этапе такое определение таблицы вообще не определяет никаких ключей. Я на это не жалуюсь. В конце концов, ещё месяц назад студенты, вероятно, и не видели за свою жизнь ни одной таблицы базы данных.
Однако, согласно курсу, будет естественным определить ключ для таблицы Restaurants
как комбинацию restaurantName
, cityName
и year
. Допущение здесь заключается в том. что название и город идентифицируют ресторан уникальным образом.
В этом конкретном примере такое допущение, вероятно, будет соблюдаться. Пока. В конце концов, датасет не очень велик, а ресторанам такого уровня важно иметь узнаваемые названия. Я бы предположил, что в мире есть только один Nobelhart & Schmutzig.
Тем не менее, хороший архитектор ПО должен подвергать сомнениям подобные фундаментальные допущения. Действительно ли название и город — это естественный ключ? Легко представить, что это не так. Что, если мы дополним ключ ещё и страной? Отлично, но что, если у нас будет ресторан China Wok в Спрингфилде, США? [Прим. пер.: в США более сорока населённых пунктов с таким названием, поэтому его носит и вымышленный город из «Симпсонов».] Не совсем уникально. Говорите, добавить ещё и штат? Вероятно, всё равно ключ окажется неуникальным.
Идентификация
Обеспечение уникальности — лишь первая из множества проблем естественных ключей. Можно достаточно быстро прийти к выводу, что для базы данных ресторанов, вероятно, лучшим выбором станет синтетический ключ.
Но что насчёт «естественных» естественных ключей, так сказать? Например, номера кузова автомобиля. Это уже непрозрачное число, которое, вероятно, берётся из какой-то базы данных. А может быть, использовать персональный идентификационный номер? У нас в Дании есть номер CPR, и я знаю, что Social Security Number США примерно ему аналогичен.
Если вы проектируете базу данных, уже содержащую такой персональный идентификационный номер, у вас может возникнуть искушение использовать его в качестве естественного ключа. В конце концов, он уже используется как ключ где-то ещё, так что гарантированно является уникальным, ведь так?
Да, номер может уникальным образом идентифицировать человека, но обратное может и не быть истинным. У человека может быть несколько идентификационных номеров. По крайней мере, они могут добавляться со временем.
Например, по исторически-техническим причинам датский номер CPR содержит такую информацию, как дата рождения и пол человека (которых не должно быть в ключах). С 2014 года новый закон позволяет транссексуальным гражданам получать новый номер CPR, отражающий их воспринимаемый гендер, поэтому у одного человека может быть несколько номеров CPR. Возможно, не больше одного в конкретный момент времени, но два в течение срока жизни.
Даже если уже существующие ключи гарантированно оказываются уникальными, мы не можем допускать, что уникальность становится причиной биекции. Если вы используете внешний уникальный ключ, то можете потерять из виду сущности, которые пытаетесь отслеживать.
Это уникально не только для людей, но и для автомобилей, для велосипедов (тоже имеющих номер рамы), для сетевых карт и так далее.
Опечатки
Кроме того, даже если вы нашли естественный ключ, который гарантированно уникален и отслеживает сущность, которую вам нужно отслеживать, есть последний аргумент против использования в системе внешне определяемого ключа: ошибки ввода данных.
Вернёмся к истории о номере кузова моей машины. Механик, обнаруживший расхождение, чётко интерпретировал его как опечатку.
Программируя уже больше десятка лет, я понял, что рано или поздно в ваших данных появятся ошибки. Это могут быть канцелярские ошибки, опечатки конечного пользователя или ошибки преобразования данных при импорте из внешней системы. Или даже ошибки преобразования данных в одной системе в процессе апгрейдов и миграций.
Систему следует проектировать так, чтобы обеспечить возможность исправления данных. В том числе и исправления внешних ключей, то есть номеров кузова, государственных идентификационных номеров и так далее. Это означает, что вы не можете использовать такие ключи в качестве ключей баз данных в вашей собственной системе.
Эвристика
На ранних этапах моей карьеры много раз бывало так, что я решал использовать «естественный ключ» в качестве ключа моей базы данных. Насколько помню, я жалел об этом каждый раз.
Сегодня я следую чёткой эвристике: всегда пользоваться синтетическими ключами для таблиц баз данных.
Заключение
Бывают ли случаи, когда правильно использовать естественные ключи при проектировании базы данных? Мой опыт говорит, что нет. В конечном итоге, вне зависимости от вашей уверенности в том, что естественный ключ стабилен и корректно отслеживает сущность, случаются ошибки данных. В том числе и ошибки в этих естественных ключах.
У вас должна быть возможность исправлять такие ошибки без потери контроля за относящимися к ним сущностям. Вы пожалеете об использовании естественных ключей. Пользуйтесь синтетическими.
Автор: PatientZero