一次MySQL线上慢查询分析及索引使用

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

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是不一样。

参考文档

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

相关文章:
【推荐】 【译文】抽象漏洞法则
【推荐】 【译文】为什么我在亚马逊工作了5个月之后就离职了

原文地址:https://www.cnblogs.com/163yun/p/10102874.html

时间: 2025-01-10 16:02:43

一次MySQL线上慢查询分析及索引使用的相关文章

mysql性能优化-慢查询分析,优化索引最佳实践

数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显,我们究竟应该如何对MySQL数据库进行优化? 下面我就从MySQL对硬件的选择.MySQL的安装.my.cnf的优化.MySQL如何进行架构设计及数据切分,查询与索引优化分析等方面来说明这个问题. (一)服务器物理硬件的优化 在挑选硬件服务器时,我们应该从下面几个方面着重对MySQL服务器的硬件配置进行优化,也就是说将项目中的资金着重投入到如下几处: 1.磁盘寻道能力(磁盘I/O),我们现在用的都是SAS15000转的硬盘,

MySQL的慢查询分析

慢查询分析日最初是用来捕获比较“慢”的查询,在mysql5.1 + 版本中,慢查询的功能被加强,可以通过设置long_query_time为0来捕获所有的查询,而且查询的响应时间已经可以做到微妙级别. ---在MySQL的当前版本中,慢查询日志是开销最低,精确度最高的测量查询时间的工具.如果还在担心开启慢查询会带来额外的I/O开销,那大可以放心,我们在I/O密集型场景做过测试,慢查询带来的开销可以忽略不计(实际上CPU密集型场景的影响还稍大一些) 更需要担心的是日志可能会消耗掉很大的磁盘空间,因

MySQL慢日志查询分析方法与工具

MySQL中的日志包括:错误日志.二进制日志.通用查询日志.慢查询日志等等.这里主要介绍下比较常用的两个功能:通用查询日志和慢查询日志. 1)通用查询日志:记录建立的客户端连接和执行的语句. 2)慢查询日志:记录所有执行时间超过long_query_time秒的所有查询或者不使用索引的查询 MySQL日志文件系统的组成   a.错误日志:记录启动.运行或停止mysqld时出现的问题.   b.通用日志:记录建立的客户端连接和执行的语句.   c.更新日志:记录更改数据的语句.该日志在MySQL

线上慢查询的排查

最近查看慢查询日志,一直有看到SELECT * FROM tb_name的SQL语句,在之前SQL审核的时候,也没发现有这些SQL的存在,所以很好奇这里怎么出现的,后来用了vc-mysql-sniffer脚本去抓SQL来分析,也没有找出SELECT * 之类的SQL,下面我上图让分析一下: slow.log 可以看到大量的SELECT * 的SQL,如果细心的小伙伴会发现,SQL之前都有use某个库,从这里应该可以想到一般程序里不可能会出现use xxx之类的SQL,所以可以排除程序在作怪的可能

【MySQL 线上 BUG 分析】之 多表同字段异常:Column ‘xxx’ in field list is ambiguous

一.生产出错! 今天早上11点左右,我在工作休息之余,撸了一下猫.突然,工作群响了,老大在里面说:APP出错了! 妈啊,这太吓人了,因为只是说了出错,但是没说错误的信息.所以我赶紧到APP上看看. 这果然是出错了,而且还是简单而粗暴的500,太吓人了. 二.本地赶紧调试起来! 既然线上出错了,我们又不能直接进行调试,那当然得马上在本地搞起来了! 1.代码是否有错? 立马启动本地的项目,访问对应的接口,看看是不是代码哪里出错了. 好了,本地的代码和 SQL 都是没错的! 2.SQL 是否有错? 那

mysql(二) 慢查询分析(一)

如下表结构: CREATE TABLE `trade_order` ( `order_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单编号', `total_price` bigint(20) DEFAULT NULL COMMENT '订单总价', `item_name` varchar(128) DEFAULT NULL COMMENT '商品名称', `mobile` varchar(16) DEFAULT NULL C

mysql(三) 慢查询分析(二)

在一般的查询中,都要求尽量围绕创建的索引进行.针对索引,常用的有主键索引,单列索引,组合索引,索引合并等. 在评价索引时,关键看区分度.索引区分度=索引列唯一值/表记录数. 如果在区分度很低的列上建索引,那索引扫描的rows会相当大,该索引的性能表现就基本接近全表扫描了. 主键索引 是一种特殊的唯一索引,不允许有空值. 单列索引 针对表的单一列设置索引. 组合索引 针对表的多列按列顺序设置索引.在组合索引中,基于BTree的原理,存在一个最左前缀匹配原则.如下索引(mobile_price_cr

使用Apache Spark 对 mysql 调优 查询速度提升10倍以上

在这篇文章中我们将讨论如何利用 Apache Spark 来提升 MySQL 的查询性能. 介绍 在我的前一篇文章Apache Spark with MySQL 中介绍了如何利用 Apache Spark 实现数据分析以及如何对大量存放于文本文件的数据进行转换和分析.瓦迪姆还做了一个基准测试用来比较 MySQL 和 Spark with Parquet 柱状格式 (使用空中交通性能数据) 二者的性能. 这个测试非常棒,但如果我们不希望将数据从 MySQL 移到其他的存储系统中,而是继续在已有的

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

一.优化概述 二.查询与索引优化分析 1性能瓶颈定位 Show命令 慢查询日志 explain分析查询 profiling分析查询 2索引及查询优化 三.配置优化 1)      max_connections 2)      back_log 3)      interactive_timeout 4)      key_buffer_size 5)      query_cache_size 6)      record_buffer_size 7)      read_rnd_buffer