理解性能的奥秘——应用程序中慢,SSMS中快(6)——SQL Server如何编译动态SQL
什么是动态SQL?
- 使用EXEC()和sp_executesql执行的SQL语句。
- 直接从客户端发到服务器的SQL语句。
- 在SQLCLR中提交的SQL语句。
SELECT @sql = 'SELECT mycol FROM tbl WHERE keycol = ' + convert(varchar, @value) EXEC(@sql)
或者在C#中:
cmd.CommandText = "SELECT mycol FROM tbl WHERE keycol = " + value.ToString();
EXEC sp_executesql N'SELECT mycol FROM dbo.tbl WHERE keycol = @value',
N'@value int', @value = @valuecmd.CommandText = "SELECT mycol FROM dbo.tbl WHERE keycol = @value";
cmd.Parameters.Add("@value", SqlDbType.Int);
cmd.Parameters["@value"].Value = value;
动态SQL的计划生成:
SELECT * FROM Orders WHERE OrderID = 11000
如果你提交的是下面这种方式,那么编译后就得到了上面这个语句:
EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderID = @p1', N'@p1 int', @p1 = 11000
对于参数化,也有两种模式:简单和强制。对于简单参数化,SQL Server仅参数化相当小的一部分简单查询。对于强制参数化,SQL Server会把所有参数用常量替换,这篇文章提到的情况除外:强制参数化(微软联机丛书) 默认模式是简单参数化,可以使用ALTER DATABASE命令对每个库进行修改。
动态SQL和计划缓存:
查询文本作为哈希键:
USE Northwind GO EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '20000101' EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '19980101' EXEC sp_executesql N'select * from Orders where OrderDate > @orderdate', N'@orderdate datetime', '19980101'
执行计划如下图,你会发现前两个执行语句使用同一个查询计划:
USE Northwind GO DBCC FREEPROCCACHE GO EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '20000101' EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '19980101' EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '19980101'
这一次,三个查询计划都一样了。
默认架构的重要性:
USE Northwind GO DBCC FREEPROCCACHE GO CREATE SCHEMA Schema2 GO CREATE USER User1 WITHOUT LOGIN WITH DEFAULT_SCHEMA = dbo CREATE USER User2 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Schema2 GRANT SELECT ON Orders TO User1, User2 GRANT SHOWPLAN TO User1, User2 GO EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '20000101' GO EXECUTE AS USER = 'User1' EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '19980101' REVERT GO EXECUTE AS USER = 'User2' EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '19980101' REVERT GO DROP USER User1 DROP USER User2 DROP SCHEMA Schema2 GO
执行计划如前面所示,前两个执行计划一样但是第三个不一样。这个脚本创建了两个数据库用户,并授权给他们运行查询。然后语句被执行了三次。第一次由默认架构(即dbo),然后使用两个新建的账号运行。
SELECT qs.plan_handle, a.attrlist
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) est
CROSS APPLY (SELECT epa.attribute + '=' + convert(nvarchar(127), epa.value) + ' '
FROM sys.dm_exec_plan_attributes(qs.plan_handle) epa
WHERE epa.is_cache_key = 1
ORDER BY epa.attribute
FOR XML PATH('')) AS a(attrlist)
WHERE est.text LIKE '%WHERE OrderDate > @orderdate%'
AND est.text NOT LIKE '%sys.dm_exec_plan_attributes%'
- 没有db_id()的限定,因为这一列仅对存储过程通用。
- 由于没有存储过程名可以匹配,所以必须使用语句文本来搜索。
- 需要额外的条件去筛选sys.dm_exec_plan_attributes。
date_first=7 date_format=1 dbid=6 objectid=158662399 set_options=251 user_id=5 date_first=7 date_format=1 dbid=6 objectid=158662399 set_options=251 user_id=1
EXEC sp_executesql N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate',
N'@orderdate datetime', '20000101'date_first=7 date_format=1 dbid=6 objectid=549443125 set_options=251 user_id=-2
在SSMS中运行应用程序查询:
EXECUTE AS USER = 'appuser' go -- 需要执行的SQL语句 go REVERT
但是,如果这个账号访问的资源不在当前库中,就会报错。这个时候,可以使用EXECUTE AS LOGIN来替代,但是这个方案需要有服务器级别的权限。
SELECT '<' + est.text + '>' FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) est WHERE est.text LIKE '%能标识出语句特征的SQL代码%'
注意这个要运行在文本模式,默认是网格模式,SSMS会使用空格来替换换行符。其中尖括号只是为了作为分隔作用。
EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate',
N'@orderdate datetime', '20000101'
即使你按这种格式来执行也无所谓:
EXEC sp_executesql N'SELECT * FROM Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', '20000101'
处理动态SQL中的参数嗅探问题:
自动参数化的影响:
SELECT * FROM Orders WHERE OrderID = 11000
Seek Keys[1]: Prefix: [Northwind].[dbo].[Orders].OrderID = Scalar Operator(CONVERT_IMPLICIT(int,[@1],0))
[@1]揭示了语句被自动参数化(auto-parameterised)
有时候这种情况是由用户发起,如:
SELECT ... FROM dbo.Orders WHERE Status = 'Delayed'
在Northwind中的这个表不存在stauts列,更不存在Delayed这个值,只是演示需要,当SQL Server参数化这个查询时,由于产生查询计划的要求必须可以覆盖所有参数,所以优化器并不会使用status上的列。
DECLARE @x int SELECT ... FROM dbo.Orders WHERE Status = 'Delayed' AND @x IS NULL
计划向导和计划冻结:
USE Northwind GO DBCC FREEPROCCACHE GO EXEC sp_executesql N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', @orderdate = '19960101' GO EXEC sp_create_plan_guide @name = N'MyGuide', @stmt = N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate', @type = N'SQL', @module_or_batch = NULL, @params = N'@orderdate datetime', @hints = N'OPTION (TABLE HINT (dbo.Orders , INDEX (OrderDate)))' GO EXEC sp_executesql N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', @orderdate = '19980101' GO EXEC sp_control_plan_guide N'DROP', N'MyGuide'
最后,@hints就是重点所在。在这个例子中,指定了查询要总使用OrderDate上的索引,不管@orderdate的嗅探值是什么。还有一个在SQL 2005中不可用的查询提示OPTION(TABLE HINT),这就是为什么这个脚本只能在SQL 2008+使用。
在这个例子中,我使用了计划向导强制使用索引,但是你也可以使用其他提示,包含USE PLAN提示指定使用特定的完整的查询计划。不过这个风险有点大。
USE Northwind GO DBCC FREEPROCCACHE SET ARITHABORT ON GO EXEC sp_executesql N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', @orderdate = '19990101' GO DECLARE @plan_handle VARBINARY(64), @rowc INT SELECT @plan_handle = plan_handle FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) est WHERE est.TEXT LIKE '%Orders WHERE OrderDate%' AND est.TEXT NOT LIKE '%dm_exec_query_stats%' SELECT @rowc = @@rowcount IF @rowc = 1 EXEC sp_create_plan_guide_from_handle 'MyFrozenPlan', @plan_handle ELSE RAISERROR ( '%d plans found in plan cache. Canno create plan guide', 16, 1, @rowc ) GO -- Test it out! SET ARITHABORT OFF GO EXEC sp_executesql N'SELECT * FROM dbo.Orders WHERE OrderDate > @orderdate', N'@orderdate datetime', @orderdate = '19960101' GO SET ARITHABORT ON EXEC sp_control_plan_guide 'DROP', 'MyFrozenPlan'
示例中,首先清空缓存,然后把ARITHABORT开启。然后执行一个能使查询使用合适查询计划的参数。然后第二个批演示了如何使用sp_create_plan_guide_from_handle。首先查询sys.dm_exec_query_stats和sys.dm_exec_sql_text去查找批的条目。然后把@@rowcount值存入本地变量。这样做可以防止从缓存中获得多个匹配或不匹配项。如果得到了准确的匹配项,就可以调用sp_create_plan_guide_from_handle,并传入两个参数:计划向导的名字和plan handle。
总结:
扩展阅读及参考资料:
理解性能的奥秘——应用程序中慢,SSMS中快(6)——SQL Server如何编译动态SQL的更多相关文章
- 理解性能的奥秘——应用程序中慢,SSMS中快(5)——案例:如何应对参数嗅探
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(4)--收集解决参数嗅探问题的信息 首先我们需要明白,参数嗅探本身不是问 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(1)——简介
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 在工作中发现有不少类似的现象,有幸看到国外大牛写的一篇文章,由于已经完善得不能再添油加醋,所以决定直接翻译,原文出处:http ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(4)——收集解决参数嗅探问题的信息
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(3)--不总是参数嗅探的错 前面已经提到过关于存储过程在SSMS中运行很 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(3)——不总是参数嗅探的错
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(2)--SQL Server如何编译存储过程 在我们开始深入研究如何处理 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(2)——SQL Server如何编译存储过程
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(1)--简介 本文介绍SQL Server如何编译存储过程并使用计划缓存 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(4)收集解决参数嗅探问题的信息
---从计划缓存中直接获取查询计划和参数: ), ) SELECT @dbname = 'hydee_连锁', @procname = 'dbo.p_select_ware'; WITH baseda ...
- 数据库 alert.log 日志中出现 "[Oracle][ODBC SQL Server Wire Protocol driver][SQL Server] 'RECOVER'"报错信息
现象描述: (1).数据库通过调用透明网络实现分布式事务,但透明网关停用后,失败的分布式事务并未清理. (2).数据库 alert 日志 Thu Sep 06 06:53:00 2018 Errors ...
- 为什么说JAVA中要慎重使用继承 C# 语言历史版本特性(C# 1.0到C# 8.0汇总) SQL Server事务 事务日志 SQL Server 锁详解 软件架构之 23种设计模式 Oracle与Sqlserver:Order by NULL值介绍 asp.net MVC漏油配置总结
为什么说JAVA中要慎重使用继承 这篇文章的主题并非鼓励不使用继承,而是仅从使用继承带来的问题出发,讨论继承机制不太好的地方,从而在使用时慎重选择,避开可能遇到的坑. JAVA中使用到继承就会有两 ...
- SQL Server优化技巧之SQL Server中的"MapReduce"
日常的OLTP环境中,有时会涉及到一些统计方面的SQL语句,这些语句可能消耗巨大,进而影响整体运行环境,这里我为大家介绍如何利用SQL Server中的”类MapReduce”方式,在特定的统计情形中 ...
随机推荐
- JavaScript 和 TypeScript 中的 class
对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影.所以 设计模式 也是大 ...
- TensorFlow学习笔记(MNIST报错修正 适用Tensorflow1.3)
在Tensorflow实战Google框架下的深度学习这本书的MNIST的图像识别例子中,每次都要报错 错误如下: Only call `sparse_softmax_cross_entropy_ ...
- [HNOI 2008]GT考试
Description 题库链接 问你长度为 \(n\) 的可含前导零的数字串中,不含长度为 \(m\) 的子串 \(X\) 有多少个,取模. \(1\leq n\leq 10^9,1\leq m\l ...
- [SDOI2008]Sandy的卡片
题目描述 Sandy和Sue的热衷于收集干脆面中的卡片. 然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡片兑换超炫的人物模型. 每一张卡片都由一些数字进行标记,第i张卡片的 ...
- 计蒜客NOIP模拟赛(2) D2T2紫色百合
[问题描述] “牵着你的手的是她,路边开满了紫色的百合花……” 你从梦中醒来,却依然忘不了梦中的她百合花,每朵百合花都有一个权值,在二进制下写成一行‘1’,第i朵紫色百合的权值在二进制下写成i个‘1’ ...
- UOJ #236. 【IOI2016】railroad
Description Anna 在一个游乐园工作.她负责建造一个新的过山车铁路.她已经设计了影响过山车速度的 nn 个特殊的路段(方便起见标记为 00 到 n−1n−1).现在 Anna 必须要把这 ...
- 【USACO】 奶牛会展
题目背景 奶牛想证明它们是聪明而风趣的.为此,贝西筹备了一个奶牛博览会,她已经对N 头奶牛进行 了面试,确定了每头奶牛的智商和情商. 题目描述 贝西有权选择让哪些奶牛参加展览.由于负的智商或情商会造成 ...
- [BZOJ]1069: [SCOI2007]最大土地面积
题目大意:给出二维平面上n个点,求最大的由这些点组成的四边形面积.(n<=2000) 思路:求出凸包后旋转卡壳枚举对踵点对作为四边形的对角线,枚举或二分另外两个点,复杂度O(n^2)或O(nlo ...
- Codeforces Round #401 (Div. 2)
和FallDream dalao一起从学长那借了个小号打Div2,他切ABE我做CD,我这里就写下CD题解,剩下的戳这里 AC:All Rank:33 小号Rating:1539+217->17 ...
- redux 最简例子
方便初学redux的同学学习,这里是最简单的redux例子 1 import React, {Component, PropTypes} from 'react' 2 import ReactDOM ...