理解性能的奥秘——应用程序中慢,SSMS中快(5)——案例:如何应对参数嗅探
本文属于《理解性能的奥秘——应用程序中慢,SSMS中快》系列
接上文:理解性能的奥秘——应用程序中慢,SSMS中快(4)——收集解决参数嗅探问题的信息
- 查询使用的参数嗅探完全不合适。也就是说,查询计划对于这次执行是合适的,但是对于下一次执行就可能不合适。
- 应用程序中存在特定的调用模式,而且与其他大部分调用模式差异很大。通常这种调用是针对初次启动或者新一天的开始时调用。
- 一个或多个表上的索引结构不能很好地支持查询,但是又有某些次优的索引,导致优化过程中,优化器会无计划选择这些索引。
没有办法的办法:
基于输入的最优索引:
CREATE PROCEDURE List_orders_12 @custid nchar(5), @fromdate datetime, @todate datetime AS SELECT * FROM Orders WHERE CustomerID = @custid AND OrderDate BETWEEN @fromdate AND @todate
use Northwind go EXEC List_orders_12 'SAVEA', '19970811', '19970811' go sp_recompile List_orders_12 go EXEC List_orders_12 'CENTC', '19960101', '19961231'
注意,对于ASVEA,我们仅查询一天的订单,但是对于CENTC,我们查询一年的数据,你可能想到,这两个调用应该使用不同的索引,下面是实际执行计划:
CREATE PROCEDURE List_orders_12 @custid nchar(5), @fromdate datetime, @todate datetime AS SELECT * FROM Orders WHERE CustomerID = @custid AND OrderDate BETWEEN @fromdate AND @todate OPTION (RECOMPILE)
CREATE PROCEDURE List_orders_12 @custid nchar(5), @fromdate datetime, @todate datetime WITH RECOMPILE AS
对于这个存储过程,使用哪种方式都无所谓,因为它是单语句的。但是对于代码很长的存储过程,使用WITH RECOMPILE不是最好的方式,因为这样会引起整个存储过程重编译,增加重编译开销。另外关于WITH RECOMPILE的一个特性,就是计划不会存入缓存,但是使用OPTION (RECOMPILE)就会存入计划缓存中。
- 存储过程被调用的频率很高,重编译会明显影响系统性能。
- 查询非常复杂并且编译时间已经明显影响了响应时间。
动态查询条件:
WHERE (CustomerID = @custid OR @custid IS NULL) AND (OrderDate = @orderdate OR @orderdate IS NULL) ...
正如你想象的,参数嗅探对这类存储过程没有好处。我(作者)不打算在这里浪费太多篇幅,因为:1)因为前面提到过,这种情况更多是应用程序的问题。2)我写了一系列独立的文章:T-SQL动态查询(1)——简介 、T-SQL动态查询(2)——关键字查询、T-SQL动态查询(3)——静态SQL、T-SQL动态查询(4)——动态SQL
评估索引:
SELECT DISTINCT c.* FROM Table_C c JOIN Table_B b ON c.Col1 = b.Col2 JOIN Table_A a ON a.Col4 = b.Col1 WHERE a.Col1 = @p1 AND a.Col2 = @p2 AND a.Col3 = @p3
- 非聚集、不唯一索引Comb0_ix ,覆盖Col1,Col2,Col5,Col4 列。
- 非聚集、不唯一索引Col2_ix,覆盖Col2列。
- 非聚集、不唯一索引Col3_ix ,覆盖Col3列。
DBCC SHOW_STATISTICS (Table_A, Col3_ix)
SELECT Col3, COUNT(*) FROM Table_A GROUP BY Col3 ORDER BY Col3
然后重新研究慢查询的查询计划,检查优化器嗅探@p3的参数值是什么。然后发现是“APPLE”,一个不存在于表中的值。也就是说,第一次存储过程执行时,SQL Server预估的影响行数是1(注意不会出现0行),所以优化器认为对于查找单行数据,使用Col3列上的索引是最高效的。
- OPTION(RECOMPILE)/ WITH RECOMPILE
- 添加一个可选的索引,覆盖Col1,Col2,Col3,并且包含Col4。
- 改写Col3的索引为筛选索引,或者直接删掉。
- 使用索引提示强制查询使用其他索引。
- 使用OPTIMIZE FOR查询提示。
- 把@p3的值复制到本地变量。
- 修改应用程序行为。
添加新索引:
更改/删除Col3上的索引:
Col3上的索引真正用处在哪里?因为不是熟悉的系统,所以也不能回答这个问题。但是通常来说,在选择度很低的列上的索引一般都不高效甚至无效。所谓其中一个方案就是删除Col3上的 索引,避免优化器对这些索引进行考虑。也许索引是因为某个原因在某个时候错误添加,或者是很多很多年前添加的。但是随着系统的使用,不能一成不变地对待。
CREATE INDEX col3_ix ON Table_A(col3) WHERE col3 IN ('FIG', 'RASPBERRY', 'APPLE', 'APRICOT')
这样有两个好处:
- 索引的体积降低了将近99%。
- 这个索引不再成为问题查询的影响因素,因为SQL Server必须选择一个可以满足所有输入值的查询计划,所以即使嗅探到“APPLE”这个值,SQL Server也不会使用,这个索引,因为对于KIWI这个值而言,查询计划不能覆盖。
强制使用不同的索引:
SELECT c.* FROM Table_C c JOIN Table_B b ON c.Col1 = b.Ccol2 JOIN Table_A a WITH (INDEX = Combo_ix) ON a.Col4 = b.Col1 WHERE a.Col1 = @p1 AND a.Col2 = @p2 AND a.Col3 = @p3
WITH (INDEX (combo_ix, col2_ix))
让优化器在这两个“好”的索引之间选择。
OPTIMIZE FOR:
SELECT c.* FROM Table_C c JOIN Table_B b ON c.col1 = b.col2 JOIN Table_A a ON a.col4 = b.col1 WHERE a.col1 = @p1 AND a.col2 = @p2 AND a.col3 = @p3 OPTION (OPTIMIZE FOR (@p3 = 'KIWI'))
OPTION (OPTIMIZE FOR (@p3 UNKNOWN))
复制参数到本地变量:
修改应用程序:
小结:
应用程序缓存引起的案例:
CREATE PROCEDURE memdb_get_updated_customers @tstamp timestamp AS SELECT CustomerID, CustomerName, Address, ..., tstamp FROM Customers WHERE tstamp > @tstamp
当MemDB调用这些存储过程时,会传入timestamp中最高的值,主存数据库会传回0x作为参数,获取所有数据。在某些情况下,发现MemDB的存储过程运行时间很长,引入MemDB的目的是分担负载,但是现在反而增加了。
EXEC memdb_get_updated_customers 0x
EXEC memdb_get_updated_customers 0x000000000003E806
但是在夜间因为某些批处理导致内存不足的情况下,存储过程实行失败并不是罕见的事情。所以常见的情况有,在早上运行时,缓存里面实际上没有任何查询计划,然后嗅探0x值。对于这个值,优化器会使用Timestamp上的索引吗?如果是聚集索引,会嗅探,但是由于Timestamp更新得如此频繁,以至这个列并不适合作为聚集索引的候选键。所以一般Timestamp上的索引都是非聚集索引,因此,当优化器看到参数意味着需要返回所有数据,会使用表扫描操作,这个查询计划会放入计划缓存,然后后续的执行都会使用表扫描,即使仅仅是查询最常用的行。这明显会影响性能。
OPTION(RECOMPILE):
EXECUTE WITH RECOMPILE:
EXECUTE memdb_get_updated_customers WITH RECOMPILE
cmd.CommandType = CommandType.Text; cmd.Text = "EXECUTE memdb_get_updated_customers @tstamp WITH RECOMPILE";
使用一个封装好的存储过程:
CREATE PROCEDURE memdb_get_updated_customers @tstamp timestamp AS IF @tstamp = 0x EXECUTE memdb_get_updated_customers_inner @tstamp WITH RECOMPILE ELSE EXECUTE memdb_get_updated_customers_inner @tstamp
不同的代码路径:
CREATE PROCEDURE memdb_get_updated_customers @tstamp timestamp AS IF @tstamp = 0x BEGIN SELECT CustomerID, CustomerName, Address, ..., tstamp FROM Customers END ELSE BEGIN SELECT CustomerID, CustomerName, Address, ..., tstamp FROM Customers WITH (INDEX = timestamp_ix) WHERE tstamp > @tstamp END
不同的存储过程:
CREATE PROCEDURE memdb_get_transactions @transid int AS IF coalesce(@transid, 0) = 0 EXECUTE memdb_get_transactions_refresh ELSE BEGIN DECLARE @maxtransid int SELECT @maxtransid = MAX(transid) FROM transactions EXECUTE memdb_get_transactions_delta @transid, @maxtransid END
这部分比较易懂,不做详细说明。
修复问题SQL:
SELECT ... FROM Orders WHERE (CustomerID = @custid OR @custid IS NULL) AND (EmployeeID = @empid OR @empid IS NULL) AND convert(varchar, OrderDate, 101) = convert(varchar, @orderdate, 101)
允许用户查询很多不同的参数组合。开发人员考虑到OrderDate可能包含时间部分,所以使用了Convert()函数来补全程序端传入不带时间部分的参数。对于这个查询,OrderDate上的索引会被优化器选中,但是这种情况下,SQL Server不会使用索引查找,因为OrderDate已经变成了表达式,由于统计信息丢失原因,这种情况成为非SARG写法。
SELECT ... FROM Orders WHERE (CustomerID = @custid OR @custid IS NULL) AND (EmployeeID = @empid OR @empid IS NULL) AND OrderDate >= @orderdate AND OrderDate < dateadd(DAY, 1, @orderdate)
低效写法通常是性能问题的常见原因,也就是说查询总是很慢。哪怕跟参数嗅探没有关系。但是当你遇到参数嗅探问题时,探讨是否可以避免输入不好的参数避免参数嗅探也同样依赖写法。低效写法有很多中,不可能在这里一一列出。隐式转换是常见的情况,同样会导致索引失效。
总结:
- SQL Server如何编译动态SQL
理解性能的奥秘——应用程序中慢,SSMS中快(5)——案例:如何应对参数嗅探的更多相关文章
- 理解性能的奥秘——应用程序中慢,SSMS中快(6)——SQL Server如何编译动态SQL
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(5)--案例:如何应对参数嗅探 我们抛开参数嗅探的话题,回到了本系列的最 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(4)——收集解决参数嗅探问题的信息
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(3)--不总是参数嗅探的错 前面已经提到过关于存储过程在SSMS中运行很 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(3)——不总是参数嗅探的错
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(2)--SQL Server如何编译存储过程 在我们开始深入研究如何处理 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(2)——SQL Server如何编译存储过程
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(1)--简介 本文介绍SQL Server如何编译存储过程并使用计划缓存 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(1)——简介
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 在工作中发现有不少类似的现象,有幸看到国外大牛写的一篇文章,由于已经完善得不能再添油加醋,所以决定直接翻译,原文出处:http ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(4)收集解决参数嗅探问题的信息
---从计划缓存中直接获取查询计划和参数: ), ) SELECT @dbname = 'hydee_连锁', @procname = 'dbo.p_select_ware'; WITH baseda ...
- [转]提高 Linux 上 socket 性能,加速网络应用程序的 4 种方法
原文链接:http://www.ibm.com/developerworks/cn/linux/l-hisock.html 使用 Sockets API,我们可以开发客户机和服务器应用程序,它们可以在 ...
- 1 开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你。 本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括: 正确的使用数据库MetaData方法 只获取需要的数据 选用最佳性能的功能 管理连
1 开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你. 本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的 ...
- 【SQL server初级】数据库性能优化三:程序操作优化
数据库优化包含以下三部分,数据库自身的优化,数据库表优化,程序操作优化.此文为第三部分 数据库性能优化三:程序操作优化 概述:程序访问优化也可以认为是访问SQL语句的优化,一个好的SQL语句是可以减少 ...
随机推荐
- phantomjs 开发爬虫框架
函数 page.childframescount page.childframesname page.close page.currentframename page.deletelater page ...
- [LeetCode] Sentence Similarity 句子相似度
Given two sentences words1, words2 (each represented as an array of strings), and a list of similar ...
- [BZOJ 4916]神犇和蒟蒻
Description 很久很久以前,有一只神犇叫yzy; 很久很久之后,有一只蒟蒻叫lty; Input 请你读入一个整数N;1<=N<=1E9,A.B模1E9+7; Output 请你 ...
- codeforces 815C Karen and Supermarket
On the way home, Karen decided to stop by the supermarket to buy some groceries. She needs to buy a ...
- 51nod 1204 Parity(并查集应用)
1204 Parity 题目来源: Ural 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 你的朋友写下一串包含1和0的串让你猜,你可以从中选择一个连续的子串 ...
- hdu 5465 (树状数组 + 博弈)
题意:基于矩阵的NIM游戏,求异或和. 思路:在x1,y1 到 x2, y2的异或和 = A[ x2 ][ y2 ] ^ A[x1-1][ y2 ] ^ A[ x2 ][y1 - 1] ^ A[ x ...
- hdu 3954 线段树 (标记)
Level up Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- 浅谈java中内置的观察者模式与动态代理的实现
一.关于观察者模式 1.将观察者与被观察者分离开来,当被观察者发生变化时,将通知所有观察者,观察者会根据这些变化做出对应的处理. 2.jdk里已经提供对应的Observer接口(观察者接口)与Obse ...
- IE下iframe跨域session和cookie失效问题的解决方案
http://blog.csdn.net/wauit/article/details/9875157
- 线性表 linear_list 顺序存储结构
可以把线性表看作一串珠子 序列:指其中的元素是有序的 注意last和length变量的内在关系 注意:将元素所占的空间和表长合并为C语言的一个结构类型 静态分配的方式,分配给一个固定大小的存储空间之后 ...