本文由作者郑智辉授权网易云社区发布。

0.前言

本文通过分析线上MySQL慢查询日志,定位出现问题的SQL,进行业务场景分析,结合索引的相关使用进行数据库优化。在两次处理问题过程中,进行的思考。

1.简要描述

在九月底某个新上的游戏业务MySQL慢查询日志

# Time: 2017-09-30T14:56:13.974292+08:00
# Query_time: 6.048835  Lock_time: 0.000038 Rows_sent: 0  Rows_examined: 12884410SET timestamp=1506754573;SELECT status, sdkid, appid, app_orderid, matrix_orderid, pay_orderid, platform, sdk_version, app_channel, pay_channel, serverid, roleid, INET6_NTOA(userip), deviceid, devic
e_name, productid, product_count, product_name, matrix_uid, app_uid, order_currency, order_price, activityid, create_time, expired_time, pay_method, pay_mode, ship_url, rese
rved, pay_time, recv_time, ship_time, pay_sub_method, pay_amount, free_amount, pay_currency, pay_total_money, pay_free_money, credit, pay_fee, extra_columns, is_test    FROM MatrixOrderSucc    WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB(NOW(), interval 20 SECOND) AND recv_time > DATE_SUB(NOW(), interval 24 HOUR)    ORDER BY retry    LIMIT 1;复制代码
  • 第一次处理方式:在该表上添加了(recv_time,status)索引,然后慢查询没有;

正当以为事情解决的时候,该游戏10月份大推,然后数据量激增,然后慢查询又出现了。

  • 第二次处理方式:删除之前的索引,然后改为对(status,recv_time)添加索引。然后至今该SQL未出现慢查询了。

线上环境说明:

  • MySQL 5.7.18

  • 表引擎为Innodb

  • 系统内核:Debian 3.16.43-2

接下来说说这两次处理过程中的测试和分析。

2.SQL分析

  • sql分析:

    • 当时九月底时该表的数据达到1200w行,但是由于没有匹配得上的索引,所以全表扫描耗时6秒多。

  • 业务分析:

    • 联系了开发同事,了解一下这个语句的业务场景。 该语句用于查找失败订单(status标记)并且时间在20秒之前一天以内(recv_time)的数据。并得知其实满足status条件的订单其实只是少量的。

小结:
可以看出数据和固定时间范围内的数据量有关系。10月份大推后,固定时间范围内的数据激增。

3.第一次处理

3.1 数据情况

将数据导到测试环境进行了数据测试。
通过下图的sql,数据基本分析如下:

*  满足单独status条件的数据大概就3w条
*  满足单独recv_time条件的数据大概是77w条
*  虽然status字段的数据离散型不是很好,但是满足条件的数据很少,数据的筛选性还是很不错的。复制代码

3.2 测试

加了索引之后。(recv_time,status)

mysql> explain select status, sdkid, appid, app_orderid, matrix_orderid, pay_orderid, platform, sdk_version, app_channel, pay_channel, serverid, roleid, INET6_NTOA(userip), deviceid, device_name, productid, product_count, product_name, matrix_uid, app_uid, order_currency, order_price, activityid, create_time, expired_time, pay_method, pay_mode, ship_url, reserved, pay_time, recv_time, ship_time, pay_sub_method, pay_amount, free_amount, pay_currency, pay_total_money, pay_free_money, credit, pay_fee, extra_columns, is_test from MatrixOrderSucc WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB('2017-10-12 14:48:49', interval 20 SECOND) AND recv_time > DATE_SUB('2017-10-12-14:48:49', interval 24 HOUR);
+----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+
| id | select_type | table           | partitions | type  | possible_keys | key       | key_len | ref  | rows    | filtered | Extra                 |
+----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+
|  1 | SIMPLE      | MatrixOrderSucc | NULL       | range | recv_time     | recv_time | 6       | NULL | 1606844 |    11.11 | Using index condition |
+----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)复制代码

执行计划:刚加上的索引确实被用上了。
正式环境临时添加了该索引之后慢查询确实消失了。

隐忧:

从执行计划里的key_len可以知道该sql,在进行数据筛选的时候只以recv_time进行数据过滤的,status字段并没有用上场。因为联合索引左侧字段用了范围查询,则其他字段无法用上。

背景知识
数据查找过程:1. 如果走了辅助索引* 先去辅助索引查找。返回索引字段和主键字段(index_column, pk column),假设数据N行,那么这里是N次的数据顺序访问* 再去聚集索引查找整行数据:N次随机访问
数据搜索代价:b+树高度次随机访问+N次顺序访问+N次随机访问。
ps:当然如果辅助索引能覆盖了SQL查询的字段,就不需要去主表查完整整行数据了。2.如果直接全表扫描:
数据搜索代价:全表总数次顺序访问磁盘顺序访问和随机访问时间消耗大概查了两个数量级。所以有可能:MySQL会估算一下,两者的代价来决定是否走索引查找。复制代码

