Является ли хорошей практикой всегда иметь первичный ключ с автоинкрементом?


191

В моих базах данных у меня есть привычка иметь автоинкрементный первичный ключ целого числа с именем idдля каждой таблицы, которую я создаю, чтобы у меня был уникальный поиск для любой конкретной строки.

Это считается плохой идеей? Есть ли недостатки сделать это таким образом? Иногда у меня будет несколько индексов, например, id, profile_id, subscriptionsгде idнаходится уникальный идентификатор, profile_idссылки на idсторонние Profileтаблицы и т. Д.

Или есть сценарии, когда вы не хотите добавлять такое поле?


61
Взгляните на проблему немецкого танка для примера, где проблема заключается в простом автоматически увеличивающемся идентификаторе. Конечно, это имеет значение, только если вы используете свои идентификаторы публично.
Берги

24
@ArukaJ Дело в том, что он дает некоторую информацию о системе. Например, предположим, что база данных содержит написанные пользователем сообщения, каждое из которых получает последовательный идентификатор. Скажем, вы делаете четыре сообщения, каждое из которых получает идентификатор: в 4 утра (20), 5 утра (25), 8 вечера (100) и 9 вечера (200). Посмотрев на идентификаторы, вы увидите, что только 5 сообщений были добавлены между 4 утра и 5 утра, а 100 были добавлены между 8 вечера и 9 вечера. Если вы пытаетесь выбрать время для атаки типа «отказ в обслуживании», это может быть ценной информацией.
Джошуа Тейлор

29
Всем, кто жалуется на «проблему немецкого танка» .... если единственное, что мешает кому-либо получить доступ к данным, это не ключ к вашему URL ... у вас проблемы больше, чем GUID по сравнению с Auto INT.
Мэтью Уайтед

11
@MatthewWhited Речь идет не только об обмене параметрами в URL. Предположим, вы используете сайт и создаете ресурс 100 одновременно t, а ресурс 120 - одновременно t + 60. Если вы видите оба этих идентификатора (100 и 120) в необсуждаемом виде, теперь вы знаете общее количество существующих активов, а также приблизительную скорость их создания. Это утечка информации. Это не чисто гипотетически.
Крис Хейс

15
«Это хорошая практика, чтобы всегда ...» Нет.
brian_o

Ответы:


137

Это никогда не плохая идея иметь гарантированный уникальный идентификатор строки. Думаю, я не должен говорить никогда - но давайте согласимся с подавляющим большинством времени, это хорошая идея.

Теоретические потенциальные недостатки включают дополнительный индекс для обслуживания и дополнительное место для хранения. Это никогда не было достаточной причиной для меня, чтобы не использовать один.


11
Это то, чем я занимаюсь. Большинство людей используют либо id, либо tablename_id (например, user_id). Аргумент обычно не в том случае, если нужен столбец, а в том, как его назвать.
GrandmasterB

103
Лично я думаю, что имя таблицы должно подразумевать все остальное. TableName.idв отличие от TableName.TableName_id, потому что что еще это idбудет означать? Если у меня есть другое поле идентификатора в таблице, я добавлю к нему имя таблицы, если оно ссылается на какую-то другую таблицу
AJJ

10
@ArukaJ вы упомянули, что используете SQLite. Это на самом деле немного особый случай, так как он всегда создает такую ​​колонку «под капотом». Таким образом, вы даже не используете дополнительное пространство, потому что вы получаете его независимо от того, хотите вы этого или нет. Кроме того, идентификатор строки SQLite всегда является 64-битным целым числом. Если я правильно понимаю, что если вы определите строку с автоинкрементом, это будет псевдоним внутреннего идентификатора строки. Так что вы могли бы хорошо всегда делать это! См. Sqlite.org/autoinc.html
GrandmasterB

9
Единственное исключение, о котором я могу подумать, - это если у вас есть уникальный идентификатор, который генерируется другим способом, и в этом случае это должен быть первичный ключ, а идентификатор с автоинкрементом избыточен.
HamHamJ

4
@GrandmasterB: Текущая версия SQLite позволяет создавать WITHOUT ROWIDтаблицы (с явными PRIMARY KEY) в качестве оптимизации. Но в противном случае INTEGER PRIMARY KEYстолбец является псевдонимом для rowid.
Ден04

92

Я не согласен со всеми ответами раньше. Существует множество причин, по которым плохая идея добавлять поле автоинкремента во все таблицы.

Если у вас есть таблица, в которой нет явных ключей, поле автоинкремента кажется хорошей идеей. В конце концов, вы не хотите select * from blog where body = '[10000 character string]'. Вы бы предпочли select * from blog where id = 42. Я бы сказал, что в большинстве этих случаев вам действительно нужен уникальный идентификатор; не последовательный уникальный идентификатор. Вы, вероятно, хотите использовать универсально уникальный идентификатор вместо этого.

В большинстве баз данных есть функции для генерации случайных уникальных идентификаторов ( uuidв mysql, postgres. newidВ mssql). Они позволяют генерировать данные в несколько баз данных, на разных машинах, в любое время, без сетевого соединения между ними, и при этом объединять данные с нулевым конфликтом. Это позволяет упростить настройку нескольких серверов и даже центров обработки данных, например, с помощью микросервисов.

Это также позволяет избежать угадывания злоумышленниками URL-адресов страниц, к которым у них не должно быть доступа. Если есть, https://example.com/user/1263то, вероятно, https://example.com/user/1262также. Это может позволить автоматизировать эксплойт безопасности на странице профиля пользователя.

