C#mĂșltiples insertos paralelos en la base de datos
multithreading ado.net (4)
Puede usar SqlBulkCopy
. Vea el código de muestra a continuación. El método WriteToServer
, escribe The datatable
en la base de datos, siempre que sean de la misma asignación
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(ConSQL)) {
if (ConSQL.State == ConnectionState.Closed) {
ConSQL.Open();
}
bulkCopy.ColumnMappings.Add(0, 0);
bulkCopy.ColumnMappings.Add(1, 1);
bulkCopy.ColumnMappings.Add(2, 2);
bulkCopy.DestinationTableName = "dbo.TableName";
bulkCopy.WriteToServer(dataTable);
bulkCopy.Close(); //redundant - since using will dispose the object
}
Tengo una tabla de datos con alrededor de 3000 filas. Cada una de esas filas debe insertarse en una tabla de base de datos. Actualmente, estoy ejecutando un bucle foreach como debajo:
obj_AseCommand.CommandText = sql_proc;
obj_AseCommand.CommandType = CommandType.StoredProcedure;
obj_AseCommand.Connection = db_Conn;
obj_AseCommand.Connection.Open();
foreach (DataRow dr in dt.Rows)
{
obj_AseCommand.Parameters.AddWithValue("@a", dr["a"]);
obj_AseCommand.Parameters.AddWithValue("@b", dr["b"]);
obj_AseCommand.Parameters.AddWithValue("@c", dr["c"]);
obj_AseCommand.ExecuteNonQuery();
obj_AseCommand.Parameters.Clear();
}
obj_AseCommand.Connection.Close();
¿Puede aconsejar cómo puedo ejecutar paralelamente el SP en la base de datos ya que el enfoque anterior toma alrededor de 10 minutos para insertar 3000 filas?
Puedes usar SqlBulkCopy
guía está aquí
es mejor pasar toda la tabla de datos a la base de datos
obj_AseCommand.CommandText = sql_proc;
obj_AseCommand.CommandType = CommandType.StoredProcedure;
obj_AseCommand.Connection = db_Conn;
obj_AseCommand.Connection.Open();
obj_AseCommand.Parameters.AddWithValue("@Parametername",DataTable);
obj_AseCommand.ExecuteNonQuery();
en la base de datos debe crear el tipo de tabla que coincida exactamente con su tabla de datos
CREATE TYPE EmpType AS TABLE
(
ID INT, Name VARCHAR(3000), Address VARCHAR(8000), Operation SMALLINT //your columns
)
en el procedimiento de la tienda puede hacer algo como esto ...
create PROCEDURE demo
@Details EmpType READONLY // it must be read only
AS
BEGIN
insert into yourtable //insert data
select * from @Details
END
Nota al margen: 10 minutos para 3000 filas es excesivo incluso con una tabla ancha y un solo hilo. ¿Qué hace tu proceso? Supuse que el procesamiento no es trivial, de ahí la necesidad del SPROC, pero si solo está haciendo inserciones simples, según el comentario de @ 3dd, SqlBulkCopy producirá insertos de ~ 1M filas por minuto en una tabla razonablemente estrecha.
Puede hacer esto en paralelo usando TPL, por ejemplo, específicamente con la sobrecarga localInit
de Parallel.ForEach :
Parallel.ForEach(dt.Rows,
() =>
{
var con = new SqlConnection();
var cmd = con.CreateCommand();
cmd.CommandText = sql_proc;
cmd.CommandType = CommandType.StoredProcedure;
con.Open();
cmd.Parameters.Add(new SqlParameter("@a", SqlDbType.Int));
// NB : Size sensitive parameters must have size
cmd.Parameters.Add(new SqlParameter("@b", SqlDbType.VarChar, 100));
cmd.Parameters.Add(new SqlParameter("@c", SqlDbType.Bit));
// Prepare won''t help with SPROCs but can improve plan caching for adhoc sql
// cmd.Prepare();
return new {Conn = con, Cmd = cmd};
},
(dr, pls, localInit) =>
{
localInit.Cmd.Parameters["@a"] = dr["a"];
localInit.Cmd.Parameters["@b"] = dr["b"];
localInit.Cmd.Parameters["@c"] = dr["c"];
localInit.Cmd.ExecuteNonQuery();
return localInit;
},
(localInit) =>
{
localInit.Cmd.Dispose();
localInit.Conn.Dispose();
});
Notas:
- A menos que realmente sepa lo que está haciendo, en general deberíamos dejar TPL para decidir el grado de paralelismo. Sin embargo, dependiendo de la cantidad de contención (léase: bloqueos para el trabajo de la base de datos) de los recursos, puede ser necesario restringir el límite superior de las tareas concurrentes (la prueba y el error pueden ser útiles, por ejemplo, intente con concurrencias de 4, 8, 16 tareas concurrentes, etc. ver cuál proporciona la mayor producción y controlar el bloqueo y la carga de la CPU en su servidor Sql.
- Del mismo modo, dejar el particionador predeterminado de TPL suele ser lo suficientemente bueno como para dividir las DataRows entre las tareas.
- Cada tarea necesitará su propia conexión Sql.
- En lugar de crear y eliminar el comando en cada llamada, créelo una vez por tarea y luego siga reutilizando el mismo Comando, solo actualice los parámetros cada vez.
- Utilice LocalInit / Local Finally lambdas para hacer por tarea de configuración y limpieza, como la eliminación de comandos y conexiones.
- También podría considerar usar
.Prepare()
si está utilizando las versiones AdHoc Sql o Sql anteriores a 2005. - Supongo que enumerar
DataTable''s
filasDataTable''s
es seguro para subprocesos. Querrá verificar esto por supuesto, por supuesto.