[netCore] how to improvement performance of Bulk in Dapper

Dapper雖然有提供Delete、Update、Insert批次處理,但我個人卻很少直接呼叫該函式,

因為這不是我所想要的資料批次處理效能

星期五,和同事討論到Dapper相關Bulk函式,我以前用Dapper經驗告訴同事,

Bulk函式,只是依照IList數量,幫你產生對應insert ,delete,update sql,

另外,依照SQL Server WAL行為,如果client沒有傳入transaction參數,

我們的datahelper建議自行new transaction包起來,一是效能考量,

二是資料一致和正確性(假設你預計結果應該會有10筆資料,但執行結束後結果卻只有8筆),

下面我實際驗證Dapper所提供Bulk效能和行為。

 

我的webapi code很簡單

[HttpGet("BulkTest/{max}")]
       public async Task<string> BulkTest(int max)
       {
           var datas = new List<EventLogModule>();
           var dt = new DataTable();
           ProfilingSession.Current.AddTag("BulkTest");
           using (ProfilingSession.Current.Step(() => "Handle Request - /"))
           {
               using (ProfilingSession.Current.Step(() => "generate datas"))
               {
                   datas = generate(max);
               }
               using (ProfilingSession.Current.Step(() => "Convert datatable"))
               {
                   dt = datas.ToDataTable(new string[] { "EventID", "LogLevel", "Message", "Exception" });
               }
               using (ProfilingSession.Current.Step(() => "bulk insert to sql server"))
               {
                   await _eventLogRepository.BulkInsertAsync(datas);
 
                   /**
                   var mytvp = new DynamicParameters();
                   mytvp.AddDynamicParams(new { DataList = dt.AsTableValuedParameter("UT_EventLog") });
                   await _eventLogRepository.ExecuteSPAsync("dbo.usp_InsertEventLogWithTvp", mytvp);
                   **/
 
               }
               return await Task.FromResult($"number of record:{datas.Count}");
           }
       }

 

 

Dapper預設BulkInsert新增100筆且未包交易

sql profiler

從sql profiler看的出來,如同我之前所說的,

dapper就只是傳了100句sql給sql server(ORM為了要支援多種資料庫,這樣的行為是可以理解的),

但交易效能其實相當低落。

CoreProfiler上看BulkInsert執行時間:2470 ms

 

Dapper預設BulkInsert新增100筆但自行包交易

sql profiler

Dapper雖然還是傳了100句sql給SQL Server,

但因為自行包交易,所以所有transaction ID都相同,

這樣才能達到資料同生共死效果,但交易效能改善有限。

 

CoreProfiler上看BulkInsert執行時間:2419 ms (從2470減少為2419)

 

如果要讓SQL Server交易效能飛快,基本上也要避免Dapper進行row by row產生相對應SQL,

因為每句SQL都會進行TDS轉換,這些轉換成本你絕對不能輕忽,

下面我改用TVP方式來改善批次處理效能,

達到我真正想要的批次處理。

Note:ado.net版本可參考我以前文章[C#][SQL SERVER] 提高 Insert 效能

 

我預先在SQL Server建立 User Table Type和處理Insert的SP,如下

create type dbo.UT_EventLog as table
(
	[EventID] [int] NOT NULL primary key nonclustered,
	[LogLevel] [varchar](20) NOT NULL,
	[Message] [nvarchar](2000) NULL,
	[Exception] [nvarchar](2000) NULL
)
with(memory_optimized=on)

CREATE PROC dbo.usp_InsertEventLogWithTvp
@DataList UT_EventLog READONLY
AS
begin
set nocount on;
begin tran
insert into [dbo].[EventLog]([EventID],[LogLevel],[Message],[Exception])
select [EventID],[LogLevel],[Message],[Exception] from @DataList ;
commit 
end

 

 

controller我就改用tvp傳入(避免row by row產生sql)

var mytvp = new DynamicParameters();
                  mytvp.AddDynamicParams(new { DataList = dt.AsTableValuedParameter("UT_EventLog") });
                  await _eventLogRepository.ExecuteSPAsync("dbo.usp_InsertEventLogWithTvp", mytvp);

 

webapi同樣是新增100筆資料

sql profiler

CoreProfiler上看BulkInsert執行時間:150 ms (從2470減少為150,交易效能提高約16倍)

 

現在我改用1000筆,讓大家更有感覺一點

Dapper預設BulkInsert新增1000筆且未包交易

Sql profiler

CoreProfiler上看BulkInsert執行時間:22467 ms

 

Dapper和TVP新增1000筆資料且包交易

Note: netcore1.x 就有支援TVP

 

Sql profiler

Sql profiler再也看不到1000句sql和1000條各自獨立交易。

 

CoreProfiler上看BulkInsert執行時間:252 ms(從22467減少為252,交易效能提高約89倍)

 

結果

1000筆要花252 ms老實說不太滿意,我還可以讓交易效能更快,因為我們公司用的是SQL Server。​

 

參考

[C#][SQL SERVER] 提高 Insert 效能

Using Table Valued Parameters in Dapper

SQL Server Specific Features

https://github.com/StackExchange/Dapper/issues/780

Dapper With .Net Core

TVP HELPER FOR DAPPER ORM

SQL Server Dapper

Does Dapper support SQL 2008 Table-Valued Parameters?

Passing Table Valued Parameters with Dapper