Есть также много случаев, когда столбец uuid бесполезен или даже вреден. Допустим, у вас есть социальная сеть. Есть usersстол и friendsстол. Таблица друзей содержит два столбца идентификаторов пользователей и поле автоинкремента. Вы хотите 3дружить 5, поэтому вы вставляете 3,5в базу данных. База данных добавляет идентификатор автоинкремента и сохраняет 1,3,5. Почему-то пользователь 3снова нажимает кнопку «Добавить друга». Вы 3,5снова вставляете в базу данных, база данных добавляет идентификатор автоинкремента и вставляет 2,3,5. Но сейчас 3и 5дружим вдвойне! Это пустая трата места, и если подумать, то же самое относится и к столбцу автоинкремента. Все, что вам нужно, чтобы увидеть, если aиbдрузья, чтобы выбрать для строки с этими двумя значениями. Вместе они являются уникальным идентификатором строки. (Вы, вероятно , хотите сделать написать некоторую логику , чтобы убедиться , что 3,5и 5,3являются дедуплицированными.)

Есть все еще случаи, когда последовательные идентификаторы могут быть полезны, например, при создании сокращающего URL-адреса, но в основном (и даже с сокращающим URL-адресом) случайно генерируемый уникальный идентификатор - это то, что вы действительно хотите использовать вместо этого.

TL; DR: используйте UUID вместо автоинкремента, если у вас еще нет уникального способа идентификации каждой строки.


26
Проблема с UUID состоит в том, что они занимают слишком много места для большинства таблиц. Используйте правильный уникальный идентификатор для каждой таблицы.
Стивен

49
Весь параграф о уникальности является спорным - уникальность может быть обеспечена, с или без первичного ключа. Кроме того, UUID лучше с теоретической точки зрения, но их ужасно использовать при отладке / выполнении задач администратора баз данных или выполнении других действий, которые не «противостоят атакам».

11
Другой сценарий, когда UUID лучше: реализация идемпотентной операции PUT, чтобы вы могли безопасно повторять запросы, не вводя дублирующиеся строки.
Юрез

21
С точки зрения «угадывания URL» наличие уникального идентификатора (последовательного или иного) не означает раскрытие этого идентификатора пользователям приложения.
Дейв Шерохман

7
Чисто с точки зрения базы данных, этот ответ совершенно неверен. Использование UUID вместо автоматически увеличивающихся целых чисел приводит к слишком быстрому росту индексов и отрицательно влияет на производительность и потребление памяти. Если вы говорите с точки зрения веб-службы или веб-приложения, в любом случае между базой данных и внешним интерфейсом должен быть слой. Все остальное - плохой дизайн. Использование данных в качестве первичного ключа еще хуже. Первичные ключи следует использовать только на уровне данных, и больше нигде.
Пьяный Код Обезьяны

60

Автоинструментальные ключи имеют в основном преимущества.

Но некоторые возможные недостатки могут быть:

  • Если у вас есть бизнес-ключ, вам также необходимо добавить уникальный индекс в этот столбец (столбцы), чтобы обеспечить соблюдение бизнес-правил.
  • При передаче данных между двумя базами данных, особенно когда данные находятся в более чем одной таблице (т. Е. Основная / подробная информация), это не так просто, поскольку последовательности не синхронизируются между базами данных, и вам придется сначала создать таблицу эквивалентности, используя бизнес-ключ как совпадение, чтобы узнать, какой идентификатор из исходной базы данных соответствует какому идентификатору в целевой базе данных. Это не должно быть проблемой при переносе данных из / в изолированные таблицы.
  • На многих предприятиях имеются специальные, графические инструменты для составления отчетов с помощью перетаскивания. Поскольку автоинкрементные идентификаторы не имеют смысла, пользователям этого типа будет трудно разобраться в данных за пределами «приложения».
  • Если вы случайно измените бизнес-ключ, скорее всего, вы никогда не восстановите эту строку, потому что у вас больше не будет людей, которые могли бы ее идентифицировать. Это однажды вызвало ошибку в платформе BitCoin .
  • Некоторые разработчики добавляют идентификатор в таблицу соединения между двумя таблицами, когда PK должен просто состоять из двух внешних идентификаторов. Очевидно, что если таблица соединения находится между тремя или более таблицами, то автоинкрементный идентификатор имеет смысл, но тогда вам нужно добавить уникальный ключ, когда он применяется к комбинации FK, для обеспечения соблюдения бизнес-правил.

Вот раздел статьи Википедии о недостатках суррогатных ключей.


13
Обвинение недостатка mt.gox в суррогатных ключах кажется довольно сомнительным. Проблема заключалась в том, что они включили все поля в свой составной ключ, даже изменяемые / изменяемые поля.
CodesInChaos

6
«Социальный» недостаток использования ключей автоинкремента состоит в том, что иногда «бизнес» предполагает, что никогда не должно быть пробелов, и требует знать, что произошло с пропущенными строками, которые происходят при неудачной вставке (откат транзакции).
Рик Райкер

4
Другим недостатком является то, что если система становится настолько большой, что вам приходится ограждать базу данных, вы больше не можете использовать автоинкремент для создания глобального уникального ключа. Когда вы дойдете до этого момента, у вас может быть много кода, основанного на этом предположении. Есть и другие способы создания уникального идентификатора, который будет продолжать работать, если база данных будет очищена.
Касперд

1
@ Voo Не гарантируется, что выбранная вами база данных поддерживает это. И попытка реализовать его на более высоком уровне, чем сама база данных, означает, что вы потеряете некоторые гарантии, которые даст вам SQL. Наконец, любое централизованное назначение идентификаторов увеличит задержку, если у вас распределенная система.
Касперд

1
@ Voo Конечно, независимо от масштаба системы, не следует делать слишком много предположений о характере автоинкрементных идентификаторов. Если у вас есть только одна база данных, они назначаются по порядку, но нет гарантии, что они переданы по порядку. И в последовательности может быть пробел, потому что не все транзакции зафиксированы.
Касперд

20

Просто чтобы быть противоположным, нет, вам не нужно всегда иметь числовой ПК AutoInc.

