SQL Server-聚焦sp_executesql執行動態SQL查詢性能真的比exec好?


前言

之前我們已經討論過動態SQL查詢呢?這里為何再來探討一番呢?因為其中還是存在一定問題,如標題所言,很多面試題也好或者有些博客也好都在說在執行動態SQL查詢時sp_executesql的性能比exec好,但是事實真是如此?下面我們來一探究竟。

探討sp_executesql和exec執行動態SQL查詢性能

 首先我們創建如下測試表。

CREATE TABLE dbo.TestDynamicSQL
    (
      Col1 INT PRIMARY KEY ,
      Col2 SMALLINT NOT NULL ,
      CreatedTime DATETIME DEFAULT GETDATE() ,
      OtherValue CHAR(10) DEFAULT 'Jeffcky'
    )
GO

接着再來插入數據,如下:

INSERT  dbo.TestDynamicSQL
        ( Col1,
          Col2
        )
        SELECT  number + 1 ,
                number
        FROM    master..spt_values
        WHERE   type = 'P'
        ORDER BY number

最終查詢為如下測試數據:

接下來我們執行如下兩個SQL查詢語句,執行4次。

SELECT  *
FROM    dbo.TestDynamicSQL
WHERE   Col2 = 3
        AND Col1 = 4
GO
 
SELECT  *
FROM    dbo.TestDynamicSQL
WHERE   Col2 = 4
        AND Col1 = 5
GO

緊接着我們通過如下SQL語句來查詢緩存計划。

SELECT  q.text ,
        cp.usecounts ,
        cp.objtype ,
        p.* ,
        q.* ,
        cp.plan_handle
FROM    sys.dm_exec_cached_plans cp
        CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) p
        CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS q
WHERE   cp.cacheobjtype = 'Compiled Plan'
        AND q.text LIKE '%dbo.TestDynamicSQL%'
        AND q.text NOT LIKE '%sys.dm_exec_cached_plans %'

由上圖可知,我們看到存在兩個查詢計划且每個執行了4次,也就是說每一次查詢都會重新生成一個新的計划。清除查詢計划緩存,通過如下命令:

DBCC FREEPROCCACHE

我們繼續往下走,我們接下來通過EXEC來執行動態SQL查詢,如下,執行查詢完畢后再來看看查詢計划次數:

DECLARE @Col2 SMALLINT
DECLARE @Col1 INT
 
SELECT  @Col2 = 11 ,
        @Col1 = 12
 
DECLARE @SQL VARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = ' + CONVERT(VARCHAR(10), @Col2) + '
and Col1 = ' + CONVERT(VARCHAR(10), @Col1)
 
EXEC (@SQL)
GO
 
DECLARE @Col2 SMALLINT
DECLARE @Col1 INT
 
SELECT  @Col2 = 12 ,
        @Col1 = 13
 
DECLARE @SQL VARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = ' + CONVERT(VARCHAR(10), @Col2) + '
and Col1 = ' + CONVERT(VARCHAR(10), @Col1)
 
EXEC (@SQL)
GO

這個就不做過多解釋,我們依然要清除查詢計划緩存,我們再利用sp_executesql來查詢,如下:

DECLARE @Col2 SMALLINT
DECLARE @Col1 INT
 
SELECT  @Col2 = 23 ,
        @Col1 = 24
 
DECLARE @SQL NVARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = ' + CONVERT(VARCHAR(10), @Col2) + '
and Col1 = ' + CONVERT(VARCHAR(10), @Col1)
 
EXEC sp_executesql @SQL
Go
 
 
DECLARE @Col2 SMALLINT
DECLARE @Col1 INT
 
SELECT  @Col2 = 22 ,
        @Col1 = 23
 
DECLARE @SQL NVARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = ' + CONVERT(VARCHAR(10), @Col2) + '
and Col1 = ' + CONVERT(VARCHAR(10), @Col1)
 
EXEC sp_executesql @SQL
GO

對比exec執行動態SQL查詢得到的結果是一模一樣,正如我所演示的,我們有兩個計划,每個執行次數為4。不是說sp_executesql執行動態SQL查詢會重用計划緩存么,這是因為我們沒有正確使用sp_executesql所以導致SQL引擎無法重用計划。

