Estoy a punto de tener que volver a escribir un código bastante antiguo usando el BULK INSERT
comando de SQL Server porque el esquema ha cambiado y se me ocurrió que tal vez debería pensar en cambiar a un procedimiento almacenado con un TVP, pero me pregunto qué efecto podría tener sobre el rendimiento.
Alguna información de fondo que podría ayudar a explicar por qué hago esta pregunta:
Los datos realmente llegan a través de un servicio web. El servicio web escribe un archivo de texto en una carpeta compartida en el servidor de la base de datos que a su vez realiza un
BULK INSERT
. Este proceso se implementó originalmente en SQL Server 2000, y en ese momento realmente no había otra alternativa que arrojar algunos cientos deINSERT
declaraciones en el servidor, que en realidad era el proceso original y era un desastre de rendimiento.Los datos se insertan de forma masiva en una tabla de preparación permanente y luego se fusionan en una tabla mucho más grande (después de lo cual se eliminan de la tabla de preparación).
La cantidad de datos para insertar es "grande", pero no "enorme", por lo general unos cientos de filas, tal vez entre 5 y 10 mil filas en raras ocasiones. Por tanto, mi primera impresión es que
BULK INSERT
ser una operación no registrada no hará que diferencia un gran (pero por supuesto que no estoy seguro, por lo tanto, la pregunta).La inserción es en realidad parte de un proceso por lotes en canalización mucho más grande y debe ocurrir muchas veces seguidas; por lo tanto, el rendimiento es fundamental.
Las razones por las que me gustaría reemplazar el BULK INSERT
con un TVP son:
Escribir el archivo de texto sobre NetBIOS probablemente ya esté costando algo de tiempo, y es bastante espantoso desde una perspectiva arquitectónica.
Creo que la tabla de etapas puede (y debe) eliminarse. La razón principal por la que está ahí es que los datos insertados deben usarse para un par de actualizaciones más al mismo tiempo de la inserción, y es mucho más costoso intentar la actualización desde la tabla de producción masiva que usar una preparación casi vacía mesa. Con un TVP, el parámetro básicamente es la tabla de preparación, puedo hacer lo que quiera con él antes / después de la inserción principal.
Podría acabar con la verificación de duplicados, el código de limpieza y toda la sobrecarga asociada con las inserciones masivas.
No hay necesidad de preocuparse por la contención de bloqueo en la tabla de preparación o tempdb si el servidor obtiene algunas de estas transacciones a la vez (intentamos evitarlo, pero sucede).
Obviamente voy a perfilar esto antes de poner algo en producción, pero pensé que sería una buena idea preguntar primero antes de pasar todo ese tiempo, ver si alguien tiene alguna advertencia severa que emitir sobre el uso de TVP para este propósito.
Entonces, para cualquiera que esté lo suficientemente cómodo con SQL Server 2008 como para haber intentado o al menos investigado esto, ¿cuál es el veredicto? Para inserciones de, digamos, unos cientos o miles de filas, que ocurren con bastante frecuencia, ¿los TVP cortan la mostaza? ¿Existe una diferencia significativa en el rendimiento en comparación con los insertos a granel?
Actualización: ¡Ahora con un 92% menos de interrogantes!
(AKA: Resultados de la prueba)
El resultado final está ahora en producción después de lo que parece un proceso de implementación de 36 etapas. Ambas soluciones fueron ampliamente probadas:
- Extraer el código de la carpeta compartida y usar la
SqlBulkCopy
clase directamente; - Cambio a un procedimiento almacenado con TVP.
Para que los lectores puedan tener una idea de qué se probó exactamente, para disipar cualquier duda sobre la confiabilidad de estos datos, aquí hay una explicación más detallada de lo que realmente hace este proceso de importación :
Empiece con una secuencia de datos temporal que normalmente tiene entre 20 y 50 puntos de datos (aunque a veces puede llegar a unos pocos cientos);
Realice un montón de procesamiento loco que sea en su mayoría independiente de la base de datos. Este proceso está en paralelo, por lo que aproximadamente 8-10 de las secuencias en (1) se procesan al mismo tiempo. Cada proceso paralelo genera 3 secuencias adicionales.
Tome las 3 secuencias y la secuencia original y combínelas en un lote.
Combine los lotes de las 8-10 tareas de procesamiento ahora terminadas en un gran superlote.
Impórtelo utilizando la
BULK INSERT
estrategia (consulte el paso siguiente) o la estrategia TVP (vaya al paso 8).Utilice la
SqlBulkCopy
clase para volcar todo el super-lote en 4 tablas de preparación permanentes.Ejecute un procedimiento almacenado que (a) realiza una serie de pasos de agregación en 2 de las tablas, incluidas varias
JOIN
condiciones, y luego (b) realiza unaMERGE
en 6 tablas de producción utilizando los datos agregados y no agregados. (Terminado)O
Genere 4
DataTable
objetos que contengan los datos a fusionar; 3 de ellos contienen tipos CLR que, lamentablemente, no son compatibles con los TVP de ADO.NET, por lo que deben introducirse como representaciones de cadenas, lo que perjudica un poco el rendimiento.Alimente los TVP a un procedimiento almacenado, que realiza esencialmente el mismo procesamiento que (7), pero directamente con las tablas recibidas. (Terminado)
Los resultados fueron razonablemente cercanos, pero el enfoque de TVP finalmente se desempeñó mejor en promedio, incluso cuando los datos excedieron las 1000 filas por una pequeña cantidad.
Tenga en cuenta que este proceso de importación se ejecuta miles de veces seguidas, por lo que fue muy fácil obtener un tiempo promedio simplemente contando cuántas horas (sí, horas) se necesitaron para finalizar todas las fusiones.
Originalmente, una fusión promedio tardaba casi exactamente 8 segundos en completarse (con carga normal). Eliminar el kludge de NetBIOS y cambiar a SqlBulkCopy
redujo el tiempo a casi exactamente 7 segundos. El cambio a TVP redujo aún más el tiempo a 5,2 segundos por lote. Eso es una mejora del 35% en el rendimiento de un proceso cuyo tiempo de ejecución se mide en horas, por lo que no está nada mal. También es una mejora de ~ 25% SqlBulkCopy
.
De hecho, estoy bastante seguro de que la verdadera mejora fue significativamente mayor que esto. Durante las pruebas se hizo evidente que la fusión final ya no era el camino crítico; en cambio, el servicio web que estaba haciendo todo el procesamiento de datos estaba comenzando a fallar por el número de solicitudes que ingresaban. Ni la CPU ni la E / S de la base de datos estaban realmente al máximo y no había actividad de bloqueo significativa. En algunos casos, observamos un intervalo de unos segundos inactivos entre fusiones sucesivas. Hubo un pequeño espacio, pero mucho más pequeño (medio segundo más o menos) cuando se usa SqlBulkCopy
. Pero supongo que se convertirá en un cuento para otro día.
Conclusión: Los parámetros con valores de tabla realmente funcionan mejor que las BULK INSERT
operaciones para procesos complejos de importación + transformación que operan en conjuntos de datos de tamaño medio.
Me gustaría agregar otro punto, solo para calmar cualquier aprensión por parte de la gente que está a favor de las tablas de preparación. En cierto modo, todo este servicio es un proceso de puesta en escena gigante. Cada paso del proceso está fuertemente auditado, por lo que no necesitamos una tabla de preparación para determinar por qué falló una combinación en particular (aunque en la práctica casi nunca ocurre). Todo lo que tenemos que hacer es establecer una bandera de depuración en el servicio y se romperá con el depurador o volcará sus datos en un archivo en lugar de en la base de datos.
En otras palabras, ya tenemos información más que suficiente sobre el proceso y no necesitamos la seguridad de una mesa de preparación; La única razón por la que teníamos la tabla de preparación en primer lugar fue para evitar golpear todas las declaraciones INSERT
y UPDATE
que hubiéramos tenido que usar de otra manera. En el proceso original, los datos de preparación solo vivían en la tabla de preparación durante fracciones de segundo de todos modos, por lo que no agregaban valor en términos de mantenimiento / mantenibilidad.
También tenga en cuenta que hemos no sustituido cada BULK INSERT
operación con TVP. Varias operaciones que tratan con grandes cantidades de datos y / o no necesitan hacer nada especial con los datos que no sean arrojarlos a la base de datos todavía se usan SqlBulkCopy
. No estoy sugiriendo que los TVP sean una panacea del desempeño, solo que tuvieron éxito SqlBulkCopy
en esta instancia específica que involucra varias transformaciones entre la puesta en escena inicial y la fusión final.
Así que ahí lo tienes. El punto va a TToni por encontrar el enlace más relevante, pero también aprecio las otras respuestas. ¡Gracias de nuevo!