Если вы тщательно анализируете свои данные, вы часто идентифицируете естественные ключи в данных. Это часто тот случай, когда данные имеют внутреннее значение для бизнеса. Иногда PK - это артефакты древних систем, которые бизнес-пользователи используют в качестве второго языка для описания атрибутов своей системы. Например, я видел VIN-номера транспортных средств, используемые в качестве основного ключа таблицы «Автомобиль» в системе управления автопарком.

Как бы то ни было, если у вас уже есть уникальный идентификатор, используйте его. Не создавайте второй, бессмысленный первичный ключ; это расточительно и может привести к ошибкам.

Иногда вы можете использовать AutoInc PK для создания значимого для клиента значения, например, номера политик. Установка начального значения на что-то разумное и применение бизнес-правил о ведущих нулях и т. Д. Это, вероятно, подход «лучшее из обоих миров».

Если у вас есть небольшое количество значений, которые являются относительно статическими, используйте значения, которые имеют смысл для пользователя системы. Зачем использовать 1,2,3, если вы можете использовать L, C, H, где L, H и C представляют Life, Car и Home в контексте страхового «Типа полиса», или, возвращаясь к примеру VIN, как насчет использования «TO» для Тойоты? Все автомобили Toyata имеют VIN-код, начинающийся с буквы «TO». Пользователи должны помнить об этом меньше, снижают вероятность появления ошибок программирования и ошибок пользователя и даже могут быть полезным заменителем полного описания в управленческих отчетах, делая отчеты проще. писать и, возможно, быстрее генерировать.

Дальнейшее развитие этого вопроса, вероятно, является «слишком большим мостом», и я, как правило, не рекомендую его, но я включаю его для полноты, и вы можете найти для него хорошее применение. То есть используйте описание в качестве первичного ключа. Для быстро меняющихся данных это мерзость. Для очень статичных данных, которые сообщаются в All The Time , возможно, нет. Просто упомянув об этом, чтобы он сидел там как возможность.

Я действительно использую AutoInc PKs, я просто задействую свой мозг и сначала ищу лучшие альтернативы. Искусство проектирования баз данных делает что-то значимое, что можно быстро запросить. Слишком много соединений мешает этому.

РЕДАКТИРОВАТЬ Еще один важный случай, когда вам не нужен автоматически сгенерированный PK, - это случай таблиц, которые представляют пересечение двух других таблиц. Чтобы придерживаться аналогии с автомобилем, у автомобиля есть 0 ..n аксессуаров, каждый аксессуар можно найти на многих автомобилях. Таким образом, чтобы представить это, Вы создаете таблицу Car_Accessory, содержащую PK от Car и Accessory и другую соответствующую информацию о Дате ссылки и т. Д.

То, что вам (обычно) не нужно, - это AutoInc PK на этом столе - доступ к нему можно получить только через автомобиль «скажи мне, какие аксессуары есть на этом автомобиле» или из аксессуара «скажи им, какие автомобили имеют этот аксессуар»


4
> У всех автомобилей Toyata есть VIN, начинающийся с «TO». Это просто неправда. Они начинаются с "JT", если сделано в Японии. Тойоты, построенные в Америке, имеют совершенно разные VIN- ы. En.wikibooks.org/wiki/…
Монти Хардер

17
Don't create a second, meaningless primary key; it's wasteful and may cause errors.Однако, если способ, которым вы устанавливаете уникальность для записи, представляет собой комбинацию из 6 столбцов, то объединение всех 6 всегда очень подвержено ошибкам. Естественно, у данных есть PK, но вам лучше использовать idстолбец и уникальное ограничение для этих 6 столбцов.
Брэд

14
Я признаю, что некоторые из этих предложений слишком далеки от меня. Да, быть прагматичным - это хорошо, но я не могу сосчитать, как часто кто-то клялся в жизни своего первенца, что какой-то атрибут вне домена будет оставаться уникальным в течение оставшихся дней. Ну, обычно это работало хорошо до второй недели после выхода в эфир, когда появились первые дубликаты. ;) Использование "описания" в качестве ПК просто далеко.
AnoE

2
@ Монти, мой плохой, ты прав. Ошибочная память, прошло 20 лет с тех пор, как я спроектировал системы управления автопарком. Нет, VIN не был первичным ключом :) Я использовал AutoInc Asset_ID IIRC, что приводит к тому, что я забыл. Таблицы, которые являются связующими для отношений «многие ко многим», когда вы связываете, скажем, автомобиль с аксессуаром (например, люк на крыше). У многих автомобилей есть много аксессуаров, поэтому вам нужна таблица «Car_Accessory», которая содержит Car_ID и Accessory_ID, но абсолютно НЕ требует Car_Accesory_ID как AutoInc PK.
Маккотл

7
Удивительно, как мало настоящих «настоящих ключей» TRULY. ПЛА - х? Нет, они могут измениться. Это редко, но это может случиться. Usernames? Нет. В конце концов, у кого-то будет веская деловая причина для изменения. VIN часто является примером из учебника, но других не так много. Даже домашние адреса могут измениться, учитывая изменения названий улиц.
Эрик Фанкенбуш

12

Многие таблицы уже имеют естественный уникальный идентификатор. Не добавляйте в эти таблицы еще один столбец уникальных идентификаторов (с автоинкрементом или иным образом). Вместо этого используйте естественный уникальный идентификатор. Если вы добавите еще один уникальный идентификатор, у вас по существу будет избыточность (дублирование или зависимость) в ваших данных. Это идет вразрез с принципами нормализации. Один уникальный идентификатор зависит от другого для точности. Это означает , что они должны быть идеально синхронизирована в любое время в любой системе , которая управляет этими строками. Это просто еще одна хрупкость в целостности ваших данных, которой вы на самом деле не хотите управлять и проверять в долгосрочной перспективе.

