SQL Server 临时表和表变量系列之选择篇

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介: # 摘要 通过前面的三篇系列文章,我们对临时表和表变量的概念、对比和认知误区已经有了非常全面的认识。其实,我们的终极目的,还是今天要讨论的话题,即当我们面对具体的业务场景的时候,该选择临时表还是表变量? # 几种典型场景 以下是几种典型的场景,让我们看看到底该作何选择,以及做出最终选择的具体原因和考量。 ## 存储过程嵌套 在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

执行结果展示如下:
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

重启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

执行结果如下所示:
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

接下来,我们使用两个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

测试时间为120秒,测试的结果如下:
Workload 1:表变量在高并发场景的性能表现为:完成迭代340次,每一次数据库平均时间消耗为6.835秒。
05.png
Workload 2:临时表在高并发场景下的性能表现为:完成迭代280次,每一次数据库平均时间消耗为8.321秒。
06.png
从我的测试机测试的结果来看,表变量在20个并发场景,迭代次数也有20%多的增加,相应的平均时间消耗比临时表有20%多的性能提升。基于这个测试结果来看,在高并发使用场景下,选择表变量而不是临时表。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
4天前
|
SQL 存储 Oracle
Oracle的PL/SQL定义变量和常量:数据的稳定与灵动
【4月更文挑战第19天】在Oracle PL/SQL中,变量和常量扮演着数据存储的关键角色。变量是可变的“魔术盒”,用于存储程序运行时的动态数据,通过`DECLARE`定义,可在循环和条件判断中体现其灵活性。常量则是不可变的“固定牌”,一旦设定值便保持不变,用`CONSTANT`声明,提供程序稳定性和易维护性。通过 `%TYPE`、`NOT NULL`等特性,可以更高效地管理和控制变量与常量,提升代码质量。善用两者,能优化PL/SQL程序的结构和性能。
|
12天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
51 10
|
22天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
17 0
|
12天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
75 6
|
7天前
|
SQL 安全 网络安全
IDEA DataGrip连接sqlserver 提示驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接的解决方法
IDEA DataGrip连接sqlserver 提示驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接的解决方法
17 0
|
12天前
|
SQL 存储 数据挖掘
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
服务器数据恢复环境: 一台安装windows server操作系统的服务器。一组由8块硬盘组建的RAID5,划分LUN供这台服务器使用。 在windows服务器内装有SqlServer数据库。存储空间LUN划分了两个逻辑分区。 服务器故障&初检: 由于未知原因,Sql Server数据库文件丢失,丢失数据涉及到3个库,表的数量有3000左右。数据库文件丢失原因还没有查清楚,也不能确定数据存储位置。 数据库文件丢失后服务器仍处于开机状态,所幸没有大量数据写入。 将raid5中所有磁盘编号后取出,经过硬件工程师检测,没有发现明显的硬件故障。以只读方式将所有磁盘进行扇区级的全盘镜像,镜像完成后将所
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
|
16天前
|
SQL 数据安全/隐私保护
SQL Server 2016安装教程
SQL Server 2016安装教程
17 1
|
16天前
|
SQL 安全 Java
SQL server 2017安装教程
SQL server 2017安装教程
15 1
|
29天前
|
SQL 存储 Python
Microsoft SQL Server 编写汉字转拼音函数
Microsoft SQL Server 编写汉字转拼音函数
|
1月前
|
SQL 存储 数据库
数据安全无忧,SQL Server 2014数据库定时备份解密
数据安全无忧,SQL Server 2014数据库定时备份解密