背景
EntityFramework Core有許多新的特性,其中一個重要特性便是批量操作。
批量操作意味着不需要為每次Insert/Update/Delete操作發送單獨的命令,而是在一次SQL請求中發送批量組合指令。
EFCore批量操作實踐
批處理是期待已久的功能,社區多次提出要求。現在EFCore支持開箱即用確實很棒,可以提高應用程序的性能和速度。
P1 對比實踐
下面以常見的批量插入為例,使用SQL Server Profiler 觀察實際產生並執行的SQL語句。
還有一種關注EFCore產生的sql語句的方式,添加Nlog支持,關注Microsoft.EntityFrameworkCore.Database.Command 日誌
<logger name=”Microsoft.EntityFrameworkCore.Database.Command” minlevel=”Debug” writeTo=”sql” />
定義插入模型Category, 插入4個實體,這裏為什麼強調4,請留意下文。
public class Category { public int Id { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } } /*EFCore 查看模型屬性,有Id使用id作為主鍵, 沒有Id,搜索public "{TableName}Id"作為主鍵,默認為int形主鍵設置標記列自增; */ info: Microsoft.EntityFrameworkCore.Database.Command[20100] Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE [Categories] ( [Id] int NOT NULL IDENTITY, [CategoryID] int NOT NULL, [CategoryName] nvarchar(max) NULL, CONSTRAINT [PK_Categories] PRIMARY KEY ([Id]) );
using (var db = new BloggingContext())
{
db.Categories.Add(new Category() { CategoryID = 1, CategoryName = “Clothing” });
db.Categories.Add(new Category() { CategoryID = 2, CategoryName = “Footwear” });
db.Categories.Add(new Category() { CategoryID = 3, CategoryName = “Accessories” });
db.Categories.Add(new Category() { CategoryID = 4, CategoryName = “Accessories” });
db.SaveChanges();
}
當執行SaveChanges(), 日誌显示:
info: Microsoft.EntityFrameworkCore.Database.Command[20100] Executing DbCommand [Parameters=[@p0='1', @p1='Clothing' (Size = 4000), @p2='2', @p3='Footwear' (Size = 4000), @p4='3', @p5='Accessories' (Size = 4000), @p6='4', @p7='Accessories' (Size = 4000)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); MERGE [Categories] USING ( VALUES (@p0, @p1, 0), (@p2, @p3, 1), (@p4, @p5, 2), (@p6, @p7, 3)) AS i ([CategoryID], [CategoryName], _Position) ON 1=0 WHEN NOT MATCHED THEN INSERT ([CategoryID], [CategoryName]) VALUES (i.[CategoryID], i.[CategoryName]) OUTPUT INSERTED.[Id], i._Position INTO @inserted0; SELECT [t].[Id] FROM [Categories] t INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) ORDER BY [i].[_Position];
從SQL Profiler追溯到的SQL:
exec sp_executesql N’SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Categories] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1),
(@p4, @p5, 2),
(@p6, @p7, 3)) AS i ([CategoryID], [CategoryName], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([CategoryID], [CategoryName])
VALUES (i.[CategoryID], i.[CategoryName])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;
SELECT [t].[Id] FROM [Categories] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];
‘,N’@p0 int,@p1 nvarchar(4000),@p2 int,@p3 nvarchar(4000),@p4 int,@p5 nvarchar(4000),@p6 int,@p7 nvarchar(4000)’,@p0=1,@p1=N’Clothing’,@p2=2,@p3=N’Footwear’,@p4=3,@p5=N’Accessories’,@p6=4,@p7=N’Accessories’
如你所見,批量插入沒有產生4個獨立的語句,而是被組合為一個傳參存儲過程腳本(用列值作為參數);如果使用EF6執行相同的代碼,則在SQL Server Profiler中將看到4個獨立的插入語句 。
① 就性能和速度而言,EFCore批量插入更具優勢。 ② 若數據庫是針對雲部署,EF6運行這些查詢,還將產生額外的流量成本。
經過驗證:EFCore批量更新、批量刪除功能,EFCore均發出了使用sp_executesql存儲過程+批量參數構建的SQL腳本。
P2 深入分析
起關鍵作用的 sp_executesql存儲過程: 可以多次執行的語句或批處理 (可帶參)
-- Syntax for SQL Server, Azure SQL Database, Azure SQL Data Warehouse, Parallel Data Warehouse sp_executesql [ @stmt = ] statement [ { , [ @params = ] N'@parameter_name data_type [ OUT | OUTPUT ][ ,...n ]' } { , [ @param1 = ] 'value1' [ ,...n ] } ]
注意官方限制:
The amount of data that can be passed by using this method is limited by the number of parameters allowed. SQL Server procedures can have, at most, 2100 parameters. Server-side logic is required to assemble these individual values into a table variable or a temporary table for processing. // SQL存儲過程最多可使用2100個參數
P3 豁然開朗
SqlServer sp_executesql存儲過程最多支持2100個批量操作形成的列值參數,所以遇到很大數量的批量操作,EFCore SqlProvider會幫我們將批量操作分塊傳輸,
實際上EFCore 對於少於4個的批量命令,不會使用sp_executesql 存儲過程,我這邊自己根據官方驗證確實如此:
估摸着EFCore使用sp_executesql 也是有點耗資源的,對於小批量(小於4條的批量操作)依舊是產生單條sql。
// 同時EFCore開放了【配置關係型數據庫批量操作大小】
protected override void OnConfiguring(DbContextOptionsBuilder optionbuilder) { string sConnString = @"Server=localhost;Database=EFSampleDB;Trusted_Connection=true;"; optionbuilder.UseSqlServer(sConnString , b => b.MaxBatchSize(1)); // 批量操作的SQL語句數量,也可設定為1禁用批量插入 }
總結
① EFCore 相比EF6,已經支持批量操作,能有效提高應用程序的性能
② EFCore的批量操作能力,由對應的DataBaseProvider支撐(Provider實現過程跟背後的存儲載體密切相關)
– 對於小批量操作(當前EFCore默認MinBatchSize為4》),EFCore不會啟用sp_executesql
- 大批量關注存儲過程sp_executesql ,存儲過程的列值參數最多2100 個,這個關鍵因素決定了在大批量操作的時候 依舊會被分塊傳輸。
③ 另外一個批量操作的方法,這裏也點一下:構造Rawsql【EFCore支持Rawsql】。
sqlite不支持存儲過程,為完成批量插入提高性能,可採用此方案。
var insertStr = new StringBuilder(); insertStr.AppendLine("insert into ProfileUsageCounters (profileid,datetime,quota,usage,natureusage) values"); var txt = insertStr.AppendLine(string.Join(',', usgaeEntities.ToList().Select(x => { return $"({x.ProfileId},{x.DateTime},{x.Quota},{x.Usage},{x.NatureUsage})"; }).ToArray())); await _context.Database.ExecuteSqlCommandAsync(txt.ToString());
+
+
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※大陸寄台灣空運注意事項
※大陸海運台灣交貨時間多久?