В настоящее время большинству таблиц действительно не требуется очень незначительное повышение производительности, которое может дать дополнительный столбец уникальных идентификаторов (а иногда это даже снижает производительность). Как правило в ИТ, избегайте избыточности, как чума! Сопротивляйтесь этому везде, где вам это предлагается. Это анафема. И прислушайся к цитате. Все должно быть максимально просто, но не проще. Не имейте двух уникальных идентификаторов, где одного будет достаточно, даже если естественный кажется менее аккуратным.


3
Разве вы не должны использовать «естественные» идентификаторы в качестве первичных ключей, если они абсолютно гарантированно никогда не изменятся? Например, вы не должны использовать номер водительской лицензии в качестве первичного ключа, потому что, если человек получает новую водительскую лицензию, вам нужно обновить не только эту таблицу, но и любые таблицы с внешними ключами, ссылающимися на нее!
Эколис

1
Существует несколько причин, по которым номер водительского удостоверения не считается естественным уникальным идентификатором. Во-первых, некоторые из них получены из других данных, таких как дата рождения и имя. Они не гарантированы уникальными в разных штатах. И, к примеру, когда человеку повторно выдают лицензию с тем же номером, но, возможно, с расширенным сроком действия, что происходит потом? У них другая лицензия с тем же номером. Естественный идентификатор все еще должен удовлетворять основным свойствам первичного ключа. Номер водительского удостоверения (по крайней мере, в США) имеет некоторые недостатки в этом отношении.
Брэд Томас

1
ОК, тогда я неправильно понял определение натурального идентификатора. Я думал, что это просто идентификатор, определяемый бизнес-правилами, независимо от того, гарантированно ли он будет неизменным.
Эколис

10

В больших системах ID повышает согласованность, используйте его практически везде. В этом контексте отдельные первичные ключи НЕ рекомендуются, они стоят дорого в конце (прочитайте почему).

Каждое правило имеет исключение, поэтому вам может не понадобиться целочисленный идентификатор автоинкремента для промежуточных таблиц, используемых для экспорта / импорта, и для похожих односторонних таблиц или временных таблиц. Вы также предпочли бы идентификаторы GUID вместо идентификаторов в распределенных системах.

Многие ответы здесь предполагают, что существующий уникальный ключ должен быть взят. Ну даже если в нем 150 символов? Я так не думаю.

Теперь моя главная мысль:

Похоже, что противники целочисленного идентификатора автоинкремента говорят о небольших базах данных до 20 таблиц. Там они могут позволить себе индивидуальный подход к каждому столу.

НО, если у вас есть ERP с 400+ таблицами, иметь целочисленный идентификатор автоинкремента в любом месте (кроме случаев, упомянутых выше) просто имеет смысл. Вы не полагаетесь на другие уникальные поля, даже если они присутствуют и защищены для уникальности.

  • Вы получаете выгоду от универсального соглашения, которое экономит время, усилия и легко запоминается.
  • В большинстве случаев вы JOINтаблицы, без необходимости проверять, какие ключи.
  • Вы можете иметь универсальные подпрограммы кода, работающие с вашим целочисленным автоинкрементным столбцом.
  • Вы можете расширить свою систему новыми таблицами или пользовательскими плагинами, которые не предусмотрены ранее, просто обратившись к идентификаторам существующих таблиц. Они уже есть с самого начала, не нужно добавлять их дополнительно.

В более крупных системах стоит игнорировать незначительные преимущества этих отдельных первичных ключей и в большинстве случаев последовательно использовать целочисленный идентификатор автоинкремента. Использование существующих уникальных полей в качестве первичных ключей, возможно, экономит несколько байтов на запись, но дополнительное хранилище или время индексации не представляют проблемы в современных механизмах баз данных. На самом деле вы теряете гораздо больше денег и ресурсов из-за потерянного времени разработчиков / разработчиков. Современное программное обеспечение должно быть оптимизировано по времени и усилиям программистов - какой подход с согласованными идентификаторами выполняет намного лучше.


Исходя из личного опыта, я искренне согласен со второй половиной вашего ответа. Вам понадобятся глобально уникальные ключи гораздо реже, чем быстрые и компактные индексы. Если вам это нужно, создайте таблицу GlobalEntities с автоматически сгенерированным идентификатором и столбцом UUID. Затем добавьте внешний ключ ExGlobalEntityId, например, в таблицу Customers. Или используйте хэш некоторых значений.
Пьяный код Обезьяна

8

Это не хорошая практика для лишних конструкций. Т.е. не рекомендуется всегда иметь автоинкрементный первичный ключ, когда он не нужен.

Давайте посмотрим на пример, где он не нужен.

У вас есть таблица статей - она ​​имеет первичный ключ int idи столбец varchar с именем title.

У вас также есть таблица, заполненная категориями статей - idint primary key, varchar name.

В одной строке таблицы «Статьи» есть id5 и title «Как приготовить гуся с маслом». Вы хотите связать эту статью со следующими строками в вашей таблице категорий: «Мясо птицы» ( id : 20), «Гусь» ( id : 12), «Готовка» ( id : 2), «Масло» (id: 9) ,

Теперь у вас есть 2 таблицы: статьи и категории. Как вы создаете отношения между ними?

Вы можете иметь таблицу с 3 столбцами: id (первичный ключ), article_id (внешний ключ), category_id (внешний ключ). Но теперь у вас есть что-то вроде:

| id | a_id | c_id |
| 1 | 5 | 20 |
| 2 | 5 | 12 |
| 3 | 5 | 2 |

Лучшее решение - иметь первичный ключ, состоящий из 2 столбцов.

| a_id | c_id |
| 5 | 20 |
| 5 | 12 |
| 5 | 2 |

Это может быть достигнуто путем:

