При выборе первичного ключа обычно вы также выбираете кластерный ключ. Их два часто путают, но вы должны понимать разницу.
Первичные ключи являются логическими бизнес элементами. Первичный ключ используется вашим приложением для идентификации сущности, и обсуждение первичных ключей в значительной степени идет на использование естественных ключей или суррогатного ключа . Ссылки содержат гораздо более подробную информацию, но основная идея заключается в том, что естественные ключи являются производными от существующего свойства объекта, такого как ssn
или phone number
, в то время как суррогатные ключи не имеют никакого значения для бизнес-объекта, такого как id
или rowid
и они обычно имеют тип IDENTITY
или какой-то тип uuid. Мое личное мнение состоит в том, что суррогатные ключи превосходят естественные ключи, и выбор должен всегда быть идентичными значениями только для локальных приложений, руководствами для любого вида распределенных данных. Первичный ключ никогда не изменяется в течение жизни объекта.
Кластерные ключи являются ключом, который определяет физическое хранение строк в таблице. В большинстве случаев они перекрываются с первичным ключом (идентификатором логического объекта), но это на самом деле не применяется и не требуется. Когда они различаются, это означает, что в таблице есть некластеризованный уникальный индекс, который реализует первичный ключ. Значения кластеризованного ключа могут фактически меняться в течение срока жизни строки, в результате чего строка физически перемещается в таблицу в новое место. Если вам нужно отделить первичный ключ от кластеризованного ключа (а иногда и делаете), выбрать хороший кластеризованный ключ значительно сложнее, чем выбрать первичный ключ. Есть два основных фактора, которые определяют ваш кластеризованный ключ:
- Распространенный шаблон доступа к данным .
- Особенности хранения .
Шаблон доступа к данным . Под этим я понимаю способ запроса и обновления таблицы. Помните, что кластеризованные ключи определяют фактический порядок строк в таблице. Для определенных шаблонов доступа некоторые макеты имеют огромное значение для скорости запросов или обновления параллелизма:
текущие и архивные данные. Во многих приложениях данные, относящиеся к текущему месяцу, часто доступны, в то время как те, что были в прошлом, редко доступны. В таких случаях в конструкции таблицы используется разбиение таблицы по дате транзакции, часто с использованием алгоритма скользящее окно . Раздел текущего месяца хранится в файловой группе, расположенной на быстром быстром диске, заархивированные старые данные перемещаются в файловые группы, размещенные в более дешевом, но более медленном хранилище. Очевидно, что в этом случае кластерный ключ (дата) не является первичным ключом (идентификатор транзакции). Разделение этих двух факторов обусловлено требованиями к масштабированию, так как оптимизатор запросов сможет обнаружить, что запросы интересуют только текущий раздел и даже не смотрят на исторические.
Обработка стиля очереди 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
. Разделите их и упорядочите таблицу соответствующим образом (например, разделите по дате), когда это уместно.