SQL调优 - Hints指定索引 解决慢查询案例
背景
每当交易高峰时期,可能会暴露一些平时无法发现的问题,机遇和挑战并存。下面聊聊最近解决的一个案例,因为执行计划走错导致慢查询,进而引发应用线程阻塞、线程池爆满,最后应用功能瘫痪。如何标本兼治的解决问题,需要很多思考。
问题分析
step1 应用瘫痪
用户反应某查询功能一直处于加载中,并出现错误提示。查看后台应用日志,调用远程查询服务出现大量超时。
step2 线程池爆满
通过jstack命令来分析查询服务jvm线程堆栈,发现设定的线程池已经满了,而且大部分线程阻塞在了数据库查询阶段(如下图),看来是有很多慢查询。
step3 慢查询的由来 - 错误执行计划
为何忽然出现这么多慢查询?我们来分析一下:
SELECT no, time
FROM tablename
where no = :1
and time >= to_date(:2, 'yyyy-mm-dd')
and time < to_date(:3, 'yyyy-mm-dd')
ORDER BY time DESC
注:条件字段(no、time)分别有索引(idx_no、idx_time)。
经过DBA帮助,分析上段sql的执行计划发现:
-
有时数据库会走idx_no索引,查询很快
-
有时会走idx_time索引,查询很慢
解决方案
既然数据库自动生成的执行计划有时会出问题,那么我们可以用hints语句指定当前sql走哪条索引,以固定执行计划。
下面以Oracle为例,添加hints:
SELECT /*+ index(tablename IDX_NO) */ no, time
FROM tablename
where no = :1
and time >= to_date(:2, 'yyyy-mm-dd')
and time < to_date(:3, 'yyyy-mm-dd')
ORDER BY time DESC
如果感觉在sql中添加不方便的话,oracle 10g以上版本提供了SQL Profile方式。
总结思考
这个问题应该一直有,为何在交易高峰时段才表现出来值得去研究。
数据库:平时数据库压力较小,出现几个走错执行计划的慢查询,也只是让数据库压力比均值高出一小部分,性能未受影响。但是,高峰时段数据库本身就处于一个高负载状态,这时出现的慢查询无疑是雪上加霜,接近某个临界点时性能就明显下滑。
应用:慢查询会导致线程阻塞等待,一直占用线程池资源。我们知道,用户在点击查询按钮后发现一直加载,会不知觉多点几遍。这样的话,线程数量暴涨且大部分处于阻塞状态,线程池满了之后应用就会瘫痪掉,使其他正常的接口也无法正常工作。
综上所述,数据层的优化是治本,同时还要借助一些手段来保证不出现雪崩式性能下降。比如,在前台页面添加重复提交限制,尽量避免短时间内产生大量慢查询。
参考资料
Oracle Hints:https://docs.oracle.com/cd/B12037_01/server.101/b10752/hintsref.htm#5156
Oracle SQL Profile:https://docs.oracle.com/database/121/TGSQL/tgsql_profiles.htm#TGSQL596