create table articles_categories (
  article_id bigint,
  category_id bigint,
  primary key (article_id, category_id)
) engine=InnoDB;

Другая причина не использовать целочисленное значение с автоинкрементом состоит в том, что вы используете UUID для своего первичного ключа.

UUID по своему определению уникальны, что выполняет то же самое, что и использование уникальных целых чисел. У них также есть свои собственные дополнительные преимущества (и недостатки) по сравнению с целыми числами. Например, с UUID вы знаете, что уникальная строка, на которую вы ссылаетесь, указывает на конкретную запись данных; это полезно в тех случаях, когда у вас нет 1 центральной базы данных или когда приложения имеют возможность создавать записи данных в автономном режиме (а затем загружать их в базу данных позднее).

В конце концов, вам не нужно думать о первичных ключах как о вещи. Вы должны думать о них как о функции, которую они выполняют. Зачем вам нужны первичные ключи? Чтобы можно было однозначно идентифицировать определенные наборы данных из таблицы, используя поле, которое не будет изменено в будущем. Вам нужен определенный столбец, вызываемый idдля этого, или вы можете основать эту уникальную идентификацию на других (неизменных) данных?


7

Или есть сценарии, когда вы не хотите добавлять такое поле?

Конечно.

Прежде всего, существуют базы данных, у которых нет автоинкрементов (например, Oracle, который, конечно, не является одним из самых маленьких претендентов). Это должно быть первым признаком того, что не все любят или нуждаются в них.

Еще более важно, подумайте о том, что идентификатор фактически является - это первичный ключ для ваших данных. Если у вас есть таблица с другим первичным ключом, то вам не нужен идентификатор, и он не должен быть. Например, таблица (EMPLOYEE_ID, TEAM_ID)(где каждый сотрудник может быть в нескольких командах одновременно) имеет четко определенный первичный ключ, состоящий из этих двух идентификаторов. Добавление IDстолбца автоинкремента , который также является первичным ключом для этой таблицы, не имеет никакого смысла. Теперь вы таскаете 2 первичных ключа, и первое слово в «первичном ключе» должно дать вам подсказку, что у вас действительно должен быть только один.


9
(Не пользователь Oracle, так что прости вопрос, но) разве Oracle не использует Sequence так же, как другие используют Autoincrement / Identity? Говорят ли о том, что у Oracle нет типа данных автоинкремента, на самом деле это просто семантический аргумент?
Брэд

Ну, это был только маленький момент; основная часть заключается в том, что рабочий идентификатор не подходит для каждой таблицы, поэтому привыкание к простому наложению автоматического идентификатора на каждую таблицу может оказаться не самым мудрым.
AnoE

нет двух первичных ключей, есть только один первичный ключ, а все остальные называются ключами-кандидатами, если они тоже могут служить первичными ключами ..
rahul tyagi

7

Я обычно использую столбец «идентичность» (автоинкрементное целое число) при определении новых таблиц для «долгоживущих» данных (записи, которые я ожидаю вставить один раз и хранить бесконечно, даже если они в конечном итоге «логически удаляются» путем установки битового поля ).

Я могу подумать о нескольких ситуациях, когда вы не хотите их использовать, большинство из которых сводятся к сценариям, когда одна таблица в одном экземпляре БД не может быть официальным источником новых значений идентификаторов:

  • При добавочных идентификаторах было бы слишком много информации для потенциального злоумышленника. Использование столбца идентификаторов для «общедоступных» служб данных делает вас уязвимым перед «немецкой проблемой танков»; если идентификатор записи 10234 существует, то, очевидно, существует запись 10233, 10232 и т. д., по крайней мере, назад к записи 10001, а затем легко проверить записи 1001, 101 и 1, чтобы выяснить, где начался ваш столбец идентификаторов. Идентификаторы GUID V4, состоящие в основном из случайных данных, нарушают это добавочное поведение по своей структуре, так что если существует только один идентификатор GUID, идентификатор GUID, созданный путем увеличения или уменьшения байта идентификатора GUID, не обязательно существует, что усложняет для злоумышленника использование службы, предназначенной для этого. для поиска одной записи в качестве инструмента дампа. Существуют и другие меры безопасности, которые могут лучше ограничить доступ, но это помогает.
  • В таблицах перекрестных ссылок M: M. Это что-то вроде Дай мне, но я видел это сделано раньше. Если между двумя таблицами в вашей базе данных есть отношение «многие ко многим», промежуточное решение - это таблица перекрестных ссылок, содержащая столбцы внешнего ключа, ссылающиеся на PK каждой таблицы. PK этой таблицы должен практически всегда быть составным ключом двух внешних ключей, чтобы получить поведение встроенного индекса и обеспечить уникальность ссылок.
  • Когда вы планируете вставлять и удалять навалом, на этой таблице много. Вероятно, самым большим недостатком столбцов идентификаторов является то, что вам нужно пройти при вставке строк из другой таблицы или запроса, где вы хотите сохранить значения ключей исходной таблицы. Вы должны включить «идентификационную вставку» (как это делается в вашей СУБД), затем вручную убедиться, что ключи, которые вы вставляете, уникальны, а затем, когда вы закончите с импортом, вы должны установить счетчик идентификаторов в метаданные таблицы для максимального значения настоящего. Если эта операция часто происходит в этой таблице, рассмотрите другую схему PK.
  • Для распределенных таблиц.Столбцы идентификаторов отлично работают для баз данных с одним экземпляром, пар отработки отказа и других сценариев, когда один экземпляр базы данных является единственным авторитетом для всей схемы данных в любой момент времени. Тем не менее, есть только настолько большой, что вы можете пойти, и при этом один компьютер будет достаточно быстрым. Репликация или доставка журналов транзакций могут принести вам дополнительные копии только для чтения, но также существует ограничение для масштаба этого решения. Рано или поздно вам понадобятся два или более экземпляров сервера, которые обрабатывают вставки данных и затем синхронизируются друг с другом. Когда возникает такая ситуация, вам нужно поле GUID вместо инкрементного, поскольку большинство СУБД предварительно настроены на использование части идентификаторов GUID, которые они генерируют в качестве идентификатора конкретного экземпляра, а затем генерируют остальную часть идентификатора случайным образом. или постепенно. В любом случае,
  • Когда вам нужно обеспечить уникальность нескольких таблиц в БД.Например, в бухгалтерских системах принято управлять Главной книгой (со строкой для каждого кредита или дебета каждой учетной записи, которая когда-либо возникала, поэтому она очень быстро становится очень большой) в виде последовательности таблиц, каждая из которых представляет один календарный месяц / год. Затем могут быть созданы представления, чтобы связать их вместе для создания отчетов. Логично, что это все одна очень большая таблица, но ее нарезка упрощает обслуживание БД. Тем не менее, он представляет проблему, заключающуюся в том, как управлять вставками в несколько таблиц (что позволяет начать регистрировать транзакции в следующем месяце, при этом закрывая последний), не заканчивая дублирующимися ключами. Опять же, GUID вместо столбцов целочисленных идентификаторов являются подходящим решением, поскольку СУБД предназначена для их генерации действительно уникальным способом,

