INSERT eficiente em uma tabela com índice clusterizado

28

Eu tenho uma instrução SQL que insere linhas em uma tabela com um índice clusterizado na coluna TRACKING_NUMBER.

POR EXEMPLO:

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

Minha pergunta é: isso ajuda a usar uma cláusula ORDER BY na instrução SELECT para a coluna de índice em cluster ou algum ganho obtido seria negado pela classificação extra necessária para a cláusula ORDER BY?

GWR
fonte

Respostas:

18

Como as outras respostas já indicam, o SQL Server pode ou não garantir explicitamente que as linhas sejam classificadas em ordem de índice em cluster antes da insert.

Isso depende se o operador de índice clusterizado no plano tem ou não a DMLRequestSortpropriedade configurada (que por sua vez depende do número estimado de linhas inseridas).

Se você achar que o SQL Server está subestimando isso por qualquer motivo, poderá se beneficiar da adição de um explícito ORDER BYà SELECTconsulta para minimizar as divisões de páginas e a fragmentação resultante da INSERToperação

Exemplo:

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')
;  

Mostra que Testá massivamente fragmentado

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

Mas a T2fragmentação é mínima

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

Por outro lado, às vezes você pode forçar o SQL Server a subestimar a contagem de linhas quando você sabe que os dados já estão pré-classificados e deseja evitar uma classificação desnecessária. Um exemplo notável é ao inserir um grande número de linhas em uma tabela com uma newsequentialidchave de índice em cluster. Nas versões do SQL Server anteriores ao Denali, o SQL Server adiciona uma operação de classificação desnecessária e potencialmente cara . Isso pode ser evitado por

DECLARE @var INT =2147483647

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

O SQL Server estimará que 100 linhas serão inseridas, independentemente do tamanho Barabaixo do limite no qual uma classificação é adicionada ao plano. No entanto, conforme apontado nos comentários abaixo, isso significa que a inserção infelizmente não poderá tirar proveito do registro mínimo.

Martin Smith
fonte
12

Se o otimizador decidir que seria mais eficiente classificar os dados antes da inserção, o fará em algum lugar a montante do operador de inserção. Se você introduzir uma classificação como parte de sua consulta, o otimizador deverá perceber que os dados já estão classificados e omitir isso novamente. Observe que o plano de execução escolhido pode variar de execução para execução, dependendo do número de linhas inseridas na sua tabela de preparação.

Se você pode capturar planos de execução do processo com e sem a classificação explícita, anexe-os à sua pergunta para comentar.

Edit: 2011-10-28 17:00

A resposta do @ Gonsalu parece mostrar que sempre ocorre uma operação de classificação, não é esse o caso. Scripts de demonstração necessários!

Como os scripts estavam ficando muito grandes, eu os mudei para o Gist . Para facilitar a experimentação, os scripts usam o modo SQLCMD. Os testes são executados em 2K5SP3, dual core, 8GB.

Os testes de inserção abrangem três cenários:

  1. Dados de armazenamento temporário em cluster na mesma ordem que o destino.
  2. Preparar o índice agrupado de dados na ordem inversa.
  3. Dados temporários agrupados por col2 que contém um INT aleatório.

Primeira execução, inserindo 25 linhas.

1ª corrida, 25 linhas

Todos os três planos de execução são iguais, nenhuma classificação ocorre em qualquer lugar do plano e a verificação de índice em cluster é "ordenada = falsa".

Segunda execução, inserindo 26 linhas.

2ª corrida, 26 linhas

Desta vez, os planos diferem.

  • A primeira mostra a varredura de índice em cluster como ordenada = false. Nenhuma classificação ocorreu, pois os dados de origem são classificados adequadamente.
  • No segundo, a varredura de índice clusterizado como ordenada = true, para trás. Portanto, não temos uma operação de classificação, mas a necessidade de os dados serem classificados é reconhecida pelo otimizador e digitalizada em ordem inversa.
  • O terceiro mostra um operador de classificação.

Portanto, há um ponto de inflexão em que o otimizador considera uma classificação necessária. Como o @MartinSmith mostra, isso parece se basear nas linhas estimadas a serem inseridas. No meu equipamento de teste, 25 não exige classificação, 26 requer (2K5SP3, núcleo duplo, 8 GB)

O script SQLCMD inclui variáveis ​​que permitem alterar o tamanho das linhas da tabela (alterando a densidade da página) e o número de linhas no dbo.MyTable antes das inserções adicionais. Dos meus testes, nenhum deles afeta o ponto de inflexão.

Se algum leitor desejar, execute os scripts e adicione seu ponto de inflexão como um comentário. Interessado em saber se isso varia entre plataformas de teste e / ou versões.

Edit: 2011-10-28 20:15

Testes repetidos no mesmo equipamento, mas com 2K8R2. Desta vez, o ponto de inflexão é 251 linhas. Mais uma vez, variar a densidade da página e as contagens de linhas existentes não tem efeito.

Mark Storey-Smith
fonte
8

A ORDER BYcláusula na SELECTdeclaração é redundante.

É redundante porque as linhas que serão inseridas, se precisarem ser classificadas , são classificadas de qualquer maneira.

Vamos criar um caso de teste.

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;

Vamos ativar a exibição de texto dos planos de consulta reais, para que possamos ver quais tarefas são executadas pelo processador de consultas.

SET STATISTICS PROFILE ON;
GO

Agora, vamos INSERT2K linhas na tabela sem uma ORDER BYcláusula.

INSERT INTO #Test
SELECT number
  FROM #Sequence

O plano de execução real para esta consulta é o seguinte.

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]))

Como você pode ver, há um operador Sort antes que o INSERT real ocorra.

Agora, vamos limpar a tabela e INSERT2k linhas na tabela com a ORDER BYcláusula

TRUNCATE TABLE #Test;
GO

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

O plano de execução real para esta consulta é o seguinte.

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]))

Observe que é o mesmo plano de execução usado para a INSERTinstrução sem a ORDER BYcláusula

Agora, a Sortoperação nem sempre é necessária, como Mark Smith mostrou em outra resposta (se o número de linhas a serem inseridas for baixo), mas a ORDER BYcláusula ainda é redundante nesse caso, porque, mesmo com uma explícita ORDER BY, nenhuma Sortoperação é gerada pelo processador de consultas.

Você pode otimizar uma INSERTinstrução em uma tabela com um índice clusterizado, usando um registro mínimo INSERT, mas isso está fora do escopo desta pergunta.

Atualizado em 11/11/2011: como Mark Smith mostrou , os INSERTs em uma tabela com um índice clusterizado nem sempre precisam ser classificados - a ORDER BYcláusula também é redundante nesse caso.

gonsalu
fonte