2008-08-23 15 views
52

Estoy golpeando algunos cuellos de botella de rendimiento con mi cliente C# insertando datos a granel en una base de datos SQL Server 2005 y estoy buscando maneras de acelerar el proceso.¿Cuál es la forma más rápida de insertar a granel una gran cantidad de datos en SQL Server (cliente C#)

Ya estoy usando el SqlClient.SqlBulkCopy (que se basa en TDS) para acelerar la transferencia de datos a través del cable, lo que ayudó mucho, pero aún estoy buscando más.

Tengo una tabla simple que se parece a esto:

CREATE TABLE [BulkData](
[ContainerId] [int] NOT NULL, 
[BinId] [smallint] NOT NULL, 
[Sequence] [smallint] NOT NULL, 
[ItemId] [int] NOT NULL, 
[Left] [smallint] NOT NULL, 
[Top] [smallint] NOT NULL, 
[Right] [smallint] NOT NULL, 
[Bottom] [smallint] NOT NULL, 
CONSTRAINT [PKBulkData] PRIMARY KEY CLUSTERED 
(
    [ContainerIdId] ASC, 
    [BinId] ASC, 
    [Sequence] ASC 
)) 

estoy insertando datos en fragmentos con un promedio de 300 filas en las que ContainerId y BinId son constantes en cada bloque y el valor de secuencia es 0-n y los valores se clasifican previamente según la clave principal.

El contador de rendimiento% de tiempo de disco pasa mucho tiempo al 100%, por lo que está claro que el disco IO es el problema principal pero las velocidades que obtengo son varios órdenes de magnitud por debajo de una copia de archivo sin formato.

¿Ayuda a cualquier si:

  1. dejar la llave primaria, mientras que yo estoy haciendo la inserción y volver a crearlo más adelante
  2. Do inserta en una tabla temporal con el mismo esquema y periódicamente transferirlos a la mesa principal para mantener el tamaño de la tabla donde ocurren las inserciones pequeñas
  3. ¿Algo más?

- Sobre la base de las respuestas que he conseguido, vamos a aclarar un poco:

Portman: Estoy usando un índice agrupado porque cuando los datos son todos importados que tendrá que acceder a los datos secuencialmente en ese orden. No necesito particularmente que el índice esté allí mientras se importan los datos. ¿Hay alguna ventaja de tener un índice PK no agrupado mientras se realizan las inserciones, en lugar de eliminar la restricción por completo para la importación?

Chopeen: Los datos se generan de forma remota en muchas otras máquinas (mi servidor SQL solo puede manejar alrededor de 10 actualmente, pero me gustaría poder agregar más). No es práctico ejecutar todo el proceso en la máquina local porque tendría que procesar 50 veces más datos de entrada para generar la salida.

Jason: No estoy haciendo ninguna consulta concurrente en la tabla durante el proceso de importación, intentaré soltar la clave principal y ver si eso ayuda.

+0

http://msdn.microsoft.com/en-us/library/ms174335.aspx – JohnB

Respuesta

0

Sí, sus ideas ayudarán.
Apóyate en la opción 1 si no hay lecturas mientras estás cargando.
Apóyate en la opción 2 si tu tabla de destino está siendo consultada durante tu procesamiento.

@Andrew
Pregunta. Su inserción en trozos de 300. ¿Cuál es la cantidad total de su inserción? El servidor SQL debería poder manejar 300 inserciones antiguas simples muy rápido.

0

¿Qué hay de aumentar la memoria asignada al servidor o el tamaño del búfer utilizado por el servidor, si es posible?

4

¿Has probado a usar transacciones?

Según lo que describe, haciendo que el servidor confíe el 100% del tiempo en el disco, parece que está enviando cada fila de datos en una sentencia SQL atómica forzando al servidor a confirmar (escribir en el disco) cada fila.

Si usó transacciones en su lugar, el servidor solo confirmará una vez al final de la transacción.