Как я уже упоминал, есть обходные пути, которые позволяют использовать столбцы идентификаторов в этих ситуациях, но в большинстве из них обновление целочисленного столбца идентификаторов до GUID проще и решает проблему более полно.


1
В некоторых случаях вам все еще может понадобиться идентификатор в таблицах M: N (с использованием столбцов ID, ID_M, ID_N) из-за присоединения свойств к экземплярам отношения M: N.
Miroxlav

V4 GUIDS не гарантированно использует криптографически стойкий PNRG, поэтому вам не стоит полагаться на него для первого примера imo (хотя, если ваш db-механизм дает более сильные обещания, у вас все будет в порядке, но это скорее не переносимо). В противном случае хорошо аргументированный пост.
Во

1
@miroxlav - Я бы сказал, что если в таблице достаточно дополнительных метаданных, касающихся отношения, что отдельный PK вне двух FK - это хорошая идея, это больше не таблица перекрестных ссылок; это его собственная сущность, которая ссылается на две другие.
KeithS

@ Voo - Вы правы, GUID V4 не гарантированно является криптографически случайным, просто уникальным (как и все GUID). Однако номера хвостов американских реактивных истребителей также не генерируются криптографически случайными начальными данными / алгоритмами. То, что вы действительно ищете, - это малонаселенный домен; GUID V4 имеет 112 байтов случайных данных, способных однозначно идентифицировать записи 5e33.
KeithS

Чтобы представить это число в перспективе, каждый мужчина, женщина и ребенок на планете (все 7 миллиардов) могут иметь 741 триллион индивидуально каталогизированных и идентифицированных точек данных в нашей БД, и мы все равно будем использовать только одно значение GUID на один миллиард доступных. Большие данные, как глобальная индустрия, даже близко не соответствуют этим масштабам знаний. Даже учитывая шаблон для генерации GUID, существуют другие источники энтропии, такие как порядок, в котором данные поступают в систему и им назначается GUID.
KeithS

7

Автоинкрементный (идентификационный) первичный ключ является хорошей идеей, за исключением того, что он не имеет смысла вне контекста базы данных и непосредственных клиентов этой базы данных. Например, если вы перенесете и сохраните некоторые данные в другой базе данных, а затем продолжите записывать разные данные в обе таблицы базы данных, идентификаторы будут расходиться, т. Е. Данные с идентификатором 42 в одной базе данных не обязательно будут соответствовать данным. с идентификатором 42 в другом.

Учитывая это, если необходимо по-прежнему иметь возможность уникально идентифицировать строки вне базы данных (а это часто бывает), то для этой цели у вас должен быть другой ключ. Подойдет тщательно подобранный бизнес-ключ, но вы часто будете в положении большого количества столбцов, необходимых для обеспечения уникальности. Другой способ - использовать столбец Id в качестве кластеризованного первичного ключа с автоинкрементом и другой столбец уникального идентификатора (guid) в качестве некластеризованного уникального ключа для уникальной идентификации строки, где бы она ни находилась в мире. Причина, по которой у вас все еще есть автоинкрементный ключ в этом случае, заключается в том, что более эффективно кластеризовать и индексировать автоинкрементный ключ, чем делать то же самое для guid.

Одним из случаев, когда вам может не понадобиться автоинкрементный ключ, будет таблица «многие ко многим», где первичный ключ представляет собой соединение столбцов Id двух других таблиц (здесь у вас все еще может быть автоинкрементный ключ, но я не вижу смысла в этом).

Еще один вопрос - тип данных автоинкрементного ключа. Использование Int32 дает вам большой, но относительно ограниченный диапазон значений. Лично я часто использую столбцы bigint для идентификатора, чтобы практически никогда не беспокоиться об исчерпании значений.


6

Поскольку другие люди приводят доводы в пользу увеличения первичного ключа, я сделаю один для GUID:

  • Это гарантированно будет уникальным
  • Вы можете совершить на одну поездку в базу данных меньше данных для вашего приложения. (Например, для таблицы типов вы можете сохранить GUID в приложении и использовать его для извлечения записи. Если вы используете идентификацию, вам нужно запросить базу данных по имени, и я видел много приложений, которые делают это, чтобы получить PK и позже запрашивает его снова, чтобы получить полную информацию).
  • Это полезно для сокрытия данных. www.domain.com/Article/2 Позвольте мне знать, что у вас есть только две статьи, тогда как www.domain.com/article/b08a91c5-67fc-449f-8a50-ffdf2403444a ничего не говорит мне.
  • Вы можете легко объединять записи из разных баз данных.
  • MSFT использует GUIDS для идентификации.

