INSERTAR eficientemente en una tabla con índice agrupado

28

Tengo una instrucción SQL que inserta filas en una tabla con un índice agrupado en la columna TRACKING_NUMBER.

P.EJ:

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

Mi pregunta es: ¿es útil usar una cláusula ORDER BY en la instrucción SELECT para la columna de índice agrupado, o cualquier ganancia obtenida sería negada por el tipo adicional requerido para la cláusula ORDER BY?

GWR
fuente

Respuestas:

18

Como las otras respuestas ya indican, SQL Server puede o no garantizar explícitamente que las filas se ordenen en orden de índice agrupado antes de insert.

Esto depende de si el operador de índice agrupado en el plan tiene o no el DMLRequestSortconjunto de propiedades (que a su vez depende del número estimado de filas que se insertan).

Si encuentra que SQL Server está subestimando esto por cualquier razón, podría beneficiarse al agregar un mensaje explícito ORDER BYa la SELECTconsulta para minimizar las divisiones de página y la consiguiente fragmentación de la INSERToperación

Ejemplo:

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

Muestra que Testá masivamente 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

Pero para la T2fragmentación es 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 el contrario, a veces es posible que desee forzar a SQL Server a subestimar el recuento de filas cuando sepa que los datos ya están ordenados previamente y desee evitar una clasificación innecesaria. Un ejemplo notable es cuando se inserta una gran cantidad de filas en una tabla con una newsequentialidclave de índice agrupada. En las versiones de SQL Server anteriores a Denali, SQL Server agrega una operación de clasificación innecesaria y potencialmente costosa . Esto se puede evitar con

DECLARE @var INT =2147483647

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

Luego, SQL Server estimará que se insertarán 100 filas independientemente del tamaño del Barcual esté por debajo del umbral en el que se agrega una clasificación al plan. Sin embargo, como se señala en los comentarios a continuación, esto significa que, por desgracia, la inserción no podrá aprovechar el registro mínimo.

Martin Smith
fuente
12

Si el optimizador decide que sería más eficiente clasificar los datos antes de insertarlos, lo hará en algún lugar aguas arriba del operador de inserción. Si introduce una ordenación como parte de su consulta, el optimizador debe darse cuenta de que los datos ya están ordenados y omitir hacerlo nuevamente. Tenga en cuenta que el plan de ejecución elegido puede variar de una ejecución a otra dependiendo del número de filas insertadas en su tabla de etapas.

Si puede capturar planes de ejecución del proceso con y sin el orden explícito, adjúntelos a su pregunta para hacer comentarios.

Editar: 2011-10-28 17:00

La respuesta de @Gonsalu parece mostrar que siempre ocurre una operación de clasificación, este no es el caso. ¡Se requieren guiones de demostración!

Como los guiones se estaban haciendo bastante grandes, los moví a Gist . Para facilitar la experimentación, los scripts usan el modo SQLCMD. Las pruebas se ejecutan en 2K5SP3, doble núcleo, 8 GB.

Las pruebas de inserción cubren tres escenarios:

  1. El índice de datos agrupados en etapas en el mismo orden que el destino.
  2. Los datos de estadificación agruparon el índice en orden inverso.
  3. Datos de estadificación agrupados por col2 que contiene un INT aleatorio.

Primera ejecución, insertando 25 filas.

1a carrera, 25 filas

Los tres planes de ejecución son iguales, no se produce ningún orden en ninguna parte del plan y el análisis de índice agrupado es "ordenado = falso".

Segunda ejecución, insertando 26 filas.

2da carrera, 26 filas

Esta vez los planes difieren.

  • El primero muestra el escaneo de índice agrupado como ordenado = falso. No se ha producido ninguna ordenación ya que los datos de origen se han ordenado adecuadamente.
  • En el segundo, el escaneo de índice agrupado como ordenado = verdadero, hacia atrás. Por lo tanto, no tenemos una operación de clasificación, pero el optimizador reconoce la necesidad de ordenar los datos y los escanea en orden inverso.
  • El tercero muestra un operador de clasificación.

Por lo tanto, hay un punto de inflexión en el que el optimizador considera que un tipo es necesario. Como muestra @MartinSmith, esto parece basarse en las filas estimadas que se insertarán. En mi plataforma de prueba 25 no requiere un tipo, 26 sí (2K5SP3, doble núcleo, 8 GB)

El script SQLCMD incluye variables que permiten cambiar el tamaño de las filas en la tabla (alterando la densidad de la página) y el número de filas en dbo.MyTable antes de las inserciones adicionales. Según mis pruebas, ninguno tiene ningún efecto en el punto de inflexión.

Si algún lector está tan inclinado, ejecute los scripts y agregue su punto de inflexión como comentario. Interesado saber si varía entre plataformas de prueba y / o versiones.

Editar: 2011-10-28 20:15

Pruebas repetidas en el mismo equipo pero con 2K8R2. Esta vez el punto de inflexión es de 251 filas. Nuevamente, variar la densidad de la página y los recuentos de filas existentes no tiene ningún efecto.

Mark Storey-Smith
fuente
8

La ORDER BYcláusula en la SELECTdeclaración es redundante.

Es redundante porque las filas que se insertarán, si es necesario ordenarlas , se ordenan de todos modos.

Vamos a crear un caso de prueba.

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;

Habilitemos la visualización de texto de los planes de consulta reales, para que podamos ver qué tareas lleva a cabo el procesador de consultas.

SET STATISTICS PROFILE ON;
GO

Ahora, vamos a INSERT2K filas en la tabla sin una ORDER BYcláusula.

INSERT INTO #Test
SELECT number
  FROM #Sequence

El plan de ejecución real para esta consulta es el siguiente.

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 puede ver, hay un operador Ordenar antes de que ocurra el INSERT real.

Ahora, limpiemos la tabla y INSERT2k filas en la tabla con la ORDER BYcláusula.

TRUNCATE TABLE #Test;
GO

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

El plan de ejecución real para esta consulta es el siguiente.

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

Tenga en cuenta que es el mismo plan de ejecución que se utilizó para la INSERTdeclaración sin la ORDER BYcláusula.

Ahora, la Sortoperación no siempre es necesaria, como Mark Smith ha mostrado en otra respuesta (si el número de filas a insertar es bajo), pero la ORDER BYcláusula sigue siendo redundante en ese caso, porque incluso con una operación explícita ORDER BY, no Sortse genera ninguna operación por el procesador de consultas.

Puede optimizar una INSERTdeclaración en una tabla con un índice agrupado, utilizando un registro mínimo INSERT, pero eso está fuera del alcance de esta pregunta.

Actualizado el 2011-11-02: Como Mark Smith ha demostrado , INSERTes posible que no siempre sea necesario ordenar los ORDER BYmensajes s en una tabla con un índice agrupado ; sin embargo, la cláusula también es redundante en ese caso.

gonsalu
fuente