SQL Server 臨時表和表變量系列之選擇篇


原文地址:https://yq.aliyun.com/articles/69187

摘要: # 摘要 通過前面的三篇系列文章,我們對臨時表和表變量的概念、對比和認知誤區已經有了非常全面的認識。其實,我們的終極目的,還是今天要討論的話題,即當我們面對具體的業務場景的時候,該選擇臨時表還是表變量? # 幾種典型場景 以下是幾種典型的場景,讓我們看看到底該作何選擇,以及做出最終選擇的具體原因和考量。 ## 存儲過程嵌套 在SQL Server中,使用存儲過程的好處顯而易見,往往會節約

摘要

通過前面的三篇系列文章,我們對臨時表和表變量的概念、對比和認知誤區已經有了非常全面的認識。其實,我們的終極目的,還是今天要討論的話題,即當我們面對具體的業務場景的時候,該選擇臨時表還是表變量?

幾種典型場景

以下是幾種典型的場景,讓我們看看到底該作何選擇,以及做出最終選擇的具體原因和考量。

存儲過程嵌套

在SQL Server中,使用存儲過程的好處顯而易見,往往會節約存儲過程執行計划編譯時間,提高查詢語句的執行效率。有時候,我們在構建存儲過程多層次嵌套場景中,會有內層存儲過程需要臨時使用外層存儲過程的“暫存”數據。在SQL Server暫存臨時數據的方法可以使用臨時表或者表變量,但是在這種場景中,僅臨時表適合。比如,下面的例子:

-- Scenario 1: Nest Store Procedure
USE tempdb
GO
IF OBJECT_ID('tempdb..#UP_Inner', 'P') IS NOT NULL
    DROP PROC #UP_Inner
GO

CREATE PROC #UP_Inner
AS
BEGIN
    SET NOCOUNT ON
    UPDATE A
    SET Comment = 'INNER'
    FROM #temp AS A
    WHERE RowID = 1;
END
GO


IF OBJECT_ID('tempdb..#UP_Outer', 'P') IS NOT NULL
    DROP PROC #UP_Outer
GO
CREATE PROC #UP_Outer
AS
BEGIN
    SET NOCOUNT ON

    IF OBJECT_ID('tempdb..#temp', 'U') IS NOT NULL
        DROP PROC #temp
    CREATE TABLE #temp(
    RowID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY
    ,Comment VARCHAR(100) NULL)

    INSERT INTO #temp(Comment) VALUES(''),('OUTER')

    -- check the data before call inner SP
    SELECT * FROM #temp
    -- call the inner store procedure
    EXEC #UP_Inner

    -- check the data after call inner SP
    SELECT * FROM #temp
END
GO

-- call the outer SP
EXEC #UP_Outer

-- END Scenario 1: Nest Store Procedure
View Code

 

執行結果展示如下:
01.png

我們在內層存儲過程將臨時表#temp字段Comment更新為INNER;外層存儲過程在調用內層存儲過程前后分別查詢臨時表的數據。從這個結果來看,內層存儲過程完全可以使用外層存儲過程創建的臨時表。這種場景無法使用表變量,因為內層存儲過程會因為表變量沒有定義而報錯。
02.png

服務啟動自動執行存儲過程

有時候,我們需要在SQL Server Service啟動完畢后,立馬自動執行某個存儲過程以獲取某些重要的數據信息。比如:我們想知道SQL Server服務啟動后,到底有哪些用戶連接到了SQL Server服務器。我們可以選擇使用全局臨時表來暫存用戶信息,而且其他進程也可以查看相應的數據信息。方法如下:

-- Scenario 2: auto execution SP when startup
USE master
GO
EXEC sys.sp_configure 'show advanced options', 1
GO
EXEC sys.sp_configure 'scan for startup procs', 1
GO
RECONFIGURE WITH OVERRIDE
GO

-- create sp to call when sql server serivce startup
CREATE PROC dbo.UP_GetLoginUserWhenStartup
AS
BEGIN
    SET NOCOUNT ON
    IF OBJECT_ID('tempdb..##temp', 'U') IS NOT NULL
        DROP TABLE ##temp
    CREATE TABLE ##temp(
        RowId INT IDENTITY(1, 1) NOT NULL,
        LoginName varchar(200) NOT NULL
    )

    INSERT INTO ##temp
    SELECT DISTINCT loginame
    FROM sys.sysprocesses

END
GO

-- registe to auto execution when startup
EXEC sys.sp_procoption 'UP_GetLoginUserWhenStartup', 'startup', 'on'
GO

-- END Scenario 2: auto execution SP when startup
View Code

 

重啟SQL Server Service,然后新開一個連接執行下面的語句,結果如下:
03.png

SQL Server Service重啟完畢后,系統會自動執行Master數據庫下的存儲過程dbo.UP_GetLoginUserWhenStartup以獲取到哪些用戶連接到SQL Server。這個過程和Linux開機自動執行自定義腳本或者服務非常類似。由於其他進程需要查看抓取到的信息,在此使用全局臨時表而不是表變量。

暫存大量數據

