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
目录
相关文章
|
4天前
|
SQL 运维 程序员
一个功能丰富的SQL审核查询平台
一个功能丰富的SQL审核查询平台
|
11天前
|
SQL 数据库 Java
HQL vs SQL:谁将统治数据库查询的未来?揭秘Hibernate的神秘力量!
【8月更文挑战第31天】Hibernate查询语言(HQL)是一种面向对象的查询语言,它模仿了SQL的语法,但操作对象为持久化类及其属性,而非数据库表和列。HQL具有类型安全、易于维护等优点,支持面向对象的高级特性,内置大量函数,可灵活处理查询结果。下面通过示例对比HQL与SQL,展示HQL在实际应用中的优势。例如,HQL查询“从员工表中筛选年龄大于30岁的员工”只需简单地表示为 `FROM Employee e WHERE e.age > 30`,而在SQL中则需明确指定表名和列名。此外,HQL在处理关联查询时也更为直观易懂。然而,对于某些复杂的数据库操作,SQL仍有其独特优势。
21 0
|
11天前
|
SQL 关系型数据库 MySQL
|
11天前
|
API Java 数据库连接
从平凡到卓越:Hibernate Criteria API 让你的数据库查询瞬间高大上,彻底告别复杂SQL!
【8月更文挑战第31天】构建复杂查询是数据库应用开发中的常见需求。Hibernate 的 Criteria API 以其强大和灵活的特点,允许开发者以面向对象的方式构建查询逻辑,同时具备 SQL 的表达力。本文将介绍 Criteria API 的基本用法并通过示例展示其实际应用。此 API 通过 API 构建查询条件而非直接编写查询语句,提高了代码的可读性和安全性。无论是简单的条件过滤还是复杂的分页和连接查询,Criteria API 均能胜任,有助于提升开发效率和应用的健壮性。
19 0
|
2月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
56 13
|
2月前
|
SQL
解锁 SQL Server 2022的时间序列数据功能
【7月更文挑战第14天】要解锁SQL Server 2022的时间序列数据功能,可使用`generate_series`函数生成整数序列,例如:`SELECT value FROM generate_series(1, 10)。此外,`date_bucket`函数能按指定间隔(如周)对日期时间值分组,这些工具结合窗口函数和其他时间日期函数,能高效处理和分析时间序列数据。更多信息请参考官方文档和技术资料。
|
2月前
|
SQL 存储 网络安全
关系数据库SQLserver 安装 SQL Server
【7月更文挑战第26天】
44 6
|
29天前
|
SQL 安全 Java
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client
132 0
|
2月前
|
存储 SQL C++
对比 SQL Server中的VARCHAR(max) 与VARCHAR(n) 数据类型
【7月更文挑战7天】SQL Server 中的 VARCHAR(max) vs VARCHAR(n): - VARCHAR(n) 存储最多 n 个字符(1-8000),适合短文本。 - VARCHAR(max) 可存储约 21 亿个字符,适合大量文本。 - VARCHAR(n) 在处理小数据时性能更好,空间固定。 - VARCHAR(max) 对于大文本更合适,但可能影响性能。 - 选择取决于数据长度预期和业务需求。
|
2月前
|
SQL Oracle 关系型数据库
MySQL、SQL Server和Oracle数据库安装部署教程
数据库的安装部署教程因不同的数据库管理系统(DBMS)而异,以下将以MySQL、SQL Server和Oracle为例,分别概述其安装部署的基本步骤。请注意,由于软件版本和操作系统的不同,具体步骤可能会有所变化。
120 3