Изменить: Дублировать точку


5
-1. GUID / UUID не гарантированно уникален и не является на 100% уникальным. GUID по-прежнему имеет конечную длину, поэтому в какой-то момент вы можете рискнуть получить дубликат, хотя это маловероятно. Ваше мнение о меньшем количестве поездок в базу данных также недействительно - почему вы не можете сохранить первичный идентификатор в приложении, как вы можете с помощью ключа GUID?
Никлас Х

2
Джефф Этвуд говорит, что это намного лучше, чем я когда-либо мог. blog.codinghorror.com/primary-keys-ids-versus-guids
Логика трех значений

Что касается того, почему вы не можете хранить основной идентификатор в своем приложении? Потому что база данных создает его. Если вы запустите свои семена в пустой базе данных, вы можете предположить, что ID будет 1. Что если вы запустите тот же скрипт в базе данных с данными в ней? Идентификатор не будет 1.
Логика трех значений

Вы ничего не сказали о создании идентификаторов в приложении - вы просто написали «хранение». Но если необходимо создать идентификатор вне базы данных, тогда да, GUID может быть ответом.
Никлас Х

2
Я бы добавил, что они лучше масштабируются. Базы данных NoSQL для больших данных, такие как Cassandra, даже не поддерживают ключи автоинкремента.
Карл Билефельдт

2

Как принцип хорошего дизайна, каждая таблица должна иметь надежный способ уникальной идентификации строки. Хотя для этого и нужен первичный ключ, он не всегда требует наличия первичного ключа. Добавление первичного ключа в каждую таблицу не является плохой практикой, поскольку обеспечивает уникальную идентификацию строки, но это может быть ненужным.

Чтобы поддерживать надежные отношения между строками двух или более таблиц, вы должны делать это через внешние ключи, следовательно, необходимы первичные ключи по крайней мере в некоторых таблицах. Добавление первичного ключа к каждой таблице облегчает расширение структуры базы данных, когда приходит время добавлять новые таблицы или связи к существующим данным. Планирование заранее - это всегда хорошо.

В качестве основного принципа (возможно, жесткого правила) значение первичного ключа никогда не должно меняться в течение всей жизни его ряда. Целесообразно предположить, что любые бизнес-данные в строке могут изменяться в течение срока службы, поэтому любые бизнес-данные будут плохим кандидатом на первичный ключ. Вот почему что-то абстрактное, например, автоинкрементное целое, часто является хорошей идеей. Однако, автоинкрементные целые числа имеют свои ограничения.

Если ваши данные будут иметь жизнь только в вашей базе данных, с автоматически возрастающими целыми числами все в порядке. Но, как уже упоминалось в других ответах, если вы когда-нибудь захотите, чтобы ваши данные передавались, синхронизировались или иным образом имели жизнь вне вашей базы данных, автоматически увеличиваемые целые числа создают плохие первичные ключи. Лучшим выбором будет guid (он же «универсально уникальный идентификатор»).


2

В этом вопросе и во многих ответах упускается важный момент, согласно которому все естественные ключи для каждой таблицы находятся исключительно в логической схеме базы данных, а все суррогатные ключи для каждой таблицы находятся исключительно в физической схеме базы данных. другие ответы обсуждают исключительно относительные преимущества суррогатных ключей по сравнению с целочисленными по сравнению с GUID, не обсуждая причины и причины правильного использования суррогатных ключей.

Кстати: давайте не будем использовать плохо определенный и неточный термин первичный ключ . Это - артефакт моделей до-реляционных данных, которые сначала (неразумно) кооптировали в реляционную модель, а затем вернули обратно в физическую область различными поставщиками СУБД. Его использование служит только для того, чтобы запутать семантику.

Обратите внимание на реляционную модель, что для того, чтобы логическая схема базы данных находилась в первой нормальной форме , каждая таблица должна иметь видимый пользователем набор полей, известный как естественный ключ, который однозначно идентифицирует каждую строку таблицы. В большинстве случаев такой естественный ключ легко идентифицировать, но в некоторых случаях его необходимо создать, будь то поле прерывателя связи или иным образом. Однако такой созданный ключ всегда остается видимым для пользователя и, следовательно, всегда находится в логической схеме базы данных.

В отличие от этого, любой суррогатный ключ в таблице находится исключительно в физической схеме базы данных (и, следовательно, должен всегда быть абсолютно невидимым для пользователей базы данных как по соображениям безопасности, так и для поддержания целостности базы данных). Единственной причиной введения суррогатного ключа является решение проблем производительности при физическом обслуживании и использовании БД; будь то соединения, репликация, несколько аппаратных источников данных или другое.

Поскольку единственной причиной введения суррогатного ключа является производительность, давайте предположим, что мы хотим, чтобы он был эффективным. Если проблема производительности связана с объединениями, то мы обязательно хотим сделать наш суррогатный ключ настолько узким, насколько это возможно (не мешая аппаратному обеспечению, поэтому короткие целые числа и байты обычно отсутствуют). Производительность соединения зависит от минимальной высоты индекса, поэтому 4-байтовое целое число является естественным решением. Если ваша проблема производительности - скорость вставки, 4-байтовое целое число также может быть естественным решением (в зависимости от внутренних компонентов вашей RDBMS). Если ваша проблема производительности для таблицы связана с репликацией или несколькими источниками данных, чем какая-либо другая технология суррогатного ключа , может быть более подходящим может быть GUID или ключ из двух частей (идентификатор хоста + целое число). Я не являюсь фаворитом GUID, но они удобны.