所以上面的sql在mysql 5.6之前执行过程:

  1. 通过recv_time条件在辅助索引搜索,返回N条记录

  2. 聚集索引查找整行数据

  3. 返回到server 段然后再进行status字段的条件筛选

  4. server层返回数据给客户端

然而,MySQL 5.6之后多了index condition push down的优化功能,就是能将索引筛选下推。
例如:
执行计划里的Using index condition是index push down的意思,是mysql 5.6后做的优化,
这个功能的效果就是,能将步骤3的数据筛选放在步骤2之前,因为既然从辅助索引取回的数据包含status字段,那么进行一下数据过滤,然后再去主表拿数据,就能减少随机访问的次数。

4.第二次处理

4.1 线上数据

  • 10月游戏大推每日数据激增。此时全表数据大概2800w。

  • 再去通过explain 查看执行计划的时候,已经从原来的走索引,又变回了全表扫描。

  • 慢查询的时间从之前的6秒上升到18秒

4.2 问题

  • 为什么之前走索引现在会不走了?
    有同事说:在应用层 force index强制走之前的索引就好了。因为可能是MySQL的优化器优化得不够好。导致走了不良的执行计划。 我认为:这个问题和应用问题和MySQL优化关系不大,是索引建得不对。如果在应用层做修改,第一需要经过测试回归才能发布版本,耗时长;第二,force index 感觉比较死板,万一以后表结构发生变更,这个索引不存在了,会存在问题。

线上数据分析:

  • 单独满足recv_time条件的数据达到600多万行。(因为游戏大推,每日数据激增),原来只有77w行。

  • 单独满足status条件的数据变化不大。

MySQL采用全表扫描的结论:

  • 因为辅助索引返回的数据激增,导致主表随机访问的次数增加,发现还不如直接全表扫描来得快。

当然MySQL的SQL优化代价模型应该包含很多因素,后续有待研究。

4.3 测试

还是利用之前导出的1200w的测试数据,对(status,recv_time)条件索引进行测试。
通过下图可以看到:

  • 查询能走上索引,并且key_len=10,表明索引的两列都派上用上了。

  • 并且执行计划里的rows数量明显比(recv_time,status)索引的查询要少很多。

4.4 问题

4.4.1 上文不是联合索引用了范围查询,第二列排不上用场吗? 为什么这里能用recv_time搜索数据?

我的理解:
1.status虽然在sql里看起来是范围查询,但是MySQL能感知到status数据的离散程度,然后将status查询改为IN(200),IN在MySQL里不算范围查询。
2.其实这个挺好理解的。结合索引的B+树的结构。 如果是IN,相当于在辅助索引里通过第一列得出的是N个B+子树(以第二索引字段进行构建的子树),那么肯定还是可以对第二列进行二叉树搜索的。

所以关键就是在第一列搜索完后,剩下的数据是否能对第二列recv_time进行二叉树搜索。

4.4.2 为什么recv_time范围查询没做上面的IN操作转换?

因为recv_time真的是足够离散。

4.5 索引选择

在索引选择,在有(recvtime,status) (status,recvtime) (status)三个索引下

  KEY `status` (`status`,`recv_time`),  KEY `status_2` (`status`),KEY `recv_time` (`recv_time`,`status`)mysql> explain SELECT count(*) FROM MatrixOrderSucc WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB('2017-10-12 14:48:49', interval 20 SECOND) AND recv_time > DATE_SUB('2017-10-12 14:48:49', interval 24 HOUR);
+----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+
| id | select_type | table           | partitions | type  | possible_keys             | key    | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | MatrixOrderSucc | NULL       | range | status,status_2,recv_time | status | 10      | NULL | 58650 |     8.94 | Using where; Using index |
+----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)复制代码

可以看出系统选择了(status,recv_time)索引。
因此在正式环境删除之前的索引,建新的索引,慢查询消失。

5.小结

5.1 不是离散性不好的字段就不能加索引,也要看数据筛选性能
5.2 时间类型的字段不大合适放在联合索引的左边
5.3 索引最左匹配原则 5.4 测试说明
5.4.1 数据是通过mysqldump不加锁方式导到测试环境重新import建立的。
5.4.2 测试的SQL:最好不要选select count() from table ,因为在这个场景中select count() 会走索引扫描,是不必再到主表拿整行数据的;和实际场景的SQL是不一样。

参考文档

  • 数据库索引设计概要

  • MySQL索引背后的数据结构及算法原理

  • MySQL索引原理及慢查询优化

  • B+Tree Indexes and InnoDB

  • MySQL官网手册

更多网易技术、产品、运营经验分享请访问网易云社区。

相关文章:
【推荐】 猛犸机器学习开发实践

