Должен ли я создать таблицу с первичным ключом varchar или int? - PullRequest
18 голосов
/ 19 августа 2009

Я знаю, что это субъективно, но я хотел бы узнать мнения людей и, надеюсь, некоторые лучшие практики, которые я могу применить при проектировании структур таблиц SQL Server.

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

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

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

Ответы [ 13 ]

39 голосов
/ 19 августа 2009

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

Первичные ключи являются логическими бизнес элементами. Первичный ключ используется вашим приложением для идентификации сущности, и обсуждение первичных ключей в значительной степени идет на использование естественных ключей или суррогатного ключа . Ссылки содержат гораздо более подробную информацию, но основная идея заключается в том, что естественные ключи являются производными от существующего свойства объекта, такого как ssn или phone number, в то время как суррогатные ключи не имеют никакого значения для бизнес-объекта, такого как id или rowid и они обычно имеют тип IDENTITY или какой-то тип uuid. Мое личное мнение состоит в том, что суррогатные ключи превосходят естественные ключи, и выбор должен всегда быть идентичными значениями только для локальных приложений, руководствами для любого вида распределенных данных. Первичный ключ никогда не изменяется в течение жизни объекта.

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

  1. Распространенный шаблон доступа к данным .
  2. Особенности хранения .

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

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

  • Обработка стиля очереди FIFO. В этом случае таблица имеет две горячие точки: хвост, где происходит вставка (постановка в очередь), и голова, в которой происходит удаление (снятие очереди). Кластерный ключ должен принять это во внимание и организовать таблицу так, чтобы физически отделить расположение хвоста и головки на диске, чтобы обеспечить согласованность между постановкой в ​​очередь и снятием очереди, например. с помощью ключа порядка постановки в очередь. В pure очередях этот кластеризованный ключ является единственным ключом, поскольку в таблице нет первичного ключа (он содержит сообщений , а не сущностей ). Но в большинстве случаев очередь не является чистой, она также действует как хранилище для сущностей, и линия между queue и table стирается. В этом случае также существует первичный ключ, который не может быть кластеризованным ключом: объекты могут быть повторно поставлены в очередь, таким образом изменяя значение кластеризованного ключа порядка постановки в очередь, но они не могут изменить значение первичного ключа. Неспособность увидеть разделение является основной причиной того, что очереди, поддерживаемые таблицами пользователей, так общеизвестно трудно получить правильно и изобиловать взаимоблокировками: потому что постановка очереди и снятие очереди чередуются между таблицами, а не локализуются в хвосте и в начале очереди.

  • Коррелированная обработка. Когда приложение хорошо спроектировано, оно распределяет обработку связанных элементов между своими рабочими потоками. Например, процессор спроектирован так, чтобы иметь 8 рабочих потоков (скажем, для соответствия 8 процессорам на сервере), поэтому процессоры разделяют данные между собой, например. работник 1 выбирает только учетные записи с именами от A до E, работник 2 от F до J и т. д. В таких случаях таблица должна быть фактически кластеризована по имени учетной записи (или по составному ключу, который имеет крайнюю левую позицию - первую букву имени учетной записи), чтобы работники локализовали свои запросы и обновления в таблице. Такой стол будет иметь 8 различных горячих точек вокруг области, в которой каждый работник концентрируется в данный момент, но важно то, что они не перекрываются (не блокируются). Этот тип дизайна преобладает в проектах OLTP с высокой пропускной способностью и в тестах загрузки TPCC, где этот тип разделения также отражается в расположении памяти страниц, загруженных в пул буферов (локальность NUMA), но я отвлекся.

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

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

Так что мы делаем из всего этого? Всегда начинайте с рассмотрения кластерного ключа, который также является первичным ключом формы entity_id IDENTITY(1,1) NOT NULL. Разделите их и упорядочите таблицу соответствующим образом (например, разделите по дате), когда это уместно.

17 голосов
/ 19 августа 2009

