2011-01-20 27 views
11

¿Cuál es la manera más rápida de hacer esto:¿La forma más rápida de insertar en una tabla de SQL Server desde el código .NET?

  • Una mesa, no hay referencias que no puedo llenado previo (es decir, no es una de las claves de referencia allí, pero tengo todos los datos rellenados)
  • gran cantidad de datos . Hablamos de cientos de millones de filas por día, entrando dinámicamente a través de una API
  • Las solicitudes deben/deben procesarse tan pronto como sea posible en un escenario de tiempo casi real (es decir, no escribir en un archivo para cargar una por día) . 2 segundos es la demora máxima normal de
  • máquinas separadas para los datos/aplicaciones y el servidor SQL Server

Lo que hago ahora:

  • agregar hasta 32 * 1024 filas en una matriz, a continuación, Cola eso.
  • Lea la cola en 2-3 hilos. Insertar en la base de datos usando SqlBulkCopy.

Obtengo alrededor de 60k-75k filas importadas por segundo, lo que no es suficiente, pero bastante cerca. Me encantaría golpear 250,000 filas.

Hasta el momento, nada se usa realmente. Obtengo un 20% de tiempo de bloques de "E/S de red", tengo un núcleo con un 80% de CPU cargado. Los discos están escribiendo 7mb-14mb, en su mayoría inactivos. La longitud promedio de la cola en un RAID 10 de 6 aves rapaces es ... 0.25.

¿Alguien alguna idea de cómo acelerar esto? Servidor más rápido (hasta el momento es virtual, 8 GB de memoria RAM, 4 núcleos, disco físico de paso para datos).


Añadiendo algunas aclaraciones:

  • Este es un R2 Enterprise de SQL Server 2008 en un servidor 2008 R2. la máquina tiene 4 núcleos, 8 gb ram. Todo 64 bit. El promedio de carga del 80% proviene de esta máquina que muestra aproximadamente un 20% de carga de CPU.
  • La tabla es simple, no tiene una clave principal, solo un índice en una referencia relacional (referencia del instrumento) y una marca de tiempo única (dentro de un conjunto de instrumentos, por lo que no se aplica).
  • Los campos en la tabla son: marca de tiempo, referencia del instrumento (sin clave forzada), tipo de datos (char 1, uno de los caracteres que indican qué datos se publican), precio (doble) y volumen (int). Como pueden ver, esta es una mesa MUY delgada. Los datos en cuestión son datos de marcado para instrumentos financieros.
  • La cuestión también es sobre hardware, principalmente porque no veo un verdadero cuello de botella. Estoy insertando en múltiples transacciones y me da un beneficio, pero pequeño. Los discos, la CPU no muestran una carga significativa, la espera de la red io es alta (300 ms/segundo, 30% en este momento), pero está en la misma plataforma de virtualización que ejecuta JSUT en los dos servidores y tiene suficientes núcleos para ejecutarlos todos. Estoy bastante dispuesto a "comprar otro servidor", pero primero quiero identificar el cuello de botella ... especialmente teniendo en cuenta que al final del día no estoy captando lo que es el cuello de botella. El registro es irrelevante: las inserciones masivas NO entran en el registro de datos como datos (sin índice agrupado).

¿Ayudaría la partición vertical, por ejemplo, mediante un byte (tinyint) que dividiría el universo del instrumento por ejemplo 16 tablas, y yo haciendo así hasta 16 inserciones al mismo tiempo?Como en realidad los datos provienen de diferentes intercambios, podría hacer una partición por intercambio. Este sería un campo de división natural (que en realidad está en el instrumento, pero podría duplicar estos datos aquí).


Algunos más aclaraciones: conseguido la velocidad aún mayor (90k), ahora claramente limitado por la red IO entre las máquinas, lo que podría ser de conmutación VM.

Lo que hago ahora es hacer una conexión por filas de 32k, poner una tabla temporal, insertar en esto con SqlBUlkdCopy, ENTONCES utilizar UNA declaración SQL para copiar a la tabla principal - minimiza los tiempos de bloqueo en la tabla principal.

La mayoría del tiempo de espera ahora está todavía en la red IO. Parece que me encuentro con problemas en lo que respecta a VM. Se moverá a hardware físico en los próximos meses;)

+0

Demonios, ¿está realmente seguro de que almacenar esos datos en un DB relacional es la solución que realmente necesita? ¿No se pueden almacenar los datos al principio en algún tipo de archivos de registro, y cuando se van a analizar los datos, ejecutar algún tipo de proceso agregado para extraer solo información relevante a su base de datos? –

