前言

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

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

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

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

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

分析

表上的字段既然都有索引,那么按照之前的文章分析,是两个字段都可以走上索引的。

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

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

mysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00';
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys  | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | testtable | ALL  | idx_bizdate_st | NULL | NULL    | NULL | 980626 | Using where |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)-- 只查询biz_date
-- 关键点:rows:980626;type:ALLmysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys  | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | testtable | ALL  | idx_bizdate_st | NULL | NULL    | NULL | 980632 | Using where |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)-- 查询biz_date + status
-- 关键点:rows:980632;type:ALLmysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2 limit 100;
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
| id | select_type | table     | type  | possible_keys  | key            | key_len | ref  | rows   | Extra                 |
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
|  1 | SIMPLE      | testtable | range | idx_bizdate_st | idx_bizdate_st | 6       | NULL | 490319 | Using index condition |
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
1 row in set (0.00 sec)-- 查询biz_date + status+ limit
-- 关键点:rows:490319;mysql > select count(*)  from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.34 sec)mysql > select count(*)  from testtable   WHERE biz_date <= '2017-08-21 00:00:00';
+----------+
| count(*) |
+----------+
|   970183 |
+----------+
1 row in set (0.33 sec)mysql > select count(*)  from testtable;
+----------+
| count(*) |
+----------+
|   991421 |
+----------+
1 row in set (0.19 sec)mysql > select distinct biz_status from testtable;
+------------+
| biz_status |
+------------+
|          1 |
|          2 |
|          4 |
+------------+

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

  • 通过 biz_date 预估出来的行数 和 biz_date + status=2 预估出来的行数几乎一样,为98w。

  • 实际查询表 biz_date + status=2 一条记录都没有。

  • 整表数据量达到了99万,MySQL发现通过索引扫描需要98w行(预估)

因此,MySQL通过统计信息预估的时候,发现需要扫描的索引行数几乎占到了整个表,放弃了使用索引,选择了走全表扫描。

那是不是他的统计信息有问题呢?我们重新收集了下表统计信息,发现执行计划的预估行数还是一样,猜测只能根据组合索引的第一个字段进行预估(待确定)。

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

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

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

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

大家应该都听说过要选择性好的字段放在组合索引的最前面?

选择性好的索引在前面并不是对所有的场景都通用的,这个场景可以将status放前面,sql速度会更快。

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

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

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;
Empty set (0.00 sec)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;
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
| id | select_type | table            | type  | possible_keys  | key            | key_len | ref  | rows | Extra                 |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | testtable        | range | idx_bizdate_st | idx_bizdate_st | 6       | NULL |  789 | Using index condition |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)-- rows降低了很多,乖乖的走了索引mysql > desc select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' ;
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
| id | select_type | table            | type  | possible_keys  | key            | key_len | ref  | rows | Extra                 |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | testtable        | range | idx_bizdate_st | idx_bizdate_st | 5       | NULL | 1318 | Using index condition |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)-- 即使没有status,也是肯定走索引啦

小结

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

来源:http://www.fordba.com/optimize-an-amazing-mysql-slowlog.html

公众号后台回复【2T】有惊喜礼包!

关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

猜你喜欢

1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别

9、2T架构师学习资料干货分享

