转自:http://fordba.com/optimize-an-amazing-mysql-slowlog.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

一、前言

开发需要定期的删除表里一定时间以前的数据,SQL如下

  1. mysql > delete from testtable WHERE biz_date <= '2017-08-21 00:00:00' AND status = 2 limit 500\G

前段时间在优化的时候,已经在相应的查询条件上加上了索引

  1. KEY `idx_bizdate_st` (`biz_date`,`status`)

但是实际执行的SQL依然非常慢,为什么呢,我们来一步步分析验证下


二、

表上的字段既然都有索引,那么按照之前的文章分析,是两个字段都可以走上索引的。如果有疑问,请参考文章 10分钟让你明白MySQL是如何利用索引的

既然能够利用索引,表的总大小也就是200M左右,那么为什么形成了慢查呢?

我们查看执行计划,去掉limit 后,发现他选择了走全表扫描。

  1. mysql > desc select * from testtable WHERE biz_date <= '2017-08-21 00:00:00';
  2. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | testtable | ALL | idx_bizdate_st | NULL | NULL | NULL | 980626 | Using where |
  6. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  7. 1 row in set (0.00 sec)
  8.  
  9. -- 只查询biz_date
  10. -- 关键点:rows:980626;type:ALL
  11.  
  12. mysql > desc select * from testtable WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
  13. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  14. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  15. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  16. | 1 | SIMPLE | testtable | ALL | idx_bizdate_st | NULL | NULL | NULL | 980632 | Using where |
  17. +----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
  18. 1 row in set (0.00 sec)
  19.  
  20. -- 查询biz_date + status
  21. -- 关键点:rows:980632;type:ALL
  22.  
  23. mysql > desc select * from testtable WHERE biz_date <= '2017-08-21 00:00:00' and status = 2 limit 100;
  24. +----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
  25. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  26. +----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
  27. | 1 | SIMPLE | testtable | range | idx_bizdate_st | idx_bizdate_st | 6 | NULL | 490319 | Using index condition |
  28. +----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
  29. 1 row in set (0.00 sec)
  30.  
  31. -- 查询biz_date + status+ limit
  32. -- 关键点:rows:490319;
  33.  
  34. mysql > select count(*) from testtable WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
  35. +----------+
  36. | count(*) |
  37. +----------+
  38. | 0 |
  39. +----------+
  40. 1 row in set (0.34 sec)
  41.  
  42. mysql > select count(*) from testtable WHERE biz_date <= '2017-08-21 00:00:00';
  43. +----------+
  44. | count(*) |
  45. +----------+
  46. | 970183 |
  47. +----------+
  48. 1 row in set (0.33 sec)
  49.  
  50. mysql > select count(*) from testtable;
  51. +----------+
  52. | count(*) |
  53. +----------+
  54. | 991421 |
  55. +----------+
  56. 1 row in set (0.19 sec)
  57.  
  58. mysql > select distinct biz_status from whwtestbuffer;
  59. +------------+
  60. | biz_status |
  61. +------------+
  62. | 1 |
  63. | 2 |
  64. | 4 |
  65. +------------+

通过以上查询,我们可以发现如下几点问题:

  • 通过 biz_date 预估出来的行数 和 biz_date + status=2 预估出来的行数几乎一样,为98w。
  • 实际查询表 biz_date + status=2 一条记录都没有
  • 整表数据量达到了99万,MySQL发现通过索引扫描需要98w行(预估)

因此,MySQL通过统计信息预估的时候,发现需要扫描的索引行数几乎占到了整个表,放弃了使用索引,选择了走全表扫描。
那是不是他的统计信息有问题呢?我们重新收集了下表统计信息,发现执行计划的预估行数还是一样,猜测只能根据组合索引的第一个字段进行预估(待确定)

那我们试下直接强制让他走索引呢?

  1. mysql > select * from testtable WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
  2. Empty set (0.79 sec)
  3.  
  4. mysql > select * from testtable force index(idx_bizdate_st) WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
  5. Empty set (0.16 sec)

我们发现,强制指定索引后,查询耗时和没有强制索引比较,的确执行速度快了很多,因为没有强制索引是全表扫描嘛!但是!依然非常慢!

那么还有什么办法去优化这个本来应该很快的查询呢?

大家应该都听说过要选择性好的字段放在组合索引的最前面?
是的,相对于status字段,biz_date 的选择性更加不错,那组合索引本身已经没有好调整了

那,能不能让他不要扫描索引的那么多范围呢?之前的索引模型中也说过,MySQL是通过索引去确定一个扫描范围,如果能够定位到尽可能小的范围,那是不是速度上会快很多呢?

并且业务逻辑上是定期删除一定日期之前的数据。所以逻辑上来说,每次删除都是只删除一天的数据,直接让SQL扫描一天的范围。那么我们就可以改写SQL啦!

  1. mysql > select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' and status = 2;
  2. Empty set (0.00 sec)
  3.  
  4. mysql > desc select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' and status = 2;
  5. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  8. | 1 | SIMPLE | testtable | range | idx_bizdate_st | idx_bizdate_st | 6 | NULL | 789 | Using index condition |
  9. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  10. 1 row in set (0.00 sec)
  11.  
  12. -- rows降低了很多,乖乖的走了索引
  13.  
  14. mysql > desc select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' ;
  15. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  16. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  17. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  18. | 1 | SIMPLE | testtable | range | idx_bizdate_st | idx_bizdate_st | 5 | NULL | 1318 | Using index condition |
  19. +----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
  20. 1 row in set (0.00 sec)
  21.  
  22. -- 即使没有status,也是肯定走索引啦

三、

