SQL Server 2012中快速插入批量数据的示例及疑惑

SQL Server 2008中SQL应用系列--目录索引

 

今天在做一个案例演示时,在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  
View Code

 

示例表,该表只有四个字段。

 

 
  1. 技术分享
    /*  
    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  
    View Code

     

现在展示批量插入10000条数据到该表中,语句如下:

 

 


 
  1. 技术分享
    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  
    View Code

     

 

该插入语句在SQL Server 2008 r2版本和SQL Server 2012版本中,测试结果如下:

技术分享

技术分享

技术分享

 

 

令我惊讶的是,SQL Server 2012居然耗时达到5分多钟,而SQL Server 2008R2版,只需要大约6秒钟。更令人费解的是:查询的I/O统计和elapsed time,在这两个版本中几乎一样。对此异象,我只能理解为每次Insert时的毫秒级精度可能不足以度量该次操作带来的细小差距,然而累积起来就非常可观了。

解决方案一:使用 Set NoCount On,效果立竿见影

  

  1. 技术分享
    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  
    View Code

     

 

技术分享

Set NoCount On(http://msdn.microsoft.com/zh-cn/library/ms189837.aspx)的作用:使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息。这在批量插入时将显著提高性能。至于 本例中,为什么SQL Server 2008 R2版中却不受该开关影响,希望知道的同学不吝赐教,非常感谢。

 

改进解决方案二:使用 Set NoCount On+Transaction

 
  1. 技术分享
    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  
    View Code

     

技术分享

解决方案三:使用递归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(0000as 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  
View Code

 

技术分享

技术分享

 

小结:SQL Server 2012中批量插入数据时,请记得Set NoCount ON,并尽可能加上Transaction,当然,推荐使用CTE,这可能会带来性能上的巨大提升。

 

 

邀月补充:

后来与微软亚太工程师多次沟通,得出初步结论:

在不打开“set nocount on”时,SSMS 2012与SSMS 2008r2版本的UI在执行效率上可能有极大差异,而与SQL Server引擎没有明显相关。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。