當參數值改變為語句是唯一變化時,可以使用sp_executesql代替存儲過程多次執行Transact-SQL語句。 因為Transact-SQL語句本身保持不變,只有參數值發生變化,因此SQL Server查詢優化器可能會重用為第一次執行生成的執行計划。

以下是正確參數化的查詢方式,我們在字符串里面有一些變量,在執行的時候,我們通過其他變量傳遞值給它。

DECLARE @Col2 SMALLINT ,
    @Col1 INT
SELECT  @Col2 = 3 ,
        @Col1 = 4
 
 
DECLARE @SQL NVARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = @InnerCol2 and Col1 = @InnerCol1' 
 
DECLARE @ParmDefinition NVARCHAR(500)
SET @ParmDefinition = N'@InnerCol2 smallint ,@InnerCol1 int'

 
EXEC sp_executesql @SQL, @ParmDefinition, @InnerCol2 = @Col2,
    @InnerCol1 = @Col1
GO
 
 
DECLARE @Col2 SMALLINT ,
    @Col1 INT
SELECT  @Col2 = 3 ,
        @Col1 = 4
 
 
DECLARE @SQL NVARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = @InnerCol2 and Col1 = @InnerCol1'
 
DECLARE @ParmDefinition NVARCHAR(500)
SET @ParmDefinition = N'@InnerCol2 smallint ,@InnerCol1 int'
 
 
EXEC sp_executesql @SQL, @ParmDefinition, @InnerCol2 = @Col2,
    @InnerCol1 = @Col1

GO

我們看到只有一個計數為8的計划,而不是像我們上述那樣運行查詢。 我們也可以只需要聲明一次,然后我們只需要在執行之前更改參數的值,如下:

DECLARE @Col2 SMALLINT ,
    @Col1 INT
SELECT  @Col2 = 3 ,
        @Col1 = 4
 
 
DECLARE @SQL NVARCHAR(1000)
SELECT  @SQL = 'select * from dbo.TestDynamicSQL
where Col2 = @InnerCol2 and Col1 = @InnerCol1' 
 
DECLARE @ParmDefinition NVARCHAR(500)
SET @ParmDefinition = N'@InnerCol2 smallint ,@InnerCol1 int'
 
 
 
EXEC sp_executesql @SQL, @ParmDefinition, @InnerCol2 = @Col2,
    @InnerCol1 = @Col1
 
--change param values and run the same query
SELECT  @Col2 = 2 ,
        @Col1 = 3
EXEC sp_executesql @SQL, @ParmDefinition, @InnerCol2 = @Col2,
    @InnerCol1 = @Col1

最終查詢計划緩存次數和上述正確方式一致。正確使用sp_executesql對於性能非常有利,而且使用sp_executesql還可以為我們提供一些EXEC無法實現的功能。比如如何得到一個表中的行數? 利用EXEC我們需要使用一個臨時表和填充,而用sp_executesql我們只需要使用一個輸出變量。

SET STATISTICS IO ON
SET STATISTICS TIME ON
--EXEC (SQL)
DECLARE @Totalcount INT ,
    @SQL NVARCHAR(100)
 
 
CREATE TABLE #temp (Totalcount INT )
SELECT  @SQL = 'Insert into #temp Select Count(*) from dbo.TestDynamicSQL'
 
EXEC( @SQL)
 
SELECT  @Totalcount = Totalcount
FROM    #temp
 
SELECT  @Totalcount AS Totalcount
 
DROP TABLE #temp
GO
 

--sp_executesql
DECLARE @TableCount INT,
@SQL NVARCHAR(100)

SELECT @SQL = N'SELECT @InnerTableCount = COUNT(*) FROM  dbo.TestDynamicSQL'
 
EXEC SP_EXECUTESQL @SQL, N'@InnerTableCount INT OUTPUT', @TableCount OUTPUT
 
SELECT @TableCount
GO

當然除了EXEC無法實現的功能外,最重要的一點則是SP_EXECUTESQL能夠防止SQL注入問題。 

總結 

執行SQL動態查詢SP_EXECUTESQL比EXEC性能更好,使得存儲過程能夠被重用,但是存儲過程能夠被重用的前提則是正確使用參數,使用參數化查詢,否則SP_EXECUTESQL將不會提供任何性能益處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM