时间:2021-07-01 10:21:17 帮助过:30人阅读
接上文:理解性能的奥秘——应用程序中慢,SSMS中快(5)——案例:如何应对参数嗅探
SELECT @sql = ‘SELECT mycol FROM tbl WHERE keycol = ‘ + convert(varchar, @value) EXEC(@sql)
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 = @value或者在C#中:
cmd.CommandText = "SELECT mycol FROM dbo.tbl WHERE keycol = @value"; cmd.Parameters.Add("@value", SqlDbType.Int); cmd.Parameters["@value"].Value = value;
SELECT * FROM Orders WHERE OrderID = 11000如果你提交的是下面这种方式,那么编译后就得到了上面这个语句:
EXEC sp_executesql N‘SELECT * FROM Orders WHERE OrderID = @p1‘, N‘@p1 int‘, @p1 = 11000
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
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%‘
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首先看看objectid,这是用于标识两个独立的缓存条目。然后看下user_id,其实这里应该计划缓存对应的默认架构名会更加合适。dbo架构总是为1。而在Northwind库中,Schema2是5(具体值不是非常重要)。 然后在运行一下这个语句:
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=-2objectID已经变了,因为查询文本不一样。user_id现在为-2,意味着什么?仔细检查语句,可以发现显式增加了架构名,意味着这个语句现在是精确的,所有用户都可以使用这个缓存条目。而-2的含义是:没有混淆的对象引用这个查询。这也是为什么编程规范里面都建议明确定义架构名的其中一个意思。不管在程序端还是在存储过程中都应该作为最佳实践。 对于存储过程,命名解释总是从存储过程的所属方执行,而不是当前用户。所以由dbo拥有的存储过程,Orders只能使用dbo.Orders而不会使用其他架构。(存储过程内部调用动态SQL除外,这个只适合存储过程中直接执行SQL语句。)
EXECUTE AS USER = ‘appuser‘ go -- 需要执行的SQL语句 go REVERT但是,如果这个账号访问的资源不在当前库中,就会报错。这个时候,可以使用EXECUTE AS LOGIN来替代,但是这个方案需要有服务器级别的权限。 由于获取SQL文本通常不容易,最好的方式就是使用跟踪来获取SQL语句,可以使用Profiler或者服务器端跟踪。如果SQL语句是非参数化的,就需要小心你复制的完整文本,然后在SSMS中执行。也就是说,不要清除或者添加一些前缀或者空格等。也不要乱换行,删除注释等。确保和应用程序执行的语句一模一样。可以通过sys.dm_exec_plan_attributes来检查。 另外一个方案是从sys.dm_exec_query_stats和sys.dm_exec_sql_text中获取,执行下面语句:
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会使用空格来替换换行符。其中尖括号只是为了作为分隔作用。 对于参数化SQL就容易的多。因为SQL语句被单括号包住。也就是说你可能在Profiler中看到:
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‘但是注意,不要改动单括号内部的代码,这样会影响哈希值的计算。
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上的列。 没有任何绝对的方式关闭任何的模式的参数化功能,但是有一些技巧可以使用。如果数据库是简单参数化模式,参数化仅发生在非常简单的查询中,比如仅仅是单表查询。其中一个技巧是在语句中使用AND 1=1来停用简单参数化的发生。 如果数据库是强制参数化,那么有两个替代方案。可以在联机丛书中看到:强制参数化 哪些情景是不适用于参数化的。其中一个是使用OPTION(RECOMPILE),另外一个是添加一个变量:
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‘在这个例子中,创建了一个计划,确保查询一定会用到OrderDate上的索引并且是索引查找。然后指定了向导的名称。然后指定了使用这个向导的语句。当你在SSMS中执行时,确保没有添加或丢失任何空格或者其他改变。其中@type参数定义了这个向导是针对动态SQL而不是存储过程。如果SELECT语句是一个大批处理中的一部分,需要在@module_or_batch中指定应用程序提交的那部分代码。如果@module_or_batch为NULL,那么@stmt被假设为整个批。@params是批的参数列表,必须与应用程序提交的字符精确匹配。
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‘
理解性能的奥秘——应用程序中慢,SSMS中快(6)——SQL Server如何编译动态SQL
标签:microsoft cti object 复杂 问题: lis use 参数 bin