2016-12-23 7 views
19

Является ли порядок сортировки кластерного индекса SQL Server 2008+ влияющим на производительность вставки?Порядок сортировки кластерного индекса SQL Server 2008+

Тип данных в конкретном случае: integer, а вставленные значения возрастают (Identity). Следовательно, порядок сортировки индекса будет противоположным порядку сортировки вводимых значений.

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

Обратите внимание, что вопрос о производительности INSERT, а не SELECT.

Update
Чтобы быть более четко на вопрос: Что происходит, когда значения, которые будут вставлены (integer) в обратном порядке (ASC) к упорядочению кластерного индекса (DESC)?

+2

Почему вы говорите, что «порядок сортировки индекса будет противоположным порядку сортировки значений, которые нужно вставить»? Следует ли считать, что кластерный индекс был объявлен как «DESC»? Если нет, по умолчанию используется 'ASC', который является порядком _same_ как значения, которые нужно вставить. Однако я мог бы что-то неправильно понять. –

+1

В этом суть вопроса: что происходит, когда значение идентификатора подсчитывается, но порядок сортировки был установлен вручную для desc. Наверное, я не сделал это полностью ясным в своем посте, извините, я не являюсь носителем английского языка. – HCL

+0

В вашей среде находятся люди, выполняющие инструкции SELECT, которые ORDER по кластерному индексу в порядке убывания? – pacreely

ответ

7

Есть разница. Вставка из кластерного порядка вызывает массированную фрагментацию.

При запуске следующего кода кластеризованный индекс DESC генерирует дополнительные операции UPDATE на уровне NONLEAF.

CREATE TABLE dbo.TEST_ASC(ID INT IDENTITY(1,1) 
          ,RandNo FLOAT 
          ); 
GO 
CREATE CLUSTERED INDEX cidx ON dbo.TEST_ASC(ID ASC); 
GO 

CREATE TABLE dbo.TEST_DESC(ID INT IDENTITY(1,1) 
          ,RandNo FLOAT 
          ); 
GO 
CREATE CLUSTERED INDEX cidx ON dbo.TEST_DESC(ID DESC); 
GO 

INSERT INTO dbo.TEST_ASC VALUES(RAND()); 
GO 100000 

INSERT INTO dbo.TEST_DESC VALUES(RAND()); 
GO 100000 

Две вставки заявления производят точно такой же план выполнения, но при взгляде на оперативную статистику различия проявляются против [nonleaf_update_count].

SELECT 
OBJECT_NAME(object_id) 
,* 
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_ASC'),null,null) 
UNION 
SELECT 
OBJECT_NAME(object_id) 
,* 
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_DESC'),null,null) 

Существует дополнительный -под в hood- операции происходит, когда SQL работает с индексом DESC, который работает против IDENTITY. Это связано с тем, что таблица DESC становится фрагментированной (строки вставлены в начале страницы), и для поддержки структуры B-дерева возникают дополнительные обновления.

Наиболее примечательным в этом примере является то, что кластерный индекс DESC становится более 99% фрагментированным. This is recreating the same bad behaviour as using a random GUID for a clustered index. Приведенный ниже код демонстрирует фрагментацию.

SELECT 
OBJECT_NAME(object_id) 
,* 
FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID('dbo.TEST_ASC'), NULL, NULL ,NULL) 
UNION 
SELECT 
OBJECT_NAME(object_id) 
,* 
FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID('dbo.TEST_DESC'), NULL, NULL ,NULL) 

UPDATE:

На некоторых тестовых средах я также видим, что таблица DESC подлежит более Уэйтс с увеличением [page_io_latch_wait_count] и [page_io_latch_wait_in_ms]

UPDATE :

Некоторое обсуждение связано с тем, что является точкой нисходящего индекса, когда SQL может выполнять обратные проверки. Пожалуйста, прочитайте эту статью о limitations of Backward Scans.

+0

Или может быть, что SQL Server оптимизирует для кластеризованных вложений индексов в порядке, которые всегда попадают на последнюю страницу индекса, а вставка desc не получает этих оптимизаций. – geofftnz

+0

Это нормально, чтобы переупорядочить не листовой уровень, но это должна быть операция, выполняемая несколько раз (в вашем примере 260 раз для вставок 100 тыс.) В очень маленькой коллекции и обычно в памяти, как при вставке последовательной текущей листовой страницы остается в памяти в течение длительного времени ... поэтому он не оказывает реального влияния на производительность. –

+0

Dumitrescu: Это нормально, но нельзя игнорировать разницу между ASC (0) и DESC (260). Если бы это было увеличено до многотервальной производственной системы, тогда разница стала заметной. – pacreely

0

