SQL Server-聚焦sp_executesql执行动态SQL查询性能真的比exec好?

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 之前我们已经讨论过动态SQL查询呢?这里为何再来探讨一番呢?因为其中还是存在一定问题,如标题所言,很多面试题也好或者有些博客也好都在说在执行动态SQL查询时sp_executesql的性能比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

最终查询为如下测试数据:
1

接下来我们执行如下两个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 %'

2

由上图可知,我们看到存在两个查询计划且每个执行了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

3

这个就不做过多解释,我们依然要清除查询计划缓存,我们再利用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

4

对比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

5

我们看到只有一个计数为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

6

当然除了EXEC无法实现的功能外,最重要的一点则是SP_EXECUTESQL能够防止SQL注入问题。

执行SQL动态查询SP_EXECUTESQL比EXEC性能更好,使得存储过程能够被重用,但是存储过程能够被重用的前提则是正确使用参数,使用参数化查询,否则SP_EXECUTESQL将不会提供任何性能益处。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情: https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
8天前
|
SQL 存储 人工智能
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
Vanna 是一个开源的 Python RAG(Retrieval-Augmented Generation)框架,能够基于大型语言模型(LLMs)为数据库生成精确的 SQL 查询。Vanna 支持多种 LLMs、向量数据库和 SQL 数据库,提供高准确性查询,同时确保数据库内容安全私密,不外泄。
56 7
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
|
15天前
|
SQL Java
使用java在未知表字段情况下通过sql查询信息
使用java在未知表字段情况下通过sql查询信息
31 8
|
21天前
|
SQL 安全 PHP
PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全
本文深入探讨了PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全。
42 4
|
24天前
|
SQL 监控 关系型数据库
SQL语句当前及历史信息查询-performance schema的使用
本文介绍了如何使用MySQL的Performance Schema来获取SQL语句的当前和历史执行信息。Performance Schema默认在MySQL 8.0中启用,可以通过查询相关表来获取详细的SQL执行信息,包括当前执行的SQL、历史执行记录和统计汇总信息,从而快速定位和解决性能瓶颈。
|
3月前
|
SQL 数据库
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库。 SQL Server数据库出现823错误的可能原因有:数据库物理页面损坏、数据库物理页面校验值损坏导致无法识别该页面、断电或者文件系统问题导致页面丢失。
104 12
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
|
18天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第16天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括配置系统源、安装 SQL Server 2019 软件包以及数据库初始化,确保 SQL Server 正常运行。
|
27天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第8天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统准备、配置安装源、安装 SQL Server 软件包、运行安装程序、初始化数据库以及配置远程连接。通过这些步骤,您可以顺利地在 CentOS 系统上部署和使用 SQL Server 2019。
|
28天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第7天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统要求检查与准备、配置安装源、安装 SQL Server 2019、配置 SQL Server 以及数据库初始化(可选)。通过这些步骤,你可以成功安装并初步配置 SQL Server 2019,进行简单的数据库操作。
|
2月前
|
存储 数据挖掘 数据库
数据库数据恢复—SQLserver数据库ndf文件大小变为0KB的数据恢复案例
一个运行在存储上的SQLServer数据库,有1000多个文件,大小几十TB。数据库每10天生成一个NDF文件,每个NDF几百GB大小。数据库包含两个LDF文件。 存储损坏,数据库不可用。管理员试图恢复数据库,发现有数个ndf文件大小变为0KB。 虽然NDF文件大小变为0KB,但是NDF文件在磁盘上还可能存在。可以尝试通过扫描&拼接数据库碎片来恢复NDF文件,然后修复数据库。
|
3月前
|
SQL 关系型数据库 MySQL
创建包含MySQL和SQLServer数据库所有字段类型的表的方法
创建一个既包含MySQL又包含SQL Server所有字段类型的表是一个复杂的任务,需要仔细地比较和转换数据类型。通过上述方法,可以在两个数据库系统之间建立起相互兼容的数据结构,为数据迁移和同步提供便利。这一过程不仅要考虑数据类型的直接对应,还要注意特定数据类型在不同系统中的表现差异,确保数据的一致性和完整性。
36 4