理解性能的奥秘——应用程序中慢,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语句是可以减少 ...
随机推荐
- JS实现手机访问pc网址自动跳转到wap网站
之前写pc端直接跳转wap端一直是后端java写的,跟js一样都是根据navigator.userAgent来判断设备是电脑还是手机的,我知道这种前端也可已完成的功能,只是后台比较强势,本人本着以和为 ...
- [LeetCode] Distribute Candies 分糖果
Given an integer array with even length, where different numbers in this array represent different k ...
- Redis安装与卸载
Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.它支持丰富的数据类型,和高速的内存读写.正在逐步取代memca ...
- [NOI 2010]能量采集
Description 题库链接 给你一个 \(n\times m\) 的坐标轴.对于坐标轴的每一个正整数整点 \((x,y)\) 其对答案产生的贡献为 \(2k+1\) ,其中 \(k\) 表示这个 ...
- [SDOI 2009]Elaxia的路线
Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间.Elaxia和w**每天都要奔波于宿舍和实验室之间, ...
- [HNOI2004]树的计数
题目描述 输入输出格式 输入格式: 输入文件第一行是一个正整数n,表示树有n个结点.第二行有n个数,第i个数表示di,即树的第i个结点的度数.其中1<=n<=150,输入数据保证满足条件的 ...
- 素数密度_NOI导刊2011提高(04)
题目描述 给定区间[L, R](L <= R <= 2147483647,R-L <= 1000000),请计算区间中素数的个数. 输入 两个数 L 和 R. 输出 一行,区间中素数 ...
- 计蒜客NOIP2017提高组模拟赛(三)day2-数三角形
传送门 这题有点坑啊 设A为两边颜色不同的角,B为两边颜色相同的角 那么考虑三种三角形:异色,同色,其他 对于任何一个异色三角形,一定会有三个颜色不同的角, 对于任何一个同色三角形,一定会有零个颜色不 ...
- web框架和django基础(粗糙版)
web框架本质: 浏览器:socket客户端 服务器:socket服务端 1.自己写socket服务端(最傻) #!/usr/bin/env python ...
- win10下python环境变量设置
我用的是python_2.7.3.msi,从官网下载之后,一路按照默认进行安装. 安装之后配置环境变量的步骤如下: 1,点“我的电脑”,右键选“属性” 2,选择“高级系统设置”--->选“环境变 ...