Я бы определенно рекомендовал использовать поле INT NOT NULL IDENTITY(1,1) в каждой таблице в качестве первичный ключ.

С помощью поля IDENTITY вы можете позволить базе данных обрабатывать все детали, чтобы убедиться, что она действительно уникальна, и все, а тип данных INT имеет размер всего 4 байта и является фиксированным, поэтому его проще и удобнее использовать для основного (и кластеризация) ключ в вашей таблице.

И вы правы - INT - это INT, это INT - он ничего не изменит в своем размере, поэтому вам никогда не придется пересоздавать и / или обновлять ваши отношения с внешним ключом.

Использование VARCHAR (10) или (20) просто занимает слишком много места - 10 или 20 байтов вместо 4, и то, что многие люди не знают - значение ключа кластеризации будет повторяться для каждого индекса запись в каждом некластеризованном индексе таблицы, поэтому потенциально вы тратите много места (не только на диске - это дешево - но и в основной памяти SQL Server). Кроме того, поскольку он переменный (может быть 4, может быть 20 символов), SQL-серверу сложнее правильно поддерживать хорошую структуру индекса.

Марк

4 голосов
/ 19 августа 2009

Я бы согласился, что в общем случае тип поля INT (или идентичности) является лучшим выбором в большинстве "нормальных" проектов баз данных:

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

Тем не менее, вам также нужно знать свои данные. Если вы собираетесь пройти через 32-битный int со знаком, вам нужно подумать о unsigned. Если вы собираетесь пройти через это, возможно, вам нужны 64-битные целые числа. Или вам нужен UUID / хеш, чтобы упростить синхронизацию между экземплярами / осколками базы данных.

К сожалению, это зависит и от YMMV, но я бы определенно использовал бы int / identity, если у вас нет веских причин не to.

3 голосов
/ 19 августа 2009

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

2 голосов
/ 19 августа 2009

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

INT

  • Используйте, если на то нет веских причин.

1012 * GUID *

  • Уникальность - Одним из примеров является случай, когда существует односторонняя связь между удаленными частями программы и стороной, которая должна инициировать, не является стороной с базой данных. В этом случае установка Guid на удаленной стороне безопасна, если не выбран INT.
  • Опять уникальность - Более надуманный сценарий - это система, в которой несколько клиентов сосуществуют в отдельных базах данных и существует миграция между клиентами, такими как аналогичные пользователи, с использованием набора программ. Если этот пользователь подписывается на другую программу, его запись пользователя может использоваться там без конфликтов. Другой сценарий, если клиенты приобретают объекты друг от друга. Если оба находятся в одной системе, они часто ожидают, что миграция будет проще. По сути, любая частая миграция между клиентами.
  • Трудно использовать - Даже опытный программист не может вспомнить руководство. При поиске и устранении неисправностей часто бывает неприятно копировать и вставлять идентификаторы для запросов, особенно если поддержка осуществляется с помощью инструмента удаленного доступа. Гораздо проще постоянно ссылаться на SELECT * ОТ Xxx WHERE ID = 7, чем на SELECT * ОТ Xxx WHERE ID = 'DF63F4BD-7DC1-4DEB-959B-4D19012A6306'

  • Индексирование - использование кластерного индекса для поля guid требует постоянной перестановки страниц данных и не так эффективно индексировать, как INT или даже короткие строки. Это может убить производительность - не делайте этого.

CHAR

  • Удобочитаемость - Хотя общепринятым является то, что в базе данных никого не должно быть, реальность систем заключается в том, что у людей будет доступ - надеюсь, персонал из вашей организации. Когда эти люди не разбираются в синтаксисе соединения, нормализованная таблица с целочисленными значениями или направляющими не ясна без многих других запросов. Та же нормализованная таблица с НЕКОТОРЫМИ строковыми ключами может быть гораздо более удобной для устранения неполадок. Я склонен использовать это для типа таблицы, куда я поставляю записи во время установки, чтобы они не менялись. Такие вещи, как StatusID в основной таблице, гораздо удобнее использовать для поддержки, когда ключ «Закрыт» или «Ожидает», чем цифра. Использование традиционных ключей в этих областях может превратить легко решаемую проблему в проблему, требующую помощи разработчика. Подобные узкие места являются серьезными, даже если они вызваны тем, что сомнительный персонал получает доступ к базе данных.
  • Ограничение - Даже если вы используете строки, сохраняйте их фиксированной длины, что ускоряет индексацию и добавляет ограничение или внешний ключ для исключения мусора. Иногда использование этой строки может позволить вам удалить справочную таблицу и сохранить выделение в коде как простой Enum - все равно важно ограничить данные, поступающие в это поле.
1 голос
/ 20 августа 2009

Если бы здесь был Джо Селко, у него были бы какие-то резкие слова ...; -)

Я хочу отметить, что INT как жесткое и быстрое правило не всегда подходят. Допустим, у вас есть таблица транспортных средств со всеми типами грузовых автомобилей и т. Д. Теперь скажите, что у вас есть таблица VehicleType. Если вы хотите получить все грузовики, вы можете сделать это (с идентификационным номером INT):

SELECT V.Make, V.Model
FROM Vehicle as V
INNER JOIN VehicleType as VT
ON V.VehicleTypeID = VT.VehicleTypeID
WHERE VT.VehicleTypeName = 'Truck'

Теперь с Varchar PK на VehicleType:

SELECT Make, Model
FROM Vehicle 
WHERE VehicleTypeName = 'Truck'

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

Просто мысль. : -)

1 голос
/ 19 августа 2009

Используйте INT. Ваши очки действительны; Я бы расставил приоритеты как:

  1. Простота использования возможностей автоинкремента SQL - зачем изобретать велосипед?
  2. Управляемость - вам не нужно менять ключевое поле.
  3. Performance
  4. Дисковое пространство

1 & 2 требуют времени / энергии / усилий разработчика. 3 и 4 вы можете бросить оборудование на.

1 голос
/ 19 августа 2009

Для наилучшая производительность , 99,999% времени первичный ключ должен быть одним целочисленным полем.

Если вам не требуется, чтобы первичный ключ был уникальным для нескольких таблиц в базе данных или для нескольких баз данных. Я предполагаю, что вы спрашиваете о MS SQL-Server, потому что именно так был помечен ваш вопрос. В этом случае рассмотрите возможность использования поля GUID. Хотя лучше, чем varchar, производительность поля GUID не так хороша, как целое число.

0 голосов
/ 20 марта 2011

Учитывая, что это довольно старый вопрос, я все же хочу привести аргумент в пользу использования varchar с суррогатными ключами для будущих читателей:

  1. Среда с несколькими реплицированными машинами
  2. Сценарии, в которых требуется, чтобы идентификатор вставляемой строки был известен до фактической вставки (т. Е. Клиент назначает этот идентификатор, а не базу данных)
0 голосов
/ 19 августа 2009

Мы должны помнить, что первичный ключ таблицы не должен иметь «бизнес-логики», а должен быть только идентификатором записи, которой он принадлежит. Следуя этому простому правилу, int и особенно идентификация int - очень хорошее решение. Задавая вопрос о varchar, я предполагаю, что вы имеете в виду использование, например, «Полного имени» в качестве ключа к таблице «people». Но что, если мы захотим изменить название с «George Something» на «George A. Something»? И какого размера будет поле? Если мы изменим размер, мы должны изменить размер и для всех сторонних таблиц. Поэтому мы должны избегать логики на ключах. Иногда мы можем использовать социальный идентификатор (целочисленное значение) в качестве ключа, но я также избегаю этого. Теперь, если у проекта есть перспективы для масштабирования, вам следует подумать и об использовании Guids (тип уникального идентификатора SQL).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...