Подводя итог, не всем таблицам потребуется суррогатный ключ (любого типа); их следует использовать только в тех случаях, когда это необходимо для работы рассматриваемой таблицы. Независимо от того, какую стандартную суррогатную технологию вы предпочитаете, тщательно продумайте фактические потребности стола, прежде чем делать выбор; Изменение суррогатного ключа выбора технологии для стола будет изнурительной работой. Запишите ключевые показатели эффективности для своей таблицы, чтобы ваши преемники поняли сделанный выбор.

Особые случаи

  1. Если ваши бизнес-требования требуют последовательной нумерации транзакций для аудиторских (или других) целей, то это поле не является суррогатным ключом; это естественный ключ (с дополнительными требованиями). Из документации автоинкрементное целое число только генерирует суррогатные ключи , поэтому найдите другой механизм для его генерации. Очевидно, что потребуется какой-то вид монитора, и если вы используете свои транзакции с нескольких сайтов, то один сайт будет особенным , поскольку он назначен узлом-хостом для монитора.

  2. Если ваша таблица никогда не будет содержать более ста строк, то высота индекса не имеет значения; каждый доступ будет при сканировании таблицы. Однако сравнение строк в длинных строках все равно будет намного дороже, чем сравнение 4-байтового целого числа, и дороже, чем сравнение GUID.

  3. Таблица кодовых значений, кодируемых полем кода char (4), должна быть такой же производительной, как таблица с 4-байтовым целым числом. Хотя у меня нет доказательств этого, я часто использую это предположение, и у меня никогда не было причин его опровергать.


-1

Мало того, что это не очень хорошая практика, на самом деле это описано как анти-паттерн в книге SQL Antipatterns Билла Карвина.

Не каждая таблица нуждается в псевдоключе - первичном ключе с произвольным значением, а не в том, что имеет семантическое значение для модели - и нет причины всегда вызывать его id.


кажется, это не дает ничего существенного по сравнению с замечаниями, сделанными и объясненными в предыдущих 9 ответах
комнат

2
и почему это может быть важно?
комнат

3
@gnat Потому что это книга о передовом опыте, в которой прямо рассматривается вопрос. Разве это не очевидно?
Педро Вернек

3
ни малейшего В поиске Google "book sql Best Practices" показано около 900 тыс. Ссылок на меня, почему это было бы особенно достойно
gnat

1
@gnat Я не собираюсь спорить весь день. Вам не нравится ответ, вот для чего нужны отрицательные голоса.
Педро Вернек

-2

Это довольно универсально - в противном случае вам нужно будет проверить, что ключ на самом деле уникален. Это можно сделать, посмотрев на все остальные ключи ... что отнимает много времени. Наличие инкрементного ключа становится дорогим, так как номер вашей записи приближается к значению переполнения ключа.

Я обычно делаю указатели более очевидными именами полей, как ref_{table}или похожая идея.

Если нет необходимости внешне указывать на запись, тогда вам не нужен идентификатор.


Значение ключевого ролловера?
AJJ

Целое число без знака имеет максимальное значение 4294967295, прежде чем при добавлении 1 оно будет свернуто до 0. Помните, что если вы добавляете запись, а затем удаляете ее, счетчик все еще увеличивается. Убедитесь, что вы используете unsigned intдля типа поля, в противном случае ограничение составляет половину этого числа.
Джонни V

Целочисленное переполнение - en.wikipedia.org/wiki/Integer_overflow
Джонни V

2
Если вы добавите / удалите много строк, автоматический счетчик приращений в конечном итоге будет переполнен.
Джонни V

1
Как люди справляются с опрокидыванием? Что если есть записи с низким идентификатором, которые никогда не удаляются, но вы начинаете почти с конца, где некоторые идентификаторы находятся в верхнем конце 4294967295? Можно ли сделать «переиндексацию»?
AJJ

-2

Я бы не сказал, что это всегда должно быть сделано. У меня здесь таблица без уникального ключа - и он не нужен. Это журнал аудита. Обновления никогда не будет, запросы будут возвращать все изменения в журнале, но это лучшее, что может быть разумно сделано, когда человеку требуется определить ошибочное изменение. (Если бы код мог это сделать, он бы вообще запретил!)


-3

Автоматический счетчик приращений для первичного ключа не очень хорошая идея. Это потому, что вам нужно вернуться в базу данных, чтобы найти следующий ключ и увеличить его на единицу, прежде чем вставлять свои данные.

При этом я обычно использую все, что база данных может предоставить для первичного ключа, а не как часть приложения.

Позволяя исходной базе данных предоставить ее вам, она может гарантировать, что ключ будет уникальным для того, что ему нужно.

Конечно, не все базы данных поддерживают это. В этом случае я обычно использую таблицу, в которой хранятся ключевые сегменты, и использую верхние и нижние диапазоны, которые управляются в приложении. Это наиболее эффективное решение, которое я нахожу, потому что вы получаете диапазон 10000 чисел и автоматически увеличиваете их в экземпляре приложения. Другой экземпляр приложения может подобрать другую группу чисел для работы. Вам нужен достаточно большой примитив первичного ключа, например, 64-битный.

UUID я не использую в качестве первичных ключей, потому что стоимость их создания и хранения намного выше, чем увеличение длинного значения на единицу. UUID все еще имеют дело с парадоксом дня рождения в том, что теоретически может возникнуть дубликат.


3
Нет. Автоинкрементные ключи означают, что увеличение ключа выполняется автоматически базой данных. Иногда (я смотрю на вас, Oracle!) Для этого вам нужна комбинация «последовательность + триггер», но вам никогда не нужно искать ранее вставленное значение ключа, добавить 1, а затем использовать его.
SQB

В некоторых средах персистентности, таких как JPA, если вы хотите вернуть значение ключа, который был создан, обратно вызывающей стороне, вам нужно загрузить запись, чтобы увидеть ключ.
Архимед Траяно,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.