Para obtener ayuda adicional: ¿Qué método está utilizando para insertar datos en el servidor? ¿Actualizando una DataTable usando un DataAdapter, o ejecutando cada oración usando una cadena?

+0

muy tardía, pero para nadie más encontrar esto ahora, esto es algo bueno de hacer. Estoy escribiendo un procedimiento de inserción usando el código genérico DbCommand desde una aplicación cliente, por lo que no puedo usar cosas específicas de SqlClient o las herramientas masivas de SQL Server; este simple consejo ha demorado mi tiempo de ejecución de un minuto y medio a 5 segundos. – Whelkaholism

18

Ya está usando SqlBulkCopy, que es un buen comienzo.

Sin embargo, el uso de la clase SqlBulkCopy no significa necesariamente que SQL realizará una copia masiva. En particular, existen algunos requisitos que se deben cumplir para que SQL Server realice una inserción masiva eficiente.

Más información:

Por curiosidad, ¿por qué se establece el índice así? Parece que ContainerId/BinId/Sequence es mucho más adecuado para ser un índice no agrupado. ¿Hay alguna razón particular por la que desea que este índice se agrupe?

1

Creo que parece que esto podría hacerse usando SSIS packages. Son similares a los paquetes DTS de SQL 2000. Los he usado para transformar con éxito todo, desde archivos CSV de texto plano, desde tablas SQL existentes, e incluso desde archivos XLS con filas de 6 dígitos distribuidos en varias hojas de trabajo. Puede usar C# para transformar los datos en un formato importable (CSV, XLS, etc.), luego haga que su servidor SQL ejecute un trabajo SSIS programado para importar los datos.

Es bastante fácil crear un paquete SSIS, hay un asistente integrado en la herramienta Enterprise Manager de SQL Server (etiquetada como "Importar datos", creo), y al final del asistente, le da la opción de guardarlo como un paquete SSIS. Hay un montón más información on Technet también.

3

BCP - es un dolor de configurar, pero ha existido desde el comienzo de DBs y es muy, muy rápido.

A menos que esté insertando datos en ese orden, el índice de 3 partes realmente ralentizará las cosas. Aplicarlo más tarde realmente también ralentizará las cosas, pero estará en un segundo paso.

Las claves compuestas en Sql son siempre bastante lentas, cuanto más grande es la tecla, más lenta.

8

Supongo que verá una mejora dramática si cambia ese índice para que sea no agrupado.Esto le deja con dos opciones:

  1. cambiar el índice de no agrupado, y dejarlo como una tabla de montón, sin un índice agrupado
  2. cambiar el índice de no agrupado, pero luego añadir una clave sustituta (como "Identificación ") y que sea una identidad, clave primaria, y el índice agrupado

Cualquiera de los dos acelerar sus inserciones sin notablemente ralentizar su lecturas.

Piénselo de esta manera: en este momento, le está diciendo a SQL que haga una inserción masiva, pero luego le pide a SQL que reordene toda la tabla en cada tabla que agregue algo. Con un índice no agrupado, agregará los registros en el orden en que ingresen y luego creará un índice separado que indique el orden que desean.

3

No soy realmente un tipo brillante y no tengo mucha experiencia con el método SqlClient.SqlBulkCopy pero aquí están mis 2 centavos por lo que vale. Espero que te ayude a ti y a los demás (o al menos haga que la gente diga mi ignorancia).

Nunca coincidirá con una velocidad de copia de archivo sin formato a menos que su archivo de datos de base de datos (mdf) se encuentre en un disco físico separado del archivo de registro de transacciones (ldf). Además, cualquier índice agrupado también debería estar en un disco físico separado para una comparación más justa.

Su copia sin formato no está registrando o manteniendo un orden de selección de campos (columnas) para fines de indexación.

Estoy de acuerdo con Portman en la creación de una semilla de identidad no agrupada y el cambio de su índice existente no agrupado a un índice agrupado.