这个问题,我原本打算用hint,强制让他走索引,但是实际上强制走索引的执行时间并不能带来满意的效果。结合业务逻辑,来优化SQL,是最好的方式,也是终极法宝,一定要好好利用。不了解业务的DBA,不是一个好DBA... 继续去补业务知识去了。。

文章导航

本文目前有2条留言,欢迎发表评论!

  1. dikang123

    为什么不用pt-archive呢?

  2. 开发者头条

    感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/t0gxlp 欢迎点赞支持!
    欢迎订阅《fordba》https://toutiao.io/subjects/266914

Write a Reply or Comment

一个不可思议的MySQL慢查分析与解决的更多相关文章

  1. MySQL死锁问题分析及解决方法实例详解(转)

      出处:http://www.jb51.net/article/51508.htm MySQL死锁问题是很多程序员在项目开发中常遇到的问题,现就MySQL死锁及解决方法详解如下: 1.MySQL常用 ...

  2. <转>一个最不可思议的MySQL死锁分析

    1 死锁问题背景 1 1.1 一个不可思议的死锁 1 1.1.1 初步分析 3 1.2 如何阅读死锁日志 3 2 死锁原因深入剖析 4 2.1 Delete操作的加锁逻辑 4 2.2 死锁预防策略 5 ...

  3. 一个最不可思议的MySQL死锁分析

    1    死锁问题背景    1 1.1    一个不可思议的死锁    1 1.1.1    初步分析    3 1.2    如何阅读死锁日志    3 2    死锁原因深入剖析    4 2. ...

  4. 改进动态设置query cache导致额外锁开销的问题分析及解决方法-mysql 5.5 以上版本

    改进动态设置query cache导致额外锁开销的问题分析及解决方法 关键字:dynamic switch for query cache,  lock overhead for query cach ...

  5. mysql慢查日志分析工具 percona-toolkit

    备忘自: http://blog.csdn.net/seteor/article/details/24017913 1. 工具简介 pt-query-digest是用于分析mysql慢查询的一个工具, ...

  6. Java中9种常见的CMS GC问题分析与解决

    1. 写在前面 | 本文主要针对 Hotspot VM 中"CMS + ParNew"组合的一些使用场景进行总结.重点通过部分源码对根因进行分析以及对排查方法进行总结,排查过程会省 ...

  7. 海量数据分析更快、更稳、更准。GaussDB(for MySQL) HTAP只读分析特性详解

    本文作者康祥,华为云数据库内核开发工程师,研究生阶段主要从事SPARQL查询优化相关工作.目前在华为公司参与华为云GaussDB(for MySQL) HTAP只读内核功能设计和研发. 1. 引言 H ...

  8. [转]一个用户SQL慢查询分析,原因及优化

    来源:http://blog.rds.aliyun.com/2014/05/23/%E4%B8%80%E4%B8%AA%E7%94%A8%E6%88%B7sql%E6%85%A2%E6%9F%A5%E ...

  9. MySQL 5.7 Command Line Client输入密码后闪退和windows下mysql忘记root密码的解决办法

    MySQL 5.7 Command Line Client输入密码后闪退的问题: 问题分析: 1.查看mysql command line client默认执行的一些参数.方法:开始->所有程序 ...

随机推荐

  1. Python面向对象篇(1)-类和对象

    面向对象编程 1.编程范式   我们写代码的目的是什么?就是为了能够让计算机识别我们所写的代码并完成我们的需求,规范点说,就是通过编程,用特定的语法+数据结构+特殊算法来让计算机执行特定的功能,实现一 ...

  2. voip技术研究

    voip:是一种通过ip现实电话通信的技术统称 sip:voip现在一般都采用sip协议 参考资料: android sip学习 问题: SipManager.newInstance(this)为nu ...

  3. linux centos下安装dokuwiki

    首先先大致介绍一下wiki: DokuWiki是一个开源wiki引擎程序,运行于PHP环境下.Doku Wiki 程序小巧而功能强大.灵活,适合中小团队和个人网站知识库的管理. DokuWiki可以与 ...

  4. 微信小程序<web-view>嵌入网页后,小程序如何和网页交互传值?

    最近开发一个项目由于小程序某些组件的限制,然后想到嵌入网页,但是遇到一个问题:网页端调取数据的时候需要 小程序传递多个参数值才能用,如何传值呢? 最初我想到是<web-view src=&quo ...

  5. 表单验证控件Verify.js

    自己工作常用到表单录入验证,就顺手写了一个验证控件,刚开始写得很烂.多年后翻出来,又优化了一下,增加了一些功能.拿出来分享分享. 主要功能就是表单的录入验证. * 1.当录入框必填时,在控件后生成红色 ...

  6. JavaScript中的 true

    经常看到有人写 如下代码,有时候也是凭经验猜想到底是什么意思,本着认真学习 JavaScript 的精神,专门写一篇去讨论这个问题. if(name){ //do something. }else{ ...

  7. <select>简易的二级联动

    1.首先是表单页面: <tr> <td align="right"> <label class="Validform_label" ...

  8. java基础,集合,Arraylist,源码解析(基础)

    ArrayList 是什么,定义? 这是动态的数组,它提供了动态的增加和减少元素,实现了List接口(List实现Collection,所以也实现Collection接口)灵活的设置数组的大小等好处 ...

  9. 【C#学习笔记之一】C#中的关键字

    C#中的关键字 关键字是对编译器具有特殊意义的预定义保留标识符.它们不能在程序中用作标识符,除非它们有一个 @ 前缀.例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字. 下面是列出的 ...

  10. Codeforces Round #451 (Div. 2)-898A. Rounding 898B.Proper Nutrition 898C.Phone Numbers(大佬容器套容器) 898D.Alarm Clock(超时了,待补坑)(贪心的思想)

    A. Rounding time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...