Пока данные поступают по кластерному индексу (независимо от того, является ли это восходящим или нисходящим), то не должно оказывать никакого влияния на производительность вставки. Причиной этого является то, что SQL не заботится о физическом порядке строк на странице для кластерного индекса. Порядок строк хранится в так называемом «Record Offset Array», который является единственным, который должен быть переписан для новой строки (которая в любом случае была бы сделана независимо от порядка). Фактические строки данных будут просто записываться один за другим.

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

Вы можете найти хорошее объяснение по физической структуре страницы/строки здесь https://www.simple-talk.com/sql/database-administration/sql-server-storage-internals-101/.

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

0

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

В этом примере логические чтения почти удваиваются.

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

--Drop Table Destination; 
Create Table Destination (MyId INT IDENTITY(1,1)) 

Create Clustered Index ClIndex On Destination(MyId ASC) 

set identity_insert destination on 
Insert into Destination (MyId) 
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
FROM sys.all_objects 
ORDER BY n 


set identity_insert destination on 
Insert into Destination (MyId) 
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
FROM sys.all_objects 
ORDER BY n desc; 

Подробнее о логических операций чтения, если вы заинтересованы: https://www.brentozar.com/archive/2012/06/tsql-measure-performance-improvements/

7

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

Я построил тест-кровать, чтобы увидеть, что происходит:

USE tempdb; 

CREATE TABLE dbo.TestSort 
(
    Sorted INT NOT NULL 
     CONSTRAINT PK_TestSort 
     PRIMARY KEY CLUSTERED 
    , SomeData VARCHAR(2048) NOT NULL 
); 

INSERT INTO dbo.TestSort (Sorted, SomeData) 
VALUES (1797604285, CRYPT_GEN_RANDOM(1024)) 
    , (1530768597, CRYPT_GEN_RANDOM(1024)) 
    , (1274169954, CRYPT_GEN_RANDOM(1024)) 
    , (-1972758125, CRYPT_GEN_RANDOM(1024)) 
    , (1768931454, CRYPT_GEN_RANDOM(1024)) 
    , (-1180422587, CRYPT_GEN_RANDOM(1024)) 
    , (-1373873804, CRYPT_GEN_RANDOM(1024)) 
    , (293442810, CRYPT_GEN_RANDOM(1024)) 
    , (-2126229859, CRYPT_GEN_RANDOM(1024)) 
    , (715871545, CRYPT_GEN_RANDOM(1024)) 
    , (-1163940131, CRYPT_GEN_RANDOM(1024)) 
    , (566332020, CRYPT_GEN_RANDOM(1024)) 
    , (1880249597, CRYPT_GEN_RANDOM(1024)) 
    , (-1213257849, CRYPT_GEN_RANDOM(1024)) 
    , (-155893134, CRYPT_GEN_RANDOM(1024)) 
    , (976883931, CRYPT_GEN_RANDOM(1024)) 
    , (-1424958821, CRYPT_GEN_RANDOM(1024)) 
    , (-279093766, CRYPT_GEN_RANDOM(1024)) 
    , (-903956376, CRYPT_GEN_RANDOM(1024)) 
    , (181119720, CRYPT_GEN_RANDOM(1024)) 
    , (-422397654, CRYPT_GEN_RANDOM(1024)) 
    , (-560438983, CRYPT_GEN_RANDOM(1024)) 
    , (968519165, CRYPT_GEN_RANDOM(1024)) 
    , (1820871210, CRYPT_GEN_RANDOM(1024)) 
    , (-1348787729, CRYPT_GEN_RANDOM(1024)) 
    , (-1869809700, CRYPT_GEN_RANDOM(1024)) 
    , (423340320, CRYPT_GEN_RANDOM(1024)) 
    , (125852107, CRYPT_GEN_RANDOM(1024)) 
    , (-1690550622, CRYPT_GEN_RANDOM(1024)) 
    , (570776311, CRYPT_GEN_RANDOM(1024)) 
    , (2120766755, CRYPT_GEN_RANDOM(1024)) 
    , (1123596784, CRYPT_GEN_RANDOM(1024)) 
    , (496886282, CRYPT_GEN_RANDOM(1024)) 
    , (-571192016, CRYPT_GEN_RANDOM(1024)) 
    , (1036877128, CRYPT_GEN_RANDOM(1024)) 
    , (1518056151, CRYPT_GEN_RANDOM(1024)) 
    , (1617326587, CRYPT_GEN_RANDOM(1024)) 
    , (410892484, CRYPT_GEN_RANDOM(1024)) 
    , (1826927956, CRYPT_GEN_RANDOM(1024)) 
    , (-1898916773, CRYPT_GEN_RANDOM(1024)) 
    , (245592851, CRYPT_GEN_RANDOM(1024)) 
    , (1826773413, CRYPT_GEN_RANDOM(1024)) 
    , (1451000899, CRYPT_GEN_RANDOM(1024)) 
    , (1234288293, CRYPT_GEN_RANDOM(1024)) 
    , (1433618321, CRYPT_GEN_RANDOM(1024)) 
    , (-1584291587, CRYPT_GEN_RANDOM(1024)) 
    , (-554159323, CRYPT_GEN_RANDOM(1024)) 
    , (-1478814392, CRYPT_GEN_RANDOM(1024)) 
    , (1326124163, CRYPT_GEN_RANDOM(1024)) 
    , (701812459, CRYPT_GEN_RANDOM(1024)); 

