在.NET并行处理中优化数据库插入

在.NET并行处理中优化数据库插入,.net,amazon-web-services,npoco,amazon-ses,.net,Amazon Web Services,Npoco,Amazon Ses,我必须使用AWS SES发送大量电子邮件(比如每项工作10000封)。关于如何并行执行,现在有一个关于如何将发送事务数据写入数据库的问题。我使用的是npoco表单和InsertBulk,粗略地看,它通过迭代每个poco打开一个连接并插入,然后关闭连接。除了每次发送的开始、写入和结束,这是一个进步。我的想法是尽量减少数据库的操作,但我应该每发送50封左右的电子邮件就向数据库写一封信,如果服务器或作业中断,作业可以在不发送副本的情况下继续进行,等等 所以我开始使用ConcurrentBag、线程锁定

我必须使用AWS SES发送大量电子邮件(比如每项工作10000封)。关于如何并行执行,现在有一个关于如何将发送事务数据写入数据库的问题。我使用的是npoco表单和InsertBulk,粗略地看,它通过迭代每个poco打开一个连接并插入,然后关闭连接。除了每次发送的开始、写入和结束,这是一个进步。我的想法是尽量减少数据库的操作,但我应该每发送50封左右的电子邮件就向数据库写一封信,如果服务器或作业中断,作业可以在不发送副本的情况下继续进行,等等

所以我开始使用ConcurrentBag、线程锁定、转换为列表、将列表发送给npoco进行插入等等。测试非常有限,它可以正常工作。但我确信这不是正确的方法,我也没有信心在这里正确地使用线程。在这种情况下有什么建议?将concurrentbag传递给npoco进行插入或其他插入方法是否更好或可行

 var bag = new ConcurrentBag<EmailSent>();
    Parallel.ForEach(recipients.AsParallel(), new ParallelOptions { MaxDegreeOfParallelism = maxParallelEmails },
          recipient =>
           {
         var response = client.SendEmail(request);
          bag.Add(new EmailSent() { JobId = jobId, MessageId = response.MessageId});
       }
    lock (syncRoot) 
        {
             count++;
             if (count % 50 == 0 || count == recipients.Count) 
              {
               var list = new List<EmailSent>();
                 while (!bag.IsEmpty)
                  {
                   EmailSent email;
                     if (bag.TryTake(out email))
                      {
                        list.Add(email);

                      }

               }
            repo.InsertBulk<EmailSent>(list);
       }
});
var-bag=新的ConcurrentBag();
Parallel.ForEach(recipients.aspallel(),新的ParallelOptions{MaxDegreeOfParallelism=maxParallelEmails},
收件人=>
{
var response=client.sendmail(请求);
添加(newemailsent(){JobId=JobId,MessageId=response.MessageId});
}
锁定(同步根)
{
计数++;
如果(计数%50==0 | |计数==recipients.count)
{
var list=新列表();
而(!bag.IsEmpty)
{
发送电子邮件;
如果(行李寄送(发送电子邮件))
{
列表。添加(电子邮件);
}
}
回购插入批量(列表);
}
});

如果您只是在寻找优化,一种是用于插入,这样您就可以向存储过程发送多条记录,而不是每次插入都调用一次

在SQL server上定义参数类型,这很像定义表。(大多数示例来自上面的链接。)

然后将该类型的参数添加到插入过程中:

CREATE PROCEDURE usp_UpdateCategories 
(@tvpNewCategories dbo.CategoryTableType READONLY)
在存储过程中,可以从该参数中进行选择,就像选择表变量一样

INSERT INTO dbo.Categories (CategoryID, CategoryName)
SELECT nc.CategoryID, nc.CategoryName FROM @tvpNewCategories AS nc;
这样做的好处是,您可以将所有插入作为一个操作来执行

在应用程序端,您将创建一个与您定义的表类型相对应的DataTable。然后用您要插入的记录填充该表

最后,在调用过程时,添加一个参数,将DataTable作为其值,指定
SqlDbType=SqlDbType.Structured
,并且“TypeName”是表类型的名称

SqlParameter tvpParam = insertCommand.Parameters.AddWithValue(
"@tvpNewCategories", yourDataTable);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";

如果您在2008年之前使用SQL server,您就会知道我们做了一些奇怪的事情来将多个记录传递给一个过程,比如连接字符串或发送和解析XML。这更容易,并且大大减少了单个存储过程调用的数量。

您也可以使用
ConcurrentQueue
而不是
ConcurrentBag
。您不必担心在添加到队列或取出项目时锁定任何内容。如果要以n个批次保存记录,只需持续
TryDequeue
并将出列项添加到集合中,直到集合计数为n或
TryDequeue
返回
false
,这意味着队列中什么都没有了。

如果您为每封电子邮件生成一个唯一的ID,这样它就不会在数据库中重复,并且还知道上次在哪里被删除了,该怎么办。。我确信在数据库中插入一条记录应该很快。所以每发送50封电子邮件插入一条记录应该在一个线程中很快完成。。
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue(
"@tvpNewCategories", yourDataTable);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";