+0

Sí, pero me encantaría no hacerlo. Hay MUCHAS cosas pasando aquí, y también es un buen ejemplo de programación. Además, cuando extraigo registros para uso activo, debo procesar de 1 a 2 mil millones de filas lo más rápido posible del formato binario comprimido en datos relacionales. Simplemente tratando de llegar a los límites aquí. – TomTom

+0

Esto es especialmente cierto porque al final no veo realmente por qué no se inserta más rápido. Incluso un núcleo no se agotó, los discos no, y tengo E/S de red como condiciones de espera. No transfiero mucha información. Esto es algo en lo que no debería pensar ...;) y arreglarlo. – TomTom

Respuesta

0

¿Podría usar el particionamiento horizontal? Ver: http://msdn.microsoft.com/en-us/library/ms178148.aspx & http://msdn.microsoft.com/en-us/library/ms188706.aspx

También puede ser que desee mirar a esta pregunta, y posiblemente cambiar el modelo de recuperación: Sql Server 2008 Tuning with large transactions (700k+ rows/transaction)

Algunas preguntas: ¿Qué edición de SQL Server está utilizando?

¿Por qué el núcleo único está al 80%? Ese podría ser el cuello de botella, por lo que probablemente sea algo que valga la pena investigar.

¿Qué sistema operativo está utilizando, y es de 64 bits?

+0

Editando la pregunta en respuesta. Tenga en cuenta que el 80% del núcleo es un promedio tomado de la carga del sistema. La máquina tiene 4 núcleos y muestra un promedio de carga de 20% de la CPU. – TomTom

1

¿Hay algún índice en la tabla del que podría prescindir? EDITAR: pregunta mientras estabas escribiendo.

¿Es posible convertir el precio en un número entero, y luego dividir por 1000 o lo que sea en las consultas?

+0

En realidad, existe la posibilidad de hacer que el precio sea una cifra int y una minúscula para el tipo de codificación, eso es lo que se piensa. Como el precio no está indexado, ¿debería marcar una gran diferencia? Aunque lo probaré. – TomTom

+0

¿Dónde se está ejecutando el cliente .NET? – Tim

+0

Misma máquina, VM separada. Mudarse al hardware físico pronto, no por error, pero tengo un problema con el reloj "moviéndose" en una máquina virtual, lo cual es malo si los datos que se obtienen tienen incrementos de marca de tiempo de 25 ms y la biblioteca lo marca como "sospechoso" para la marca de tiempo fluctuante retrasos – TomTom

1

¿Ha intentado agregar un pk a la mesa? ¿Eso mejora la velocidad?

También existe una forma de utilizar tablas de conteo para importar datos de csv desde http://www.sqlservercentral.com/articles/T-SQL/62867/ (cerca de abajo, requiere registro gratuito pero vale la pena).

Puede intentarlo y probar su rendimiento ... con una pequeña tabla de recuento correctamente indexada.

+0

pk no añadido .... lo intentaré. csv es malo, no quiero que la aplicación pueda manipular el sistema de archivos del servidor. – TomTom

+2

Generalmente, la adición de cualquier forma de índice o clave primaria reducirá la velocidad de inserción. – andora

3

Si administra 70k filas por segundo, tiene mucha suerte hasta el momento. Pero sospecho que es porque tienes un esquema muy simple.

No puedo creer que preguntar acerca de este tipo de carga en

  • servidor virtual
  • sola matriz
  • discos SATA

La red y las CPU son compartidos, es IO restringido: no puede usar todos los recursos. Cualquier estadística de carga que veas no es muy útil. Sospecho que la carga de red que ve es el tráfico entre los 2 servidores virtuales y se convertirá en IO obligado si resuelve este

Antes de continuar, lea este 10 lessons from 35K tps. Él no estaba usando una caja virtual.

Esto es lo que haría, suponiendo que no hay SAN y sin capacidad de recuperación de desastres si desea aumentar los volúmenes.

  • Comprar 2 grandes servidores phyical, CPU RAM tipo de irreleveant, máx RAM, van x64 instalar
  • discos + = controladores de husillos de más rápido, más rápido SCSI. O una gran stonking NAS
  • 1000 MB + NIC
  • RAID 10 con 6-10 disco para uno archivo de registro para su base de datos única
  • restante de disco RAID 5 o RAID 10 para archivo de datos

Como referencia, nuestra carga pico es de 12 millones de filas por hora (16 núcleos, 16 GB, SAN, x64) pero tenemos complejidad en la carga. No estamos a capacidad.