Первый столбец является первичным ключом, и, как вы можете увидеть значения перечислены в случайном (иш) порядке. Перечисляя значения в произвольном порядке должны сделать SQL Server либо:

  1. Сортировка данных, предварительно вставить
  2. Не сортировать данные, в результате чего фрагментированной таблицы.

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

После того, как вы запустите выше вставку, вы можете проверить фрагментацию, как это:

SELECT * 
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('TestSort'), 1, 0, 'SAMPLED') ips; 

Запуск этого на мой SQL Server 2012 экземпляра издания Developer показывает среднюю фрагментацию 90%, что указывает на SQL Server не сортировать во время вставить.

нравственность этого частности история, вероятно, будет «когда есть сомнения, соберите, если это будет полезно». Сказав это, добавление и предложение ORDER BY в инструкцию insert не гарантируют, что в этом порядке будут вставлены вставки. Подумайте, что произойдет, если вставку идет параллельно, в качестве примера.

В непроизводственных системах вы можете использовать флаг трассировки 2332 в качестве опции в инструкции insert для принудительного SQL Server для сортировки ввода до его вставки. @PaulWhite имеет интересную статью, Optimizing T-SQL queries that change data, охватывающую это и другие детали. Имейте в виду, что флаг трассировки не поддерживается и не должен использоваться в производственных системах, поскольку это может аннулировать вашу гарантию. В системе непроизводственной, для собственного образования, вы можете попробовать добавить это к концу INSERT заявления:

OPTION (QUERYTRACEON 2332); 

После того, как вы есть, что добавляется к вставке, посмотрите на план, вы» увидите явный вид:

enter image description here

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

Paul White made me aware что SQL Server делает автоматически вводит оператор сортировки в план, когда он считает, что один будет полезен. Для примера запроса выше, если я запустил вставку с 250 элементами в предложении values, сортировка не будет выполнена автоматически. Однако в 251 элементе SQL Server автоматически сортирует значения до вставки. Почему обрезание 250/251 строк остается для меня загадкой, за исключением того, что она жестко закодирована. Если я уменьшу размер данных, вставленных в столбец SomeData, на один байт, обрезание равно еще 250/251 строк, хотя размер таблицы в обоих случаях составляет всего одну страницу. Интересно, что, глядя на вставку с SET STATISTICS IO, TIME ON;, вставки с байтом SomeData принимают в два раза больше времени при сортировке.

Без сортировки (т.е. 250 строк вставлено):

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
SQL Server parse and compile time: 
    CPU time = 16 ms, elapsed time = 16 ms. 
SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
Table 'TestSort'. Scan count 0, logical reads 501, physical reads 0, 
    read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
    read-ahead reads 0. 

(250 row(s) affected) 

(1 row(s) affected) 

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 11 ms.

С рода (т.е. 251 строк вставлено):

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
SQL Server parse and compile time: 
    CPU time = 15 ms, elapsed time = 17 ms. 
SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
Table 'TestSort'. Scan count 0, logical reads 503, physical reads 0, 
    read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
    read-ahead reads 0. 
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, 
    read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
    read-ahead reads 0. 

(251 row(s) affected) 

(1 row(s) affected) 

SQL Server Execution Times: 
    CPU time = 16 ms, elapsed time = 21 ms. 

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


В качестве побочного сведению, в случае, если вы заинтересованы, я сгенерировал пункт VALUES (...) с помощью этого T-SQL:

;WITH s AS (
    SELECT v.Item 
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(Item) 
) 
, v AS (
    SELECT Num = CONVERT(int, CRYPT_GEN_RANDOM(10), 0) 
) 
, o AS (
    SELECT v.Num 
     , rn = ROW_NUMBER() OVER (PARTITION BY v.Num ORDER BY NEWID()) 
    FROM s s1 
     CROSS JOIN s s2 
     CROSS JOIN s s3 
     CROSS JOIN v 
) 
SELECT TOP(50) ', (' 
     + REPLACE(CONVERT(varchar(11), o.Num), '*', '0') 
     + ', CRYPT_GEN_RANDOM(1024))' 
FROM o 
WHERE rn = 1 
ORDER BY NEWID(); 

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

+1

Спасибо за ваш очень интересный и глубокий ответ. Тем не менее, ваша аргументация основана на случайных данных, но вопрос касается конкретно упорядоченных данных (IDENTITY). Кластеризованный индекс устанавливается в DESC, и значения будут подсчитываться в порядке возрастания. Кроме того, никогда не будет навалов вставки, всегда одна запись за раз, которая вставлена. – HCL