一次MySQL线上慢查询分析及索引使用相关推荐

  1. MySQL选错索引导致的线上慢查询事故复盘

    前言 又和大家见面了!又两周过去了,我的云笔记里又多了几篇写了一半的文章草稿.有的是因为质量没有达到预期还准备再加点内容,有的则完全是一个灵感而已,内容完全木有.羡慕很多大佬们,一周能产出五六篇文章, ...

  2. 【性能优化】MySQL常用慢查询分析工具

    常用慢查询分析工具 引言 在日常的业务开发中 MySQL 出现慢查询是很常见的 大部分情况下会分为两种情况 1.业务增长太快 2.要么就是SQL 写的太xx了 所以 对慢查询 SQL 进行分析和优化很 ...

  3. 线上慢查询?试试这几个优化思路!

    点击蓝色"程序猿DD"关注我哟 加个"星标",不忘签到哦 来源:yangyidba 一.前言 不管是开发同学还是DBA,想必大家都遇到慢查询(select,up ...

  4. mysql 8.0 慢查询_MySQL慢查询分析

    [伍哥原创] 在我们做系统性能调优的时候,数据库的慢查询语句的优化是必不可少的,特别是电子商务类型的重度MYSQL应用类型. 下面我们一起来看看怎么做好MYSQL的慢查询分析吧. 1,开启MYSQL的 ...

  5. Anemometer MySQL 【慢查询日志监控平台】(实战)

    Anemometer 是一款开源的(慢查询)日志监控平台,当前主要用于 MySQL 的慢查询日志跟踪. Anemometer 演示地址:http://lab.fordba.com/anemometer ...

  6. 高性能MySQL学习——提高查询性能

    高性能MySQL学习--提高查询性能 提高查询性能 MySQL 查询优化器 MySQL 执行计划分析"三步曲" MySQL 执行计划查询分析 如何优化 SQL MySQL 自身优化 ...

  7. mysql中的强制索引_你如何强制MySQL中的查询使用索引?

    我试图通过向慢查询日志中出现的查询添加索引来提高锤击的wordpress DB的性能. 在MS SQL中,您可以使用查询提示强制查询使用索引,但如果正确覆盖列,则通常很容易获得查询以使用索引. 我有这 ...

  8. MySQL的or/in/union与索引优化

    假设订单业务表结构为: order(oid, date, uid, status, money, time, -) 其中: oid,订单ID,主键 date,下单日期,有普通索引,管理后台经常按照da ...

  9. mysql性能优化-慢查询分析、优化索引和配置

    目录 一.优化概述 二.查询与索引优化分析 1性能瓶颈定位 Show命令 慢查询日志 explain分析查询 profiling分析查询 2索引及查询优化 三.配置优化 1)      max_con ...

最新文章

  1. 基于tcp和udp的socket实现
  2. Android 仿微信朋友圈添加图片
  3. JSONP跨域jQuery处理整理(附天气数据实例)
  4. scrapy的post登录:renren
  5. list redis 怎样做排行_list类型的应用场景 —— Redis实战经验
  6. solarflare低延迟网卡_动态丨赛灵思收购solarflare,数据优先是重要布局
  7. codeforces 984 A. Game
  8. VS2005 添加lib 的方法
  9. 2013搜狐移动互联战略
  10. 使用idea和gradle编译spring5源码
  11. WebSocket is already in CLOSING or CLOSED state
  12. HFSS同轴馈电矩形贴片天线馈电点以及尺寸的计算
  13. canvas SVG webGL比较
  14. 永不放弃--一个藏羚人的感人故事
  15. 腾讯云服务器文件怎么恢复吗,实战腾讯云镜像备份恢复云服务器实例提取网站数据文件...
  16. 从原理层面掌握@RequestAttribute、@SessionAttribute的使用【享学Spring MVC】
  17. eclipse配置python django环境_windows下python+Django+eclipse开发环境的配置
  18. 一个定制CFileDialog对话框的实例
  19. 【01.14】网络安全学习day3
  20. 解决 ArcGIS Server 10.2.2 无法打开http://localhost:6080/arcgis/manager/

热门文章

  1. python 量化交易_Python量化交易,tushare与talib学习示例演示,双均线(DMA)买卖策略...
  2. python模块matplotlib.pyplot用法_Python中Matplotlib模块的简单使用
  3. thinkphp开发发卡网源码
  4. 全网最新Spring Boot2.5.1整合Activiti5.22.0企业实战教程<网关篇>
  5. checkcode.aspx 生成随即验证码
  6. Magento教程 22:如何确认订单报表?
  7. Linux中7个用来浏览网页和下载文件的命令
  8. jquery 高亮插件 highlight
  9. node.js——麻将算法(四)胡牌算法的一些优化处理方案(无赖子版)
  10. 【图说word】宏二