Cómo cambiar inserciones lentas parametrizadas en copias masivas rápidas (incluso desde la memoria)

StackOverflow https://stackoverflow.com/questions/127152

Pregunta

Tenía algo como esto en mi código (.Net 2.0, MS SQL)

SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
Catalog=DataBase;Integrated Security=True");
  connection.Open();

  SqlCommand cmdInsert = connection.CreateCommand();
  SqlTransaction sqlTran = connection.BeginTransaction();
  cmdInsert.Transaction = sqlTran;

  cmdInsert.CommandText =
     @"INSERT INTO MyDestinationTable" +
      "(Year, Month, Day, Hour,  ...) " +
      "VALUES " +
      "(@Year, @Month, @Day, @Hour, ...) ";

  cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
  cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
  cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
  // more fields here
  cmdInsert.Prepare();

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] {' '};
  String[] records;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
    cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
    cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
    // more here complicated stuff here
    cmdInsert.ExecuteNonQuery()
  }
  sqlTran.Commit();
  connection.Close();

Con cmdInsert.ExecuteNonQuery() comentado este código se ejecuta en menos de 2 segundos.Con la ejecución de SQL se tarda 1m 20 seg.Hay alrededor de 0,5 millones de registros.La mesa se vacía antes.La tarea de flujo de datos SSIS de funcionalidad similar tarda alrededor de 20 segundos.

  • Inserción masiva era no es una opción (ver más abajo).Hice algunas cosas elegantes durante esta importación.
  • Mi máquina de prueba es Core 2 Duo con 2 GB de RAM.
  • Al mirar en el Administrador de tareas, la CPU no estaba completamente aprovechada.La OI tampoco parecía utilizarse plenamente.
  • El esquema es muy simple:una tabla con AutoInt como índice principal y menos de 10 entradas, entradas pequeñas y caracteres (10).

Después de algunas respuestas aquí descubrí que es posible ejecutar copia masiva desde la memoria!Me negaba a utilizar una copia masiva porque pensé que debía hacerse desde un archivo...

Ahora uso esto y me lleva unos 20 segundos (como la tarea SSIS)

  DataTable dataTable = new DataTable();

  dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
  dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
 // ... and more to go

  DataRow dataRow;
  object[] objectRow = new object[dataTable.Columns.Count];

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] { ' ' };
  String[] records;
  int recordCount = 0;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    dataRow = dataTable.NewRow();
    objectRow[0] = null; 
    objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
    objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
    objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
    // my fancy stuf goes here

    dataRow.ItemArray = objectRow;         
    dataTable.Rows.Add(dataRow);

    recordCount++;
  }

  SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
  bulkTask.DestinationTableName = "MyDestinationTable"; 
  bulkTask.BatchSize = dataTable.Rows.Count;
  bulkTask.WriteToServer(dataTable);
  bulkTask.Close();
¿Fue útil?

Solución

En lugar de insertar cada registro individualmente, intente utilizar el Copia masiva Sql clase para insertar de forma masiva todos los registros a la vez.

Cree una DataTable y agregue todos sus registros a la DataTable, y luego use Copia masiva Sql.Escribir al servidor para insertar de forma masiva todos los datos a la vez.

Otros consejos

¿Se requiere la transacción?El uso de transacciones necesita muchos más recursos que comandos simples.

Además, si está seguro de que los valores insertados son correctos, puede utilizar BulkInsert.

1 minuto suena bastante razonable para 0,5 millones de registros.Eso es un récord cada 0,00012 segundos.

¿La tabla tiene algún índice?Quitarlos y volver a aplicarlos después de la inserción masiva mejoraría el rendimiento de las inserciones, si esa es una opción.

No me parece descabellado procesar 8.333 registros por segundo... ¿qué tipo de rendimiento espera?

Si necesita una mayor velocidad, podría considerar implementar una inserción masiva:

http://msdn.microsoft.com/en-us/library/ms188365.aspx

Si alguna forma de inserción masiva no es una opción, la otra forma serían varios subprocesos, cada uno con su propia conexión a la base de datos.

El problema con el sistema actual es que tiene 500.000 viajes de ida y vuelta a la base de datos y está esperando a que se complete el primer viaje de ida y vuelta antes de comenzar el siguiente; cualquier tipo de latencia (es decir, una red entre las máquinas) significará que la mayoría de Tu tiempo lo pasas esperando.

