Суррогатный Ключ (СК) - автоматически сгенерированное
поле, никак не связанное с информационным содержанием
записи. Обычно в роли СК выступает автоинкрементное поле
типа INTEGER.
Есть два мнения:
СК должны использоваться, только если ЕК не
существует. Если же ЕК существует, то идентификация записи
внутри БД осуществляется по имеющемуся ЕК;
СК должны добавляться в любую таблицу, на которую
существуют ссылки (REFERENCES) из других таблиц, и связи
между ними должны организовываться только при помощи СК.
Разумеется, поиск записи и представление её пользователю
по прежнему производятся на основании ЕК.
Естественно, можно представить себе и некое промежуточное
мнение, но сейчас дискуссия ведётся в рамках двух
вышеизложенных.
Когда появляются СК
Для понимания места и значения СК рассмотрим этап
проектирования, на котором они вводятся в структуру БД, и
методику их введения.
Для ясности рассмотрим БД из 2-х отношений - Города
(City) и Люди (People) Предполагаем, что город
характеризуется Hазванием (Name), все города имеют разные
названия, человек характеризуется Фамилией (Family), номером
паспорта (Passport) и городом проживания (City). Также
полагаем, что каждый человек имеет уникальный номер
паспорта. Hа этапе составления инфологической модели БД её
структура одинакова и для ЕК и для СК.
CREATE TABLE City(
Name VARCHAR(30) NOT NULL PRIMARY KEY
);
CREATE TABLE People(
Passport CHAR(9) NOT NULL PRIMARY KEY,
Family VARCHAR(20) NOT NULL,
City VARCHAR(30) NOT NULL REFERENCES City(Name)
);
Для ЕК все готово. Для СК делаем еще один этап и
преобразуем таблицы следующим образом:
CREATE TABLE City(
/*
В разных диалектах языка SQL автоинкрементное поле будет выражено по-разному -
например, через IDENTITY, SEQUENCE или GENERATOR.
Здесь мы используем условное обозначение AUTOINCREMENT.
*/
Id INT NOT NULL AUTOINCREMENT PRIMARY KEY
Name VARCHAR(30) NOT NULL UNIQUE
);
CREATE TABLE People(
Id INT NOT NULL AUTOINCREMENT PRIMARY KEY,
Passport CHAR(9) NOT NULL UNIQUE,
Family VARCHAR(20) NOT NULL,
CityId INT NOT NULL REFERENCES City(Id)
);
Обращаю внимание, что:
Все условия, диктуемые предметной областью
(уникальность имени города и номера паспорта) продолжают
присутствовать в БД, только обеспечиваются не условием
PRIMARY KEY, а условием UNIQUE;
Ключевого слова AUTOINCREMENT ни в одном из известных
мне серверов нет. Это просто обозначение, что поле
генерируется автоматически.
В общем случае алгоритм добавления СК выглядит следующим
образом:
В таблицу добавляется поле INTEGER AUTOINCREMENT;
Оно объявляется PRIMARY KEY;
Старый PRIMARY KEY (ЕК) заменяется на UNIQUE
CONSTRAINT ;
Если в таблице есть REFERENCES на другие таблицы, то
поля, входящие в REFERENCES, заменяются на одно поле типа
INTEGER, составляющее первичный ключ (как People.City
заменена на People.CityId).
Это механическая операция, которая никак не нарушает
инфологической модели и целостности данных. С точки зрения
инфологической модели эти две базы данных эквивалентны.
Зачем всё это надо
Возникает резонный вопрос - а зачем? Действительно,
вводить в таблицы какие-то поля, что-то заменять, зачем?
Итак, что мы получаем, проделав эту "механическую"
операцию.
Упрощение сопровождения
Это область, где СК демонстрируют наибольшие
преимущества. Поскольку операции связи между таблицами
отделены от логики "внутри таблиц" - и то и другое можно
менять независимо и не затрагивая остального.
Hапример - выяснилось, что города имеют дублирующиеся
названия. Решено ввести в City еще одно поле - Регион
(Region) и сделать ПК (City, Region). В случае ЕК -
изменяется таблица City, изменяется таблица People -
добавляется поле Region (да, да, для всех записей, про
размеры молчу), переписываются все запросы, в том числе на
клиентах, в которых участвует City, в них добавляются строка
AND XXX.Region = City.Region.
Да, чуть не забыл, большинство серверов сильно не любят
ALTER TABLE на поля, входящие в PRIMARY KEY и FOREIGN
KEY.
В случае СК - добавляется поле в City, изменяется UNIQUE
CONSTRAINT. Всё.
Еще пример - в случае СК изменение списка полей в SELECT
никогда не заставляет переписывать JOIN. В случае ЕК -
добавилось поле, не входящее в ПК связанной таблицы -
переписывайте.
Еще пример - поменялся тип данных поля, входящего в ЕК. И
опять переделки кучи таблиц, заново оптимизация
индексов...
В условиях меняющегося законодательства это достоинство
СК само по себе достаточно для их использования.
Уменьшение размера БД
Предположим в нашем примере, что средняя длина названия
города - 10 байт. Тогда на каждого человека в среднем будет
приходиться 10 байт для хранения ссылки на город (реально
несколько больше за счёт служебной информации на VARCHAR и
гораздо больше за счёт индекса по People.City, который
придётся построить, чтобы REFERENCES работала эффективно). В
случае СК - 4 байта. Экономия - минимум 6 байт на человека,
приблизительно 10 Мб для г. Hовосибирска. Очевидно, что
в большинстве случаев уменьшение размера БД - не самоцель,
но это, очевидно, приведет и к росту быстродействия.
Звучали аргументы, что БД может сама оптимизировать
хранение ЕК, подставив вместо него в People некую
хэш-функцию (фактически создав СК сама). Hо ни один из
реально существующих коммерческих серверов БД так не делает,
и есть основания полагать, что и не будет делать. Простейшим
обоснованием такого мнения является то, что при подобной
подстановке банальные операторы ADD CONSTRAINT … FOREIGN KEY
или DROP CONSTRAINT … FOREIGN KEY будут приводить к
нешуточной перетряске таблиц, с ощутимым изменением всей БД
(надо будет физически добавить или удалить (с заменой на
хэш-функцию)) все поля, входящие в CONSTRAINT.
Увеличение скорости выборки данных
Вопрос достаточно спорный, однако, исходя из
предположений, что:
База данных нормализована;
Записей в таблицах много (десятки тысяч и более);
Запросы преимущественно возвращают ограниченные наборы
данных (максимум единицы процентов от размера таблицы).
быстродействие системы на СК будет ощутимо выше. И вот
почему:
ЕК могут потенциально дать более высокое быстродействие,
когда:
Требуется только информация, входящая в первичные
ключи связанных таблиц;
нет условий WHERE по полям связанных таблиц.
Т.е., в нашем примере это запрос типа:
SELECT Family, City FROM People;
В случае СК этот запрос будет выглядеть как
SELECT P.Family, C.Name
FROM People P INNER JOIN City C ON P.CityId = C.Id;
Казалось бы, ЕК дает более простой запрос с меньшим
количеством таблиц, который выполнится быстрее. Hо и тут не
всё так просто: размеры таблиц для ЕК - больше
(см. выше) и дисковая активность легко съест
преимущество, полученное за счёт отсутствия JOIN`а. Ещё
сильнее это скажется, если при выборке данных используется
их фильтрование (а при сколько-либо существенном объеме
таблиц оно используется обязательно). Дело в том, что поиск,
как правило, осуществляется по информативным полям типа
CHAR, DATETIME и т.п. Поэтому часто бывает быстрее найти в
справочной таблице набор значений, ограничивающий
возвращаемый запросом результат, а затем путем JOIN`а по
быстрому INTEGER-индексу отобрать подходящие записи из
большой таблицы. Например:
(ЕК) SELECT Family, City FROM People WHERE City = 'Иваново';
будет выполняться в разы медленнее, чем
(CК) SELECT P.Family, C.Name
FROM People P INNER JOIN City C ON P.CityId = C.Id
WHERE C.Name = 'Иваново';
В случае ЕК - будет INDEX SCAN большой таблицы People по
CHARACTER-индексу. В случае СК - INDEX SCAN меньшей CITY и
JOIN по эффективному INTEGER индексу.
А вот если заменить = 'Иваново' на LIKE '%ваново', то
речь пойдет о торможении ЕК относительно СК на порядок и
более.
Аналогично, как только в случае с ЕК понадобится включить
в запрос поле из City, не входящее в её первичный ключ -
JOIN будет осуществлятся по медленному индексу и
быстродействие упадет ощутимо ниже уровня СК. Выводы каждый
может делать сам, но пусть он вспомнит, какой процент от
общего числа его запросов составляют SELECT * FROM
ЕдинственнаяТаблица. У меня - ничтожно малый.
Да, сторонники ЕК любят проводить в качестве достоинства
"информативность таблиц", которая в случае ЕК растет. Ещё
раз повторю, что максимальной информативностью обладает
таблица, содержащая всю БД в виде flat-file. Любое
"повышение информативности таблиц" есть увеличение степени
дублирования в них информации, что не есть хорошо.
Увеличение скорости обновления данных
INSERT
Hа первый взгляд ЕК быстрее - не надо при INSERT
генерировать лишнего поля и проверять его уникальность. В
общем-то так оно и есть, хотя это замедление проявляется
только при очень высокой интенсивности транзакций. Впрочем и
это неочевидно, т.к. некоторые серверы оптимизируют вставку
записей, если по ключевому полю построен монотонно
возрастающий CLUSTERED индекс. В случае СК это элементарно,
в случае ЕК - увы, обычно недостижимо. Кроме этого, INSERT в
таблицу на стороне MANY (который происходит чаще) пойдет
быстрее, т.к. REFERENCES будут проверяться по более быстрому
индексу.
UPDATE
При обновлении поля, входящего в ЕК, придётся каскадно
обновить и все связанные таблицы. Так, переименование
Ленинграда в Санкт-Петербург потребует с нашем примере
транзакции на несколько миллионов записей. Обновление любого
атрибута в системе с СК приведет к обновлению только одной
записи. Очевидно, что в случае распределенной системы,
наличия архивов и т.п. ситуация только усугубится. Если
обновляются поля не входящие в ЕК – быстродействие будет
почти одинаковым.
Еще о CASCADE UPDATE
Далеко не все серверы БД поддерживают их на декларативном
уровне. Аргументы "это у вас сервер кривой" в этом случае
вряд ли корректны. Это вынуждает писать отдельную логику для
обновления, что не всегда просто (приводился хороший пример
- при отсутствии CASCADE UPDATE обновить поле, на которое
есть ссылки, вообще невозможно - надо отключать REFERENCES
или создавать копию записи, что не всегда допустимо (другие
поля могут быть UNIQUE)).
DELETE
В случае СК будет выполняться быстрее, по той простой
причине, что проверка REFERENCES пойдет по быстрому
индексу.
А есть ли хорошие ЕК?
Hичто не вечно под Луной. Самый, казалось бы, надежный
атрибут вдруг отменяется и перестаёт быть уникальным (далеко
ходить не буду - рубль обычный и рубль деноминированный,
примерам несть числа). Американцы ругаются на неуникальность
номера социального страхования, Microsoft - на китайские
серые сетевые платы с дублирующимися MAC-адресами, которые
могут привести к дублированию GUID, врачи делают операции по
смене пола, а биологи клонируют животных. В этих условиях (и
учитывая закон неубывания энтропии) закладывать в систему
тезис о неизменности ЕК - закладывать под себя мину. Их надо
выделять в отдельный логический слой и по возможности
изолировать от остальной информации. Так их изменение
переживается куда легче. Да и вообще: однозначно
ассоциировать сущность с каким-то из атрибутов этой сущности
- ну, странно, что-ли. Hомер паспорта ещё не есть человек.
СК же - это некая субстанция, именно и означающая сущность.
Именно сущность, а не какой-то из её атрибутов.
Типичные аргументы сторонников ЕК
В системе с СК не осуществляется контроль правильности
ввода информации
Это не так. Контроль не осуществлялся бы, если бы на
поля, входящие в ЕК не было наложено ограничение
уникальности. Очевидно, что если предметная область диктует
какие-то ограничения на атрибуты ЕК, то они будут отражены в
БД в любом случае.
В системе с ЕК меньше JOIN`ов, следовательно, запросы
проще и разработка удобнее
Да, меньше. Hо, в системе с СК тривиально пишется:
CREATE VIEW PeopleEK AS
SELECT P.Family, P.Passport, C.Name
FROM People P INNER JOIN City C ON P.CityId = C.Id
И можно иметь все те же прелести. С более, правда,
высоким быстродействием. При этом неплохо упомянуть, что в
случае ЕК многим придется программировать каскадные
операции, и, не дай Бог в распределённой среде, бороться с
проблемами быстродействия. Hа фоне этого "короткие" запросы
уже не кажутся столь привлекательными.
Введение ЕК нарушает третью нормальную форму
Вспомним определение:
Таблица находится в третьей
нормальной форме (3НФ), если она удовлетворяет определению
2НФ, и ни одно из её неключевых полей не зависит
функционально от любого другого неключевого поля.
То есть, речи о ключевых полях там не идёт вообще.
Поэтому добавление ещё одного ключа в таблицу ни в коей мере
не может нарушить 3НФ. Вообще, для таблицы с несколькими
возможными ключами имеет смысл говорить не о 3 НФ, а о
Нормальной Форме Бойса-Кодда, которая специально введена для
таких таблиц.
Итак:
Таблица находится в нормальной форме Бойса-Кодда
(НФБК), если и только если любая функциональная зависимость
между его полями сводится к полной функциональной
зависимости от возможного ключа.
Таким образом, таблица, имеющая СК, легко может быть
нормализована хоть до 5НФ. Точнее будет сказать, что СК к
нормализации не имеют никакого отношения. Более того,
введение СК уменьшает избыточность данных в БД, что вообще
хорошо согласуется с идеологией нормализации. В сущности,
нормализация и есть уменьшение информативности отдельных
таблиц по определенным правилам. Только СК устраняют
аномалии не внутри таблицы, а на межтабличном уровне (типа
устранения каскадных обновлений). Так сказать, система с СК
- святее Папы Римского :-). В самом деле – ситуация, когда
при изменении одного из полей таблицы приходится изменять
содержимое этого же поля в других записях ЭТОЙ ЖЕ таблицы,
рассматривается как аномалия обновления. Но в системе с ЕК
придется проделать то же самое В СВЯЗАННОЙ таблице при
изменении ключевого атрибута на стороне 1 отношения 1:N.
Очевидно, что эта ситуация с точки зрения физической
реализации БД ничем не лучше. В системе с СК таких ситуаций
не возникает.
Таблицы в системе с ЕК информативнее
Максимальной информативностью обладает таблица,
содержащая всю БД в виде flat-file. Любое "повышение
информативности таблиц" есть увеличение степени дублирования
в них информации, что не обязательно есть хорошо. Да и
вообще термин "Информативность таблицы" сомнителен. Видимо,
более важна информативность БД, которая в обоих случаях
одинакова.
Заключение
В общем-то, выводы очевидны – введение СК позволяет
получить лучше управляемую, более компактную и
быстродействующую БД. Разумеется, это не панацея. В
некоторых случаях (например, таблица на которую нет
REFERENCES и в которую осуществляется интенсивная вставка
данных и т.п.) более верно использовать ЕК или не
использовать ПК вообще (последнее категорически
противопоказано для многих РСУБД и средств разработки
клиентских приложений). Но речь шла именно о типовой
методике, которую надо рекомендовать к применению в общем
случае. Уникальные ситуации могут потребовать уникальных же
решений (иногда и нормализацией приходится поступаться).
Тенцер А. Л.
ICQ UIN 15925834