Имеет ли значение порядок столбцов в индексе PK?

33

У меня есть несколько очень больших столов с одинаковой базовой структурой. У каждого есть RowNumber (bigint)и DataDate (date)столбец. Данные загружаются с использованием SQLBulkImport каждую ночь, и никакие «новые» данные никогда не загружаются - это историческая запись (стандарт SQL, а не Enterprise, поэтому нет разделения).

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

Я заметил, что из-за того, как я определил PK в конструкторе таблиц SSMS, RowNumberон указан первым и DataDateвторым.

Я также заметил, что моя фрагментация всегда очень высока ~ 99%.

Теперь, поскольку каждый из них DataDateпоявляется только один раз, я ожидал бы, что индексатор будет просто добавлять на страницы каждый день, но мне интересно, действительно ли он индексирует на основе RowNumberсначала, и, следовательно, вынужден перекладывать все остальное?


Rownumberэто не столбец идентификаторов, это int, сгенерированный внешней системой (к сожалению). Сбрасывается в начале каждого DataDate.

Пример данных

RowNumber | DataDate | a | b | c..... 
   1      |2013-08-01| x | y | z 
   2      |2013-08-01| x | y | z 
...
   1      |2013-08-02| x | y | z 
   2      |2013-08-02| x | y | z 
...

Данные загружаются по RowNumberпорядку, по одному DataDateна каждую загрузку.

Процесс импорта bcp - я попытался загрузить временную таблицу и затем выбрать по порядку оттуда ( ORDER BY RowNumber, DataDate), но все равно получается высокая фрагментация.

BlueChippy
источник

Ответы:

50

Имеет ли значение порядок столбцов в индексе PK?

Да, это так.

По умолчанию ограничение первичного ключа применяется в SQL Server с помощью уникального кластеризованного индекса. Кластерный индекс определяет логический порядок строк в таблице. Может быть добавлено несколько дополнительных страниц индекса для представления верхних уровней индекса b-дерева, но самый низкий (конечный) уровень кластеризованного индекса - это просто логический порядок самих данных.

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

При кластеризованном первичном ключе (RowNumber, DataDate)строки сначала логически сортируются, RowNumberа затем DataDate- так, чтобы все строки RowNumber = 1были логически сгруппированы, а затем строки RowNumber = 2и так далее.

Когда вы добавляете новые данные (с RowNumbers1 по n), новые строки логически принадлежат существующим страницам, поэтому SQL Server, скорее всего, придется много работать, разбивая страницы, чтобы освободить место. Вся эта деятельность генерирует много дополнительной работы (включая регистрацию изменений) без какой-либо выгоды.

Разделение страниц также начинается примерно на 50% пустым, поэтому чрезмерное разделение также может привести к низкой плотности страниц (меньше строк, чем оптимально для каждой страницы). Это не только плохие новости для чтения с диска (более низкая плотность = больше страниц для чтения), но страницы с более низкой плотностью также занимают больше места в памяти при кэшировании.

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

Если ничего другого, ваши запросы будут искать чаще, DataDateчем RowNumber. Кластерный индекс включен (DataDate, RowNumber) поддерживает поиск индекса DataDate(и затем RowNumber). Существующая договоренность поддерживает только поиск RowNumber(и только тогда, возможно, включение DataDate). Возможно, вы сможете удалить существующий некластеризованный индекс DataDateпосле изменения первичного ключа. Кластерный индекс будет шире, чем некластеризованный индекс, который он заменяет, поэтому вам следует проверить, чтобы производительность оставалась приемлемой.

При импорте новых данных с bcp, вы можете получить более высокую производительность, если данные в файле импорта сортируются по ключам кластеризованного индекса (в идеале (DataDate, RowNumber)), и вы указываете bcpопцию:

-h "ORDER(DataDate,RowNumber), TABLOCK"

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

Пол Уайт говорит, что GoFundMonica
источник
4
Отличный ответ - теперь я знаю, что я должен делать и почему. Я так и думал, но не ЗНАЛ так! Спасибо.
BlueChippy
Потребовалось LOOOOONG, чтобы вставить БД в мой локальный SQL Server для тестирования: до изменения нагрузки индекса потребовалось 45 минут ... после этого потребовалось всего 5 !!!
BlueChippy
13

Да, порядок критичен. Я очень сомневаюсь, что вы когда-нибудь запросили по RowNumber (например WHERE RowNumber=1). В подавляющем большинстве случаев временные ряды запрашиваются функцией date ( WHERE DataDate BEWEEN @start AND @end), и такие запросы требуют кластерной организации DataDate.

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

Ремус Русану
источник
У меня также есть некластеризованный индекс (ы) для DataDate, который, как вы говорите, часто используется WHEREв запросах.
BlueChippy
1
Если ПОРЯДОК столбцов имеет решающее значение, увеличится ли влияние неправильного порядка на мой ввод-вывод? Я думаю, что это упорядочение по RowNumber и, следовательно, каждый раз приходится много работать над индексами, тогда как он должен основываться на DataDate?
BlueChippy