Эффективная вставка в таблицу с кластерным индексом

28

У меня есть оператор SQL, который вставляет строки в таблицу с кластеризованным индексом по столбцу TRACKING_NUMBER.

НАПРИМЕР:

INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC) 
SELECT TRACKING_NUMBER, COL_B, COL_C 
FROM STAGING_TABLE

Мой вопрос - помогает ли это использовать предложение ORDER BY в операторе SELECT для столбца кластеризованного индекса, или будет ли любой полученный выигрыш сведен на нет дополнительной сортировкой, необходимой для предложения ORDER BY?

ГУВ
источник

Ответы:

18

Поскольку другие ответы уже указывают, что SQL Server может или не может явно гарантировать, что строки отсортированы в порядке кластеризованного индекса до insert.

Это зависит от того, имеет ли оператор кластерного индекса в плане DMLRequestSortнабор свойств (который, в свою очередь, зависит от предполагаемого количества вставляемых строк).

Если вы обнаружите, что SQL Server недооценивает это по какой-либо причине, вам может быть полезно добавить явное выражение ORDER BYв SELECTзапрос, чтобы минимизировать разбиение страниц и последующую фрагментацию в результате INSERTоперации.

Пример:

use tempdb;

GO

CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))

CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))

GO

DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)

INSERT INTO @T(N)
SELECT number 
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499

/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2

/*Same operation using explicit sort*/    
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;  


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;  

Показывает, что Tэто сильно фрагментировано

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536             92535                92535                67.1668272794663               250000
99.5                         200                  200                  74.2868173956017               92535
0                            1                    1                    32.0978502594514               200

Но для T2фрагментации минимально

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376                        262                  62500                99.456387447492                250000
2.1551724137931              232                  232                  43.2438349394613               62500
0                            1                    1                    37.2374598468001               232

И наоборот, иногда вам может понадобиться заставить SQL Server недооценивать количество строк, если вы знаете, что данные уже предварительно отсортированы и хотите избежать ненужной сортировки. Один примечательный пример - вставка большого количества строк в таблицу с newsequentialidключом кластеризованного индекса. В версиях SQL Server до Denali SQL Server добавляет ненужные и потенциально дорогие операции сортировки . Этого можно избежать

DECLARE @var INT =2147483647

INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar

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

Мартин Смит
источник
12

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

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

Изменить: 2011-10-28 17:00

Ответ @ Gonsalu, кажется, показывает, что операция сортировки всегда происходит, это не так. Требуются демонстрационные сценарии!

Поскольку сценарии становились достаточно большими, я переместил их в Gist . Для простоты экспериментов сценарии используют режим SQLCMD. Тесты проводятся на 2K5SP3, двухъядерный, 8 ГБ.

Тесты на вставку охватывают три сценария:

  1. Промежуточный индекс кластеризованных данных в том же порядке, что и целевой.
  2. Постановка данных кластерного индекса в обратном порядке.
  3. Промежуточные данные, сгруппированные по col2, который содержит случайный INT.

Первый запуск, вставив 25 строк.

1-й пробег, 25 рядов

Все три плана выполнения одинаковы, нигде в плане не происходит сортировки, и сканирование кластеризованного индекса имеет вид «order = false».

Второй запуск, вставка 26 строк.

2-й пробег, 26 рядов

На этот раз планы отличаются.

  • Первый показывает сканирование кластеризованного индекса как order = false. Сортировка не произошла, поскольку исходные данные отсортированы надлежащим образом.
  • Во втором сканируется кластерный индекс как упорядоченный = true, назад. Таким образом, у нас нет операции сортировки, но необходимость сортировки данных распознается оптимизатором, и она сканирует в обратном порядке.
  • Третий показывает оператор сортировки.

Таким образом, существует переломный момент, когда оптимизатор считает, что это необходимо. Как показывает @MartinSmith, похоже, что это основано на оценочных строках, которые нужно вставить. На моем тестовом стенде 25 не требует сортировки, 26 - (2K5SP3, двухъядерный, 8 ГБ)

Сценарий SQLCMD включает переменные, которые позволяют изменять размер строк в таблице (изменение плотности страниц) и количество строк в dbo.MyTable перед дополнительными вставками. Из моих испытаний ни один не влияет на переломный момент.

Если кто-то из читателей так склонен, пожалуйста, запустите сценарии и добавьте свой переломный момент в качестве комментария. Интересно узнать, меняется ли он в зависимости от тестовых установок и / или версий.

Изменить: 2011-10-28 20:15

Повторные испытания на той же установке, но с 2K8R2. На этот раз переломный момент составляет 251 ряд. Опять же, изменение плотности страниц и количества существующих строк не имеет никакого эффекта.

Марк Стори-Смит
источник
8

ORDER BYПункт в SELECTзаявлении является излишним.

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

Давайте создадим контрольный пример.

CREATE TABLE #Test (
    id INTEGER NOT NULL
);

CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);

CREATE TABLE #Sequence (
    number INTEGER NOT NULL
);

INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;

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

SET STATISTICS PROFILE ON;
GO

Теперь давайте INSERT2K строк в таблицу без ORDER BYпредложения.

INSERT INTO #Test
SELECT number
  FROM #Sequence

Фактический план выполнения для этого запроса следующий.

INSERT INTO #Test  SELECT number    FROM #Sequence
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Как вы можете видеть, оператор Sort существует до того, как произойдет фактическая вставка.

Теперь давайте очистим таблицу и INSERT2k строк в таблице с ORDER BYпредложением.

TRUNCATE TABLE #Test;
GO

INSERT INTO #Test
SELECT number
  FROM #Sequence
 ORDER BY number

Фактический план выполнения для этого запроса следующий.

INSERT INTO #Test  SELECT number    FROM #Sequence   ORDER BY number
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Обратите внимание, что это тот же план выполнения, который использовался для INSERTоператора без ORDER BYпредложения.

Теперь Sortоперация не всегда обязательна, как показал Марк Смит в другом ответе (если число вставляемых строк невелико), но в этом случае ORDER BYпредложение все еще избыточно, поскольку даже при явном ORDER BYуказании Sortоперация не генерируется. обработчиком запросов.

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

Обновлено 2011-11-02: Как показал Марк Смит , INSERTдля таблицы s с кластеризованным индексом не всегда требуется сортировка - ORDER BYхотя в этом случае это предложение также является избыточным.

gonsalu
источник