+0

Lo sentimos, IO no tiene restricciones. Los discos sata de matriz única no son tan pequeños si los discos son de 10k de rapaces, solo hay 10 discos utilizados por el servidor sql Y los discos no están ocupados por la información del controlador RAID. ESTO ES un problema de red - los tiempos de espera señalan claramente a la red IO;) – TomTom

1

De las respuestas que leí aquí, parece que realmente tiene un problema de hardware en lugar de un problema con el código. Idealmente, obtendrá aumentos de rendimiento al ofrecer más E/S de disco o ancho de banda de red, o al ejecutar el programa en la misma máquina virtual que aloja la base de datos.

Sin embargo, quiero compartir la idea de que los insertos de parámetros de tabla son realmente ideales para la transferencia de big data; aunque SqlBulkCopy parece ser igual de rápido, es significativamente menos flexible.

escribí un artículo sobre el tema aquí: http://www.altdevblogaday.com/2012/05/16/sql-server-high-performance-inserts/

La respuesta general es que más o menos quiere crear un tipo de tabla:

CREATE TYPE item_drop_bulk_table_rev4 AS TABLE (
    item_id BIGINT, 
    monster_class_id INT, 
    zone_id INT, 
    xpos REAL, 
    ypos REAL, 
    kill_time datetime 
) 

A continuación, se crea un procedimiento almacenado para copiar de la parámetro de tabla en la tabla real directamente, por lo que hay menos pasos intermedios:

CREATE PROCEDURE insert_item_drops_rev4 
    @mytable item_drop_bulk_table_rev4 READONLY 
AS 

INSERT INTO item_drops_rev4 
    (item_id, monster_class_id, zone_id, xpos, ypos, kill_time) 
SELECT 
    item_id, monster_class_id, zone_id, xpos, ypos, kill_time 
FROM 
    @mytable 

el código de SQL Server se ve detrás de la siguiente manera:

DataTable dt = new DataTable(); 
dt.Columns.Add(new DataColumn("item_id", typeof(Int64))); 
dt.Columns.Add(new DataColumn("monster_class_id", typeof(int))); 
dt.Columns.Add(new DataColumn("zone_id", typeof(int))); 
dt.Columns.Add(new DataColumn("xpos", typeof(float))); 
dt.Columns.Add(new DataColumn("ypos", typeof(float))); 
dt.Columns.Add(new DataColumn("timestamp", typeof(DateTime))); 

for (int i = 0; i < MY_INSERT_SIZE; i++) { 
    dt.Rows.Add(new object[] { item_id, monster_class_id, zone_id, xpos, ypos, DateTime.Now }); 
} 

// Now we&#039;re going to do all the work with one connection! 
using (SqlConnection conn = new SqlConnection(my_connection_string)) { 
    conn.Open(); 
    using (SqlCommand cmd = new SqlCommand("insert_item_drops_rev4", conn)) { 
     cmd.CommandType = CommandType.StoredProcedure; 

     // Adding a "structured" parameter allows you to insert tons of data with low overhead 
     SqlParameter param = new SqlParameter("@mytable", SqlDbType.Structured); 
     param.Value = dt; 
     cmd.Parameters.Add(param); 
     cmd.ExecuteNonQuery(); 
    } 
} 
1

Todo es lento.

Hace algún tiempo resolvimos un problema similar (inserte en DB decenas de miles de datos de precios, ya que recuerdo que eran aproximadamente 50K por marco de tiempo, y teníamos unos 8 marcos temporales que chocaban a: 00, por lo que aproximadamente 400K registros) y funcionó muy rápido para nosotros (MS SQL 2005). Imagine cómo funcionará hoy (SQL 2012):

<...init...> 
if(bcp_init(m_hdbc, TableName, NULL, NULL, DB_IN) == FAIL) 
    return FALSE; 

int col_number = 1; 

// Bind columns 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.SymbolName, 0, 16, (LPCBYTE)"", 1, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Time, 0, 4, 0, 0, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Open, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.High, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Low, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Close, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE; 
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Volume, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE; 


<...save into sql...> 
BOOL CSymbolStorage::Copy(SQL_SYMBOL_DATA *sd) 
{ 
    if(!m_bUseDB) 
     return TRUE; 

    memcpy(&m_sd, sd, sizeof(SQL_SYMBOL_DATA)); 

    if(bcp_sendrow(m_hdbc) != SUCCEED) 
     return FALSE; 

    return TRUE; 
} 
Cuestiones relacionadas