Estoy ejecutando una comparación de rendimiento entre el uso de 1000 declaraciones INSERT:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0)
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1)
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999)
.. versus el uso de una sola declaración INSERT con 1000 valores:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES
('db72b358-e9b5-4101-8d11-7d7ea3a0ae7d', 'First 0', 'Last 0', 0),
('6a4874ab-b6a3-4aa4-8ed4-a167ab21dd3d', 'First 1', 'Last 1', 1),
...
('9d7f2a58-7e57-4ed4-ba54-5e9e335fb56c', 'First 999', 'Last 999', 999)
Para mi gran sorpresa, los resultados son opuestos a lo que pensaba:
- 1000 declaraciones INSERT: 290 mseg.
- 1 instrucción INSERT con 1000 VALORES: 2800 mseg.
La prueba se ejecuta directamente en MSSQL Management Studio con SQL Server Profiler usado para la medición (y obtuve resultados similares ejecutándolo desde el código C # usando SqlClient, lo cual es aún más sorprendente considerando todos los recorridos de ida y vuelta de las capas DAL)
¿Puede esto ser razonable o explicarse de alguna manera? ¿Cómo es que un método supuestamente más rápido da como resultado un rendimiento 10 veces (!) Peor ?
Gracias.
EDITAR: Adjuntar planes de ejecución para ambos:
Respuestas:
Su plan muestra que las inserciones individuales están usando procedimientos parametrizados (posiblemente auto parametrizados) por lo que el tiempo de análisis / compilación para estos debe ser mínimo.
Pensé en investigar esto un poco más, así que configuré un bucle ( script ) e intenté ajustar el número de
VALUES
cláusulas y registrar el tiempo de compilación.Luego dividí el tiempo de compilación por el número de filas para obtener el tiempo de compilación promedio por cláusula. Los resultados están debajo
Hasta 250
VALUES
cláusulas presentes, el tiempo de compilación / número de cláusulas tiene una ligera tendencia al alza pero nada demasiado dramático.Pero luego hay un cambio repentino.
Esa sección de los datos se muestra a continuación.
El tamaño del plan en caché que había estado creciendo linealmente cae repentinamente, pero CompileTime aumenta 7 veces y CompileMemory se dispara. Este es el punto de corte entre el plan que es auto parametrizado (con 1000 parámetros) y uno no parametrizado. A partir de entonces, parece volverse linealmente menos eficiente (en términos de número de cláusulas de valor procesadas en un tiempo determinado).
No estoy seguro de por qué debería ser esto. Presumiblemente, cuando está compilando un plan para valores literales específicos, debe realizar alguna actividad que no se escala linealmente (como ordenar).
No parece afectar el tamaño del plan de consulta en caché cuando probé una consulta que consta completamente de filas duplicadas y tampoco afecta el orden de la salida de la tabla de las constantes (y mientras lo inserta en un montón, el tiempo dedicado a ordenar sería inútil de todos modos incluso si lo hiciera).
Además, si se agrega un índice agrupado a la tabla, el plan aún muestra un paso de clasificación explícito, por lo que no parece estar ordenando en tiempo de compilación para evitar una clasificación en tiempo de ejecución.
Traté de ver esto en un depurador, pero los símbolos públicos para mi versión de SQL Server 2008 no parecen estar disponibles, así que en su lugar tuve que buscar la
UNION ALL
construcción equivalente en SQL Server 2005.A continuación se muestra un rastro de pila típico
Entonces, al eliminar los nombres en el seguimiento de la pila, parece pasar mucho tiempo comparando cadenas.
Este artículo de KB indica que
DeriveNormalizedGroupProperties
está asociado con lo que solía llamarse la etapa de normalización del procesamiento de consultasEsta etapa ahora se llama enlace o algebrización y toma la salida del árbol de análisis sintáctico de la etapa anterior y genera un árbol de expresión algebrizado (árbol del procesador de consultas) para avanzar a la optimización (optimización del plan trivial en este caso) [ref] .
Probé un experimento más ( Script ) que consistía en volver a ejecutar la prueba original pero observando tres casos diferentes.
Se puede ver claramente que cuanto más largas son las cadenas, peor se ponen las cosas y que, por el contrario, cuanto más duplicados, mejores son las cosas. Como se mencionó anteriormente, los duplicados no afectan el tamaño del plan almacenado en caché, por lo que supongo que debe haber un proceso de identificación duplicada al construir el árbol de expresión algebrizado.
Editar
@Lieven muestra un lugar donde se aprovecha esta información aquí
Debido a que en el momento de la compilación puede determinar que la
Name
columna no tiene duplicados, omite el orden por la1/ (ID - ID)
expresión secundaria en el tiempo de ejecución (la ordenación en el plan solo tiene unaORDER BY
columna) y no se genera ningún error de división por cero. Si se agregan duplicados a la tabla, el operador de clasificación muestra dos orden por columnas y se genera el error esperado.fuente
<ParameterList>
uno que con una<ConstantScan><Values><Row>
lista.<ConstantScan><Values><Row>
lugar de<ParameterList>
.No es demasiado sorprendente: el plan de ejecución para la pequeña inserción se calcula una vez y luego se reutiliza 1000 veces. Analizar y preparar el plan es rápido, porque solo tiene cuatro valores para eliminar. Un plan de 1000 filas, por otro lado, debe manejar 4000 valores (o 4000 parámetros si parametrizó sus pruebas de C #). Esto podría consumir fácilmente el ahorro de tiempo que gana al eliminar 999 viajes de ida y vuelta a SQL Server, especialmente si su red no es demasiado lenta.
fuente
El problema probablemente tenga que ver con el tiempo que lleva compilar la consulta.
Si desea acelerar las inserciones, lo que realmente debe hacer es envolverlas en una transacción:
Desde C #, también puede considerar usar un parámetro con valores de tabla. Emitir varios comandos en un solo lote, separándolos con punto y coma, es otro enfoque que también ayudará.
fuente
Me encontré con una situación similar al intentar convertir una tabla con varias filas de 100k con un programa C ++ (MFC / ODBC).
Dado que esta operación tomó mucho tiempo, pensé en agrupar múltiples inserciones en una (hasta 1000 debido a las limitaciones de MSSQL ). Supongo que muchas declaraciones de inserción únicas crearían una sobrecarga similar a la que se describe aquí .
Sin embargo, resulta que la conversión tomó un poco más de tiempo:
Entonces, 1000 llamadas individuales a CDatabase :: ExecuteSql, cada una con una sola instrucción INSERT (método 1) son aproximadamente el doble de rápidas que una sola llamada a CDatabase :: ExecuteSql con una instrucción INSERT de varias líneas con 1000 tuplas de valor (método 2).
Actualización: Entonces, lo siguiente que intenté fue agrupar 1000 declaraciones INSERT separadas en una sola cadena y hacer que el servidor lo ejecute (método 3). Resulta que esto es incluso un poco más rápido que el método 1.
Editar: estoy usando Microsoft SQL Server Express Edition (64 bits) v10.0.2531.0
fuente