今天在做一個案例演示時,在SQL Server 2012中使用Insert語句插入1萬條數據,結果遇到了一個奇怪的現象,現將過程分享出來,以供有興趣的同學參考。
附:我的測試環境為:
SQL Server 2012,命名實例
Microsoft SQL Server 2012 - 11.0.2100.60 (Intel X86)
Feb 10 2012 19:13:17
Copyright (c) Microsoft Corporation
Enterprise Edition on Windows NT 6.0 <X86> (Build 6002: Service Pack 2)
創建示例數據庫
IF OBJECT_ID('DemoPager2012') IS NOT NULL DROP DataBase DemoPager2012 GO CREATE Database DemoPager2012 GO USE DemoPager2012 GO
示例表,該表只有四個字段。
/* Setup script to create the sample table and fill it with sample data. */ IF OBJECT_ID('Customers','U') IS NOT NULL DROP TABLE Customers CREATE TABLE Customers ( CustomerID INT primary key identity(1,1), CustomerNumber CHAR(4), CustomerName VARCHAR(50), CustomerCity VARCHAR(20) ) GO
現在展示批量插入10000條數據到該表中,語句如下:
TRUNCATE table Customers GO ----清除干擾查詢 DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE SET STATISTICS IO ON; SET STATISTICS TIME ON; GO DECLARE @d Datetime SET @d=getdate(); declare @i int=1 while @i<=10000 begin INSERT INTO Customers (CustomerNumber, CustomerName, CustomerCity) SELECT REPLACE(STR(@i, 4), ' ', '0'),'Customer ' + STR(@i,6), CHAR(65 + (@i % 26)) + '-City' set @i=@i+1 end select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate()) SET STATISTICS IO OFF ; SET STATISTICS TIME OFF; GO
該插入語句在SQL Server 2008 r2版本和SQL Server 2012版本中,測試結果如下:
令我驚訝的是,SQL Server 2012居然耗時達到5分多鍾,而SQL Server 2008R2版,只需要大約6秒鍾。更令人費解的是:查詢的I/O統計和elapsed time,在這兩個版本中幾乎一樣。對此異象,我只能理解為每次Insert時的毫秒級精度可能不足以度量該次操作帶來的細小差距,然而累積起來就非常可觀了。
解決方案一:使用 Set NoCount On,效果立竿見影
TRUNCATE table Customers GO ----清除干擾查詢 DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE SET STATISTICS IO ON; SET STATISTICS TIME ON; GO DECLARE @d Datetime SET @d=getdate(); set nocount on declare @i int=1 while @i<=10000 begin INSERT INTO Customers (CustomerNumber, CustomerName, CustomerCity) SELECT REPLACE(STR(@i, 4), ' ', '0'),'Customer ' + STR(@i,6), CHAR(65 + (@i % 26)) + '-City' set @i=@i+1 end select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate()) SET STATISTICS IO OFF ; SET STATISTICS TIME OFF; GO
Set NoCount On(http://msdn.microsoft.com/zh-cn/library/ms189837.aspx)的作用:使返回的結果中不包含有關受 Transact-SQL 語句影響的行數的信息。這在批量插入時將顯著提高性能。至於 本例中,為什么SQL Server 2008 R2版中卻不受該開關影響,希望知道的同學不吝賜教,非常感謝。
改進解決方案二:使用 Set NoCount On+Transaction
TRUNCATE table Customers GO ----清除干擾查詢 DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE SET STATISTICS IO ON; SET STATISTICS TIME ON; GO DECLARE @d Datetime SET @d=getdate(); set nocount on declare @i int=1 BEGIN TRANSACTION while @i<=10000 begin INSERT INTO Customers (CustomerNumber, CustomerName, CustomerCity) SELECT REPLACE(STR(@i, 4), ' ', '0'),'Customer ' + STR(@i,6), CHAR(65 + (@i % 26)) + '-City' set @i=@i+1 end COMMIT select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate()) SET STATISTICS IO OFF ; SET STATISTICS TIME OFF; GO
解決方案三:使用遞歸CTE插入
TRUNCATE table Customers GO DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE SET STATISTICS IO ON; SET STATISTICS TIME ON; GO DECLARE @d Datetime SET @d=getdate(); /*****運用CTE遞歸插入,速度較快,邀月注***********************/ WITH Seq (num,CustomerNumber, CustomerName, CustomerCity) AS (SELECT 1,cast('0000'as CHAR(4)),cast('Customer 0' AS NVARCHAR(50)),cast('X-City' as NVARCHAR(20)) UNION ALL SELECT num + 1,Cast(REPLACE(STR(num, 4), ' ', '0') AS CHAR(4)), cast('Customer ' + STR(num,6) AS NVARCHAR(50)), cast(CHAR(65 + (num % 26)) + '-City' AS NVARCHAR(20)) FROM Seq WHERE num <= 10000 ) INSERT INTO Customers (CustomerNumber, CustomerName, CustomerCity) SELECT CustomerNumber, CustomerName, CustomerCity FROM Seq OPTION (MAXRECURSION 0) select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate()) SET STATISTICS IO OFF ; SET STATISTICS TIME OFF; GO
小結:SQL Server 2012中批量插入數據時,請記得Set NoCount ON,並盡可能加上Transaction,當然,推薦使用CTE,這可能會帶來性能上的巨大提升。
邀月補充:
后來與微軟亞太工程師多次溝通,得出初步結論:
在不打開“set nocount on”時,SSMS 2012與SSMS 2008r2版本的UI在執行效率上可能有極大差異,而與SQL Server引擎沒有明顯相關。