数据库中的C#多个并行插入

时间:2015-01-05 06:10:17

标签: c# multithreading ado.net sqlcommand

我有一个大约3000行的数据表。每个行都需要插入数据库表中。目前,我正在运行一个foreach循环,如下所示:

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();

您能告诉我如何在数据库中并行执行SP,因为上述方法需要大约10分钟才能插入3000行。

4 个答案:

答案 0 :(得分:9)

修改

事后看来,使用Parallel.ForEach来并行化数据库插入是有点浪费的,因为它也会为每个连接消耗一个线程。可以说,更好的并行解决方案是使用System.Data Db操作的异步版本,例如ExecuteNonQueryAsync ,启动执行(并发),然后使用await Task.WhenAll()等待完成 - 这将避免调用者的线程开销,尽管整体Db性能可能不会更快。 More here

原始答案,多个并行插入数据库

您可以使用TPL并行执行此操作,例如:特别是localInit超载Parallel.ForEach。您几乎肯定希望通过调整MaxDegreeOfParalelism来限制并行数量,以免您淹没数据库:

Parallel.ForEach(dt.Rows,
    // Adjust this for optimum throughput vs minimal impact to your other DB users
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    () =>
    {
        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();
    });

注意:

  • 除非你真的知道自己在做什么,否则我们应该让TPL决定并行度。但是,根据资源的争用(读取:数据库工作的锁定),可能需要限制并发任务的上限(试验和错误可能很有用,例如尝试使用4,8,16个并发任务的并发等)查看哪个提供了大多数吞吐量,并监视Sql Server上的锁定和CPU负载。
  • 同样,保留TPL的默认分区程序通常足以在任务中对DataRows进行分区。
  • 每个任务都需要自己独立的Sql连接。
  • 不是在每个调用上创建和处理命令,而是每个任务创建一次,然后继续重用相同的命令,每次只更新参数。
  • 使用LocalInit / Local最后一个lambdas来完成每个任务的设置和清理,比如Disposing commands and connections。
  • 如果您使用的是AdHoc Sql或.Prepare()
  • ,您还可以考虑使用Sql versions prior to 2005
  • 我假设枚举DataTable's行是线程安全的。当然,你要仔细检查一下。

旁注:

即使使用宽表和单线程,3000行的10分钟也是过多的。你的过程做了什么?我假设处理不是微不足道的,因此需要SPROC,但是如果你只是做简单的插入,按照@ 3dd的评论,SqlBulkCopy将合理地每分钟插入大约1M行狭窄的桌子。

答案 1 :(得分:4)

最好将整个数据表传递到数据库

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();

在数据库中,您必须创建与您的数据表完全匹配的表类型

CREATE TYPE EmpType AS TABLE 
(
    ID INT, Name VARCHAR(3000), Address VARCHAR(8000), Operation SMALLINT //your columns
)

在商店程序中你可以做这样的事情......

create PROCEDURE demo

@Details EmpType READONLY // it must be read only
AS
BEGIN
    insert into yourtable   //insert data
    select * from @Details 
    END

答案 2 :(得分:3)

您可以使用SqlBulkCopy。请参阅下面的示例代码WriteToServer方法将datatable写入数据库,前提是它们具有相同的映射

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

}

答案 3 :(得分:0)

您可以使用SqlBulkCopy

指南是here