Si puede dividir el trabajo, tal vez utilizando alguna forma de configuración de productor/consumidor, es posible que pueda aprovechar mucho más todos los recursos.

Sin embargo, para hacer esto tendrás que perder la gran transacción; de lo contrario, el primer hilo del escritor bloqueará todos los demás hasta que se complete la transacción.Aún puedes usar transacciones, pero tendrás que usar muchas pequeñas en lugar de una grande.

El SSIS será rápido porque utiliza el método de inserción masiva: primero haga todo el procesamiento complicado, genere la lista final de datos para insertar y entréguelos todos al mismo tiempo para la inserción masiva.

Supongo que lo que lleva aproximadamente 58 segundos es la inserción física de 500.000 registros, por lo que se obtienen alrededor de 10.000 inserciones por segundo.Sin conocer las especificaciones de su máquina servidor de base de datos (veo que está usando localhost, por lo que los retrasos en la red no deberían ser un problema), es difícil decir si esto es bueno, malo o abismal.

Yo miraría el esquema de su base de datos: ¿hay varios índices en la tabla que deben actualizarse después de cada inserción?Esto podría provenir de otras tablas con claves externas que hagan referencia a la tabla en la que está trabajando.Hay herramientas de creación de perfiles SQL y funciones de supervisión del rendimiento integradas en SQL Server, pero nunca las he usado.Pero pueden mostrar problemas como cerraduras y cosas así.

Primero haga las cosas elegantes con los datos, en todos los registros.Luego, insértelos en masa.

(ya que no estás haciendo selecciones después de una inserción...No veo el problema de aplicar todas las operaciones en los datos antes del BulkInsert

Si tuviera que adivinar, lo primero que buscaría son demasiados índices o el tipo incorrecto en la tabla tbTrafficLogTTL.Sin mirar la definición del esquema de la tabla, realmente no puedo decirlo, pero he experimentado problemas de rendimiento similares cuando:

  1. La clave principal es un GUID y el índice principal es CLUSTERED.
  2. Hay una especie de índice ÚNICO en un conjunto de campos.
  3. Hay demasiados índices sobre la mesa.

Cuando comienzas a indexar medio millón de filas de datos, el tiempo dedicado a crear y mantener índices se suma.

También señalaré que si tiene alguna opción para convertir los campos Año, Mes, Día, Hora, Minuto, Segundo en un único campo de fecha y hora2 o marca de tiempo, debería hacerlo.Está agregando mucha complejidad a su arquitectura de datos, sin ningún beneficio.La única razón por la que consideraría el uso de una estructura de campo dividido como esa es si se trata de un esquema de base de datos preexistente que no se puede cambiar por ningún motivo.En cuyo caso, apesta ser tú.

Tuve un problema similar en mi último contrato.Estás haciendo 500.000 viajes a SQL para insertar tus datos.Para obtener un aumento espectacular del rendimiento, desea investigar el método BulkInsert en el espacio de nombres SQL.Tuve procesos de "recarga" que pasaron de más de 2 horas para restaurar un par de docenas de tablas a 31 segundos una vez que implementé la importación masiva.

La mejor manera de lograr esto es utilizando algo como el comando bcp.Si no está disponible, las sugerencias anteriores sobre el uso de BULK INSERT son su mejor opción.Está realizando 500.000 viajes de ida y vuelta a la base de datos y escribiendo 500.000 entradas en los archivos de registro, sin mencionar el espacio que debe asignarse al archivo de registro, la tabla y los índices.

Si está insertando en un orden diferente al de su índice agrupado, también tendrá que lidiar con el tiempo necesario para reorganizar los datos físicos en el disco.Aquí hay muchas variables que podrían hacer que su consulta se ejecute más lentamente de lo que le gustaría.

~10,000 transacciones por segundo no son terribles para las inserciones individuales que vienen de ida y vuelta desde el código/

BULK INSERT = bcp de un permiso

Puede lotar los insertos para reducir las tripas redondas sqldataadaptor.updateBatchSize = 10000 proporciona 50 viajes redondos

Aunque todavía tienes inserciones de 500k...

Artículo

MSDN

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top