Столбец NVARCHAR как ПЕРВИЧНЫЙ КЛЮЧ или как УНИКАЛЬНЫЙ столбец

11

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

У меня есть эта таблица:

CREATE TABLE [dbo].[CODES]
(
    [ID_CODE] [bigint] IDENTITY(1,1) NOT NULL,
    [CODE_LEVEL] [tinyint] NOT NULL,
    [CODE] [nvarchar](20) NOT NULL,
    [FLAG] [tinyint] NOT NULL,
    [IS_TRANSMITTED] [bit] NOT NULL DEFAULT 0,
     CONSTRAINT [PK_CODES] PRIMARY KEY CLUSTERED 
    (
        [CODE_LEVEL] ASC,
        [CODE] ASC
    )
)

Но теперь я хочу использовать [CODE]столбец в качестве первичного ключа и удалить [ID_CODE]столбец.

Есть ли проблема или штраф, если у меня есть NVARCHARстолбец как PRIMARY KEY?

[CODE]значение столбца должно быть уникальным, поэтому я подумал, что могу установить UNIQUEограничение для этого столбца.

Нужно ли использовать в [CODE]качестве первичного ключа, или лучше установить UNIQUEограничение на [CODE]столбец?

VansFannel
источник
1
Очень важно учитывать, сколько строк будет в вашей таблице?
Джеймс З
Это не ответ сам по себе , но я склонен думать, что ваш CODEстолбец должен быть уникальным, а не первичным ключом. Я подозреваю, что это несет информацию. Если эта информация каким-либо образом изменяема, то вы CODEдолжны изменить или устареть. Это сделало бы ваш Первичный Ключ изменчивым, и я не вижу, чтобы это хорошо заканчивалось. Лучше всего, чтобы ваш ПК был просто ключом, а ваш КОД мог делать то, что ему нравится. Просто мнение.
Манго
@Manngo, спасибо за ваш комментарий. Да, я так и сделал: ID_CODE - это первичный ключ, а CODE - УНИКАЛЬНЫЙ.
VansFannel

Ответы:

13

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

Необходимо учитывать, что поля в кластеризованном индексе переносятся в некластеризованные индексы. Таким образом, вы смотрите не только до 40 байтов на строку, но и (40 * some_number) байтов. И в любых таблицах FK у вас есть те же 40 байтов в строке, и чаще всего в этом поле будет некластеризованный индекс, так как он используется в JOIN, поэтому теперь он действительно удваивается в любых таблицах, для которых FK используется. вот этот. Если кто-то склонен думать, что 40 байтов * 1 миллион строк * 10 копий этого документа не о чем беспокоиться, см. Мою статью « Диск дешев»! ORLY? которая подробно описывает все (или, по крайней мере, большинство) областей, на которые повлияло это решение.

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

Следовательно, использование чего-то подобного CHAR(5), вероятно, будет хорошо для кластерного PK, но в основном, если оно также было определено с COLLATE Latin1_General_100_BIN2(или что-то подобное).

И может ли ценность [CODE]когда-либо измениться? Если да, то это еще одна причина не использовать его в качестве PK (даже если вы установили FK на ON UPDATE CASCADE). Если он не может или никогда не изменится, это нормально, но все же есть более чем достаточно причин, чтобы не использовать его в качестве кластерного ПК.

Конечно, вопрос может быть неправильно сформулирован, поскольку кажется, что у вас уже есть это поле в вашем ПК.

Независимо от этого, наилучшим вариантом, безусловно, является использование [ID_CODE]в качестве кластерного PK, использование этого поля в связанных таблицах как FK и сохранение его [CODE]как UNIQUE INDEX(что означает, что это «альтернативный ключ»).


Обновление
Немного больше информации на основе этого вопроса в комментарии к этому ответу:

Является ли [ID_CODE], как PRIMARY KEY, лучшим вариантом, если я использую столбец [CODE] для просмотра таблицы?