一个奇怪的MySQL慢查询,打懵了一群不懂业务的DBA!相关推荐

  1. mysql查询错误_一个奇怪的MySQL查询错误

    t_user表的phone_number字段是varchar(255)类型的,表示手机号, 在查询某个手机号时,sql语句如下: SELECT phone_number  FROM t_user WH ...

  2. 分享一个mysql 复杂查询的例子

    发布:脚本学堂/MySQL  编辑:thebaby   2013-08-23 09:37:37  [大 中 小] 有关mysql复杂查询的一个例子,正在学习mysql的朋友,可以作为一个参考. 在my ...

  3. Delphi XE 使用 MySQL 数据库一个奇怪的问题

    Delphi XE 使用 MySQL 数据库一个奇怪的问题 我用的是自带的 ADO 组件连接 MYSQL ,连接很简单,如下: 1.下载一个 MYSQL 驱动安装上 下载地址 2.连接串 Adoc.C ...

  4. mysql中查询一个字段属于哪一个数据库中的哪一个表的方式

    mysql中查询一个字段具体是属于哪一个数据库的那一张表:用这条语句就能查询出来,其中 table_schema 是所在库, table_name 是所在表 --mysql中查询某一个字段名属于哪一个 ...

  5. mysql复杂查询示例_找到时间和内存复杂性之间的平衡-一个示例

    mysql复杂查询示例 by Anmol Uppal 通过Anmol Uppal 找到时间和内存复杂性之间的平衡-一个示例 (Finding the balance between time and ...

  6. mysql查询到最新记录就停止_使用Limit参数优化MySQL查询 在找到一个记录后将停止查询...

    优化 MySQL 查询的 Limit 参数 我们在做一些查询的时候总希望能避免数据库引擎做全表扫描,因为全表扫描时间长,而且其中大部分扫描对客户端而言是没有意义的.那么在 MySQL 中有那些方式是可 ...

  7. mysql分组查询选择数量最多的前十个_mysql 查询每一个分组前N条记录

    mysql 查询每一个分组前N条记录html 假设存在表movie,  有字段 id, part(地区), mcount(观看次数)mysql 现查询每一个地区观看次数最多的3部movie, 则表sq ...

  8. JSP+Mysql 做一个简单的学生成绩查询web系统

    IDEA基于JSP+Mysql 做一个简单的学生成绩查询web系统 目录 一.相关软件的下载和配置环境 Java环境的下载和配置 (1)IDEA的下载和安装 ​ (2)JdK的安装 Tomcat的下载 ...

  9. mysql 不在另一个表中_MySQL选择查询从表中选择不在另一个表中的行?

    对于我们的示例,我们将创建两个表并应用"自然左联接"以从第二个表中不存在的表中获取行. 创建第一个表.mysql> create table FirstTableDemo - ...

  10. mysql 执行计划不对_关于mysql主从查询执行计划不一致问题的分析

    最近面试过程中被面试官抛了一个问题,说曾经有一个线上出现的奇怪的问题,主库和从库各种配置是一致的,当数据量比较大的时候,某些时候同样的查询,在从库里的执行计划执行成功了,而主库里没有执行这个执行计划, ...

最新文章

  1. 敏捷开发第二阶段个人(二)
  2. 根据GPS经纬度查找指定范围内的对象
  3. 计算机系统—CPU结构和内部工作
  4. 印度版的“大众点评”如何将 Food Feed 业务从 Redis 迁移到 Cassandra
  5. php apd,PHP debug 工具 之 APD
  6. C# 获取项目程序路径的10种方法
  7. 基于sklearn分析特征工程(特征预处理、特征选择、降维)
  8. 【Listener】监听器基础
  9. event mpm php,Apache下三种MPM模式:prefork,worker和event
  10. java 农历公历转换_java 版本 农历 公历 转换
  11. 大话设计模式:第27章 解释器模式
  12. 一个人能懒到什么地步?
  13. C++类的声明和类的实现分开写(.hpp和.cpp)
  14. C语言扫雷游戏代码以及基本原理教学(一看就会)
  15. C语言回调函数的定义和写法
  16. 【前端】elementUI分页场景下对表格进行排序
  17. 深度学习 噪声抑制_使用深度学习抑制噪声
  18. 错误: 0x80072efe
  19. Lightroom无法在卷计算机上,lightroom无法正常启动怎么办?解决lightroom无法启动方法...
  20. The multi-part request contained parameter data (excluding uploaded files) that exceeded the limit f

热门文章

  1. bug宝典linux篇 LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory(转)
  2. runtime之ivar内存布局篇
  3. mysql命令常用参数实例讲解
  4. Remoting示例
  5. Curl 方式实现POST提交数据
  6. NetBeans 6.9 正式版发布!
  7. 如何使用 AirDrop 将 MAC 中的照片整理好,并上传到 iPhone 的相册中?
  8. PhotoMill X for Mac(图片批处理工具)
  9. Sketch 80 for mac(矢量绘图设计软件)
  10. CF1067D Computer Game