在很多場景我們需要暫存大量數據或者根本無法預估需要暫存的數據量。這里需要首先明確的一個問題是,到底暫存多大的數據量算大量?根據上一篇文章SQL Server 臨時表和表變量系列之認知誤區篇中的章節“表變量僅駐留在內存中”的內容,我們可以認為需要暫存的數據量大小接近或者超過SQL Server最大可以使用內存一半的時候,這個數據量就是大量數據。比如:當SQL Server的Max Server Memory設置為1GB,需要暫存的數據量接近或者超過512MB時,512MB就是大量數據;但是,當SQL Server Max Server Memory為10GB甚至更高,需要暫存512MB時,這個數據量又不算是大量數據。在大量數據需要暫存時,無論使用臨時表或者表變量,SQL Server系統最終會將數據存在Tempdb的數據文件磁盤上。所以這個時候,請選擇使用臨時表來暫存數據,最好是能夠根據業務場景為臨時表創建合適的索引,以提高后續臨時表查詢語句的執行效率。

需要支持用戶事務

有時候用戶業務場景需要暫存的數據結構支持用戶事務,在這種場景下,我們應該選擇臨時表。根據我們之前的文章SQL Server 臨時表和變量系列之對比篇的“對事務支持”部分,我們知道,表變量不支持用戶事務回滾,而臨時表對用戶事務的支持和正式表沒有任何差異。所以,在這種場景下,我們需要選擇臨時表作為暫存數據結構。

表值函數的返回值

表值函數的返回值為表變量,這個場景中,是無法使用臨時表來替換的。比如:我想要找出當前數據庫下表名稱中含有某個特定字符串的所有表信息,用表值函數來實現的方式如下:

-- Scenario: function returned temp variables
USE AdventureWorks2014
GO
IF OBJECT_ID('dbo.UTF_FindTables', 'TF') IS NOT NULL
    DROP FUNCTION dbo.UTF_FindTables
GO
CREATE FUNCTION dbo.UTF_FindTables(
    @partner sysname
) RETURNS @Tables TABLE(
RowID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY
,tb_Object_ID BIGINT NULL
,database_name SYSNAME NULL
,schema_name SYSNAME NULL
,object_Name SYSNAME NULL
)
AS
BEGIN
    INSERT INTO @Tables
    SELECT object_id, DB_NAME(), SCHEMA_NAME(schema_id), name
    FROM sys.tables
    WHERE name like '%' + @partner + '%';

    RETURN
END
GO

-- Calling example
SELECT * FROM dbo.UTF_FindTables('Person')
-- END Scenario
View Code

 

執行結果如下所示:
04.png

查找當前數據庫下表名字中含有Person關鍵字的表詳情。由於表值函數返回值僅支持表變量,所以這種場景中是無法使用臨時表的。

高並發場景選擇表變量

在SQL Server數據庫高並發場景中,請慎重選擇臨時表的使用,建議使用表變量。對於這個場景的測試,我們會使用到SQLTest這個測試工具,關於這個工具的使用請參照我之前的文章SQLTest系列之INSERT語句測試
首先,我們創建兩個測試存儲過程,它們的邏輯一模一樣,唯一不同的是一個使用表變量,一個使用臨時表。

USE AdventureWorks2014
GO
IF OBJECT_ID('dbo.UP_TableVariables', 'P') IS NOT NULL
    DROP PROC dbo.UP_TableVariables
GO
CREATE PROC dbo.UP_TableVariables
AS
BEGIN
    SET NOCOUNT ON
    DECLARE @t1 TABLE(
    c1 INT, 
    c2 INT, 
    c3 CHAR(2000)
    )

    ;WITH DATA
    AS(
        SELECT *
        FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS T(C)
    )
    INSERT INTO @t1
    SELECT a.C, b.C, REPLICATE('A', 2000)
    FROM DATA as a, data as b
END
GO

IF OBJECT_ID('dbo.UP_TempTable', 'P') IS NOT NULL
    DROP PROC dbo.UP_TempTable
GO
CREATE PROC dbo.UP_TempTable
AS
BEGIN
    SET NOCOUNT ON
    CREATE TABLE #t1 (
    c1 INT, 
    c2 INT, 
    c3 CHAR(2000)
    )

    ;WITH DATA
    AS(
        SELECT *
        FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS T(C)
    )
    INSERT INTO #t1
    SELECT a.C, b.C, REPLICATE('A', 2000)
    FROM DATA as a, data as b
END
GO
View Code

 

接下來,我們使用兩個Workload,每個Workload開啟20個進程。Workload 1執行EXEC dbo.UP_TableVariables 100次;workload 2執行EXEC dbo.UP_TempTable 100次。測試代碼如下:

USE AdventureWorks2014
SET NOCOUNT ON
DECLARE 
    @i INT
;

SET 
    @i = 1
;
WHILE @i <= 100
BEGIN
    EXEC dbo.UP_TableVariables --EXEC  dbo.UP_TempTable
    SET @i = @i + 1
END
GO
View Code

 

測試時間為120秒,測試的結果如下:
Workload 1:表變量在高並發場景的性能表現為:完成迭代340次,每一次數據庫平均時間消耗為6.835秒。
05.png
Workload 2:臨時表在高並發場景下的性能表現為:完成迭代280次,每一次數據庫平均時間消耗為8.321秒。
06.png
從我的測試機測試的結果來看,表變量在20個並發場景,迭代次數也有20%多的增加,相應的平均時間消耗比臨時表有20%多的性能提升。基於這個測試結果來看,在高並發使用場景下,選擇表變量而不是臨時表。


免責聲明!

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



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