Все это зависит от множества факторов, некоторые из которых я уже упоминал, но повторю:

Первичный ключ - это способ идентификации отдельной строки независимо от того, на нее ссылаются какие-либо внешние ключи. То, как ваша система внутренне идентифицирует строку, связано, но не обязательно, с тем, как ваши пользователи идентифицируют себя / эту строку. Любой столбец NOT NULL с уникальными данными может работать, но есть вопросы практичности, которые следует учитывать, особенно если на PK на самом деле ссылаются какие-либо FK. Например, GUID являются уникальными, и некоторым людям действительно нравится использовать их по разным причинам, но они довольно плохи для кластерных индексов ( NEWSEQUENTIALIDлучше, но не идеально). С другой стороны, GUID просто хороши как альтернативные ключи и используются приложением для поиска строки, но JOIN все еще выполняется с использованием INT (или подобного) PK.

До сих пор вы не сказали нам, как [CODE]поле вписывается в систему со всех сторон, за исключением упоминания о том, что именно так вы просматриваете строки, но это для всех запросов или только для некоторых? Следовательно:

  • Что касается [CODE]значения:

    • Как это генерируется?
    • Это инкрементное или псевдослучайное?
    • Это одинаковая длина или разная длина?
    • Какие символы используются?
    • При использовании букв алфавита: чувствителен ли он к регистру или нечувствителен?
    • Может ли оно измениться после вставки?
  • Что касается этой таблицы:

    • Есть ли другие таблицы FK для этой таблицы? Или эти поля ( [CODE]или [ID_CODE]) используются в других таблицах, даже если явно не с внешним ключом?
    • Если [CODE] единственное поле используется для получения отдельных строк, то для чего оно предназначено [ID_CODE]? Если он не используется, зачем его вообще (что может зависеть от ответа «Может ли [CODE]поле когда-нибудь измениться?»)?
    • Сколько строк в этой таблице?
    • Если другие таблицы ссылаются на эту таблицу, сколько и сколько строк в каждой из них?
    • Каковы индексы для этой таблицы?

Это решение не может быть принято исключительно по вопросу «NVARCHAR да или нет?». Я снова скажу, что, вообще говоря, я не считаю, что это хорошая идея, но бывают случаи, когда это хорошо. Учитывая, что в этой таблице так мало полей, маловероятно, что индексов будет больше или, по крайней мере, не так много. Таким образом, вы могли бы быть хорошо в любом случае иметь [CODE]в качестве кластерного индекса. И если никакие другие таблицы не ссылаются на эту таблицу, то вы также можете сделать ее PK. Но если другие таблицы ссылаются на эту таблицу, я бы выбрал [ID_CODE]поле в качестве PK, даже если не кластеризован.

Соломон Руцкий
источник
Будет ли анонимный downvoter (который, по-видимому, также отклонил ответ @noIDonthissystem) хочет предложить какую-либо конструктивную критику или указать на какую-то ошибочную логику?
Соломон Руцкий,
Спасибо за Ваш ответ. Является ли [ID_CODE], как PRIMARY KEYлучше всего, если я использую [CODE]столбец для поиска в таблице?
VansFannel
@VansFannel, пожалуйста, смотрите мое обновление. Спасибо.
Соломон Руцки
Я присоединился к сообществу dba, чтобы поддержать этот ответ.
Ахмет Арслан
6

Вы должны разделить понятия:

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

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

Не требуется, чтобы первичный ключ был кластеризованным индексом. Вы можете иметь ID_CODEкак PK, так и (CODE_LEVEL, CODE)кластерный ключ. Или наоборот.

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

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

Что касается выбора первичного ключа, это даже не должно быть проблемой: ваша модель данных, логика вашего приложения должны определять первичный ключ.

Это , как говорится, мой 2с: NVARCHAR(20)это не широкий. Это вполне приемлемый размер кластеризованного ключа, даже для большого стола.

Ремус Русану
источник
Спасибо за Ваш ответ. Является ли [ID_CODE], как PRIMARY KEYлучше всего, если я использую [CODE]столбец (и, возможно, [CODE_LEVEL]), чтобы просмотреть таблицу?
VansFannel
@VansFannel только ты можешь ответить на это.
Ремус Русану
Но по вашему мнению ...
VansFannel
2
Мое мнение должно было бы учитывать точный DDL всей таблицы и всех индексов, внешние ключи, ссылающиеся на нее, предполагаемое количество строк, ожидаемую рабочую нагрузку запроса, ожидаемые SLA приложения и, что не менее важно, доступное резервное копирование для оборудования и лицензирования.
Ремус Русану
Спасибо. Я буду использовать [CODE]колонку в качестве первичного ключа.
VansFannel
4

Я бы никогда не позволил кому-либо сделать nvarchar(20)PK в моей базе данных. Вы тратите место на диске и кеш-память. Каждый индекс в этой таблице и все FK в ней копируют это широкое значение. Может быть, символ (20), если они могут это оправдать. Какие данные вы пытаетесь сохранить CODE? Вам действительно нужно хранить символы nvarchar? Я склонен делать PK «внутренними» значениями, невидимыми для пользователей, и стараюсь хранить значения, которые отображаются отдельно. Отображаемые значения иногда нуждаются в изменении, что становится очень проблематичным для PK + FK.

Кроме того, понимаете ли вы, что «идентификация bigint (1,1)» может увеличиваться до 9,223,372,036,854,775,807?

[ID_CODE] [bigint] IDENTITY(1,1)

Если вы не создаете эту базу данных для Google, не будет ли нормальным int identity (1,1)с лимитом более 2 миллиардов?

нет идентификатора в этой системе
источник
int - это 4 байта в SQL, что дает вам от -2,1 миллиарда до + 2,1 миллиарда.
Датагод
@ datagod, спасибо, так много цифр, что я ошибся!
нет идентификатора в этой системе
Спасибо за Ваш ответ. Является ли [ID_CODE], как PRIMARY KEYлучше всего, если я использую [CODE]столбец для поиска в таблице? Спасибо.
VansFannel
Раньше я был в этой лодке, пока кто-то не использовал последовательную природу «int» для прогнозирования данных / пользователей в моей БД и собирал почти все, что у меня было. Больше никогда. Публичным БД должно быть немного сложнее получить информацию.
DaBlue
3

Не должно быть никакого врожденного / заметного наказания, за исключением того, что вы рискуете использовать широкие ключи при использовании nvarchar / varchar, если не знаете. Особенно, если вы начнете объединять их в составные ключи.

Но в вашем примере длины (20) у вас все будет хорошо, и я бы не стал сильно беспокоиться об этом. Потому что, если CODE - это то, как вы в основном запрашиваете свои данные - кластерный индекс по этому звучит очень разумно.

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

Также подумайте, нужен ли вам ID_Code, теперь у вас есть уникальный код.

Аллан С. Хансен
источник
2
На самом деле, NVARCHAR(20)его размер составляет не более 40 байт, и, поскольку он является столбцом переменной длины , на самом деле это не лучший выбор для кластеризованного индекса. ID_CODEбудучи BIGINT IDENTITYбы быть гораздо лучше , выбор здесь!
marc_s
Я знаю, что это 40 байтов, но не было особой причины указывать это, поскольку оно далеко от 900 байтов. И если вы в основном запрашиваете данные из CODE, было бы лучше избегать сохранения избыточных индексов, потому что вам все еще нужен индекс для них, а затем вам придется искать в кластеризованном направлении назад
Аллан С. Хансен
Стоит упомянуть - что я забыл упомянуть и, как я подозреваю, именно там, где @marc_s обращается, так это то, что индекс этого типа может привести к большей фрагментации индекса, чем последовательная идентификация, но я все еще вижу его как разумный индекс в этой конкретной ситуации, основанной на на фактор запроса.
Аллан С. Хансен