En cuanto a qué construcción está utilizando en los clientes ... (adaptador de datos, conjunto de datos, tabla de datos, etc.). Si su disco io en el servidor está al 100%, no creo que su tiempo se dedique mejor a analizar las construcciones del cliente, ya que parecen ser más rápidas de lo que el servidor puede manejar actualmente.

Si usted sigue los enlaces de Portman sobre el registro mínimo, no pensaría que rodea sus copias a granel en las transacciones sería una gran ayuda si los hay, pero me he equivocado muchas veces en mi vida;)

Este won' Necesariamente lo ayudo ahora mismo pero si descubre su problema actual, este próximo comentario podría ayudar con el próximo cuello de botella (rendimiento de la red), especialmente si está en Internet ...

Chopeen hizo una pregunta interesante también. ¿Cómo determinó utilizar 300 fragmentos de recuento de registros para insertar? SQL Server tiene un tamaño de paquete predeterminado (creo que es 4096 bytes) y tendría sentido derivar el tamaño de sus registros y asegurarse de que está haciendo un uso eficiente de los paquetes que transmiten entre el cliente y el servidor. (Tenga en cuenta que puede cambiar el tamaño de su paquete en su código cliente en lugar de la opción del servidor que obviamente lo cambiaría para todas las comunicaciones del servidor, probablemente no sea una buena idea). Por ejemplo, si su tamaño de registro resulta en 300 lotes de registro que requieren 4500 bytes, enviará 2 paquetes con el segundo paquete desperdiciado en su mayoría. Si el recuento de registros por lotes se asignó arbitrariamente, podría tener sentido realizar cálculos matemáticos rápidos y fáciles.

Por lo que puedo decir (y recuerde acerca de los tamaños de tipo de datos) tiene exactamente 20 bytes para cada registro (si int = 4 bytes y smallint = 2 bytes). Si está utilizando 300 lotes de recuento de registros, entonces está tratando de enviar 300 x 20 = 6.000 bytes (además supongo que habrá un poco de sobrecarga para la conexión, etc.). Es posible que sea más eficiente enviarlos en 200 lotes de recuentos (200 x 20 = 4,000 + espacio para gastos generales) = 1 paquete.Por otra parte, su cuello de botella todavía parece ser el disco io del servidor.

me doy cuenta de que está comparando una transferencia de datos sin procesar a la SqlBulkCopy con el mismo hardware/configuración, pero aquí es donde me gustaría ir también si el desafío era mío:

Este post probablemente no le ayudará más pues es bastante antiguo, pero a continuación pregunto cuál es la configuración RAID de tu disco y qué velocidad de disco estás usando. Intente colocar el archivo de registro en una unidad que use RAID 10 con un RAID 5 (idealmente 1) en su archivo de datos. Esto puede ayudar a reducir un gran movimiento del eje a diferentes sectores en el disco y dar como resultado más tiempo de lectura/escritura en lugar del improductivo estado "en movimiento". Si ya separa sus archivos de datos y de registro, ¿tiene su índice en una unidad de disco física diferente de su archivo de datos (solo puede hacerlo con índices agrupados). Eso permitiría no solo actualizar al mismo tiempo la información de registro con la inserción de datos, sino que también permitiría que la inserción de índice (y cualquier operación costosa de página de índice) ocurriera simultáneamente.

18

Así es como se puede activar/desactivar los índices en SQL Server:

--Disable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users DISABLE 
GO 
--Enable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users REBUILD

Éstos son algunos recursos para ayudar a encontrar una solución:

Some bulk loading speed comparisons

Use SqlBulkCopy to Quickly Load Data from your Client to SQL Server

Optimizing Bulk Copy Performance

duda se verá en las opciones NOCHECK y TABLOCK:

Table Hints (Transact-SQL)

INSERT (Transact-SQL)

+0

¡Alguna buena información, gracias! – tbone

+0

La información sobre este hilo podría ser útil http://dba.stackexchange.com/questions/30734/bulk-data-loading-and-transaction-log –

Cuestiones relacionadas