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

Har*_*rsh 5 c# ado.net multithreading 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();
Run Code Online (Sandbox Code Playgroud)

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

Stu*_*tLC 9

编辑

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

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

您可以使用TPL并行执行此操作,例如,特别是与Parallel.ForEachlocalInit重载.您几乎肯定希望通过调整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();
    });
Run Code Online (Sandbox Code Playgroud)

笔记:

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

边注:

即使使用宽桌和单线,3000行的10分钟也是过多的.你的过程做了什么?我假设处理不是微不足道的,因此需要SPROC,但是如果你只是做简单的插入,按照@ 3dd的注释,SqlBulkCopy将在一个相当窄的表上产生每分钟大约1M行的插入.


Dha*_*val 5

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

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();
Run Code Online (Sandbox Code Playgroud)

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

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

在存储过程中,你可以做这样的事情......

create PROCEDURE demo

@Details EmpType READONLY // it must be read only
AS
BEGIN
    insert into yourtable   //insert data
    select * from @Details 
    END
Run Code Online (Sandbox Code Playgroud)