MySQL5.6:一个神奇的执行计划

今天遇到一个非常神奇的sql执行计划时好时坏,我们一起来领略一下吧

废话不多说,直接进入实战

环境

* version:MySQL5.6.27 社区版

* 表结构

CREATE TABLE `xx` (
  `TagId` int(11) NOT NULL AUTO_INCREMENT COMMENT '',
  `TagType` int(11) DEFAULT NULL COMMENT '',
  `SubType` int(11) DEFAULT NULL COMMENT '',
  `CommId` int(11) NOT NULL DEFAULT '0' COMMENT '',
  `TagFlag` int(11) NOT NULL DEFAULT '0' COMMENT '',
  `TagName` varchar(255) DEFAULT NULL COMMENT '',
  `OrderId` int(11) DEFAULT '0' COMMENT '',
  `Unum` int(10) NOT NULL DEFAULT '0' COMMENT '',
  `IsBest` int(11) NOT NULL DEFAULT '0' COMMENT '',
  `BrokerId` int(11) NOT NULL DEFAULT '0' COMMENT '',
  `AddDate` int(11) DEFAULT NULL COMMENT '',
  `UpdateDate` int(11) DEFAULT NULL COMMENT '',
  `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `tmpnum` int(10) DEFAULT '0' COMMENT '',
  `cityid` int(11) DEFAULT '0' COMMENT '',
  PRIMARY KEY (`TagId`),
  KEY `idx_4` (`IsBest`,`TagFlag`,`CommId`),
  KEY `idxnew` (`UpdateDate`),
  KEY `idx_lc_1` (`TagName`,`TagType`,`TagId`),
  KEY `idx_lc_2` (`CommId`,`TagName`,`TagType`),
  KEY `idx_tagName_brokerId_cityId` (`TagName`,`BrokerId`,`cityid`),
  KEY `idx_lc_3` (`SubType`,`TagType`,`cityid`),
) ENGINE=InnoDB AUTO_INCREMENT=20628140 DEFAULT CHARSET=utf8

DB症状

1. slow query 非常多
2. thread_running 非常多
3. cpu 90%
4. too many connection

多症齐发

定位问题

很明显就是去寻找slow query,毕竟slow是我衡量DB性能重要标准。

然后发现99%都是类似这样的语句:

# Time: 170304 10:32:07
# User@Host[] @  []  Id: 26019853
# Query_time: 0.251174  Lock_time: 0.000078 Rows_sent: 1  Rows_examined: 470135
SET timestamp=1488594727;
select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = '1'  and `TagName` = '**高'  order by `TagId` ASC limit 1 ;

分析问题

  • step1:查看执行计划
explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = '1'  and `TagName` ='**高'  order by `TagId`  limit 1;
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
| id | select_type | table           | type  | possible_keys                        | key     | key_len | ref  | rows | Extra       |
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | xx | index | idx_lc_1,idx_tagName_brokerId_cityId | PRIMARY | 4       | NULL |  175 | Using where |
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

这条语句执行时间是: 0.99s

奇怪,从表结构上看,应该会使用idx_lc_1才对,为什么执行计划是错的呢?
  • step2:第二反应
会不会是TagType是int类型,但是sql语句确实字符串呢?隐士类型转换的导致的执行计划出错之前也是碰到过的。
试试吧,

explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = 1  and `TagName` ='**高'  order by `TagId`  limit 1;
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
| id | select_type | table           | type  | possible_keys                        | key     | key_len | ref  | rows | Extra       |
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | xx | index | idx_lc_1,idx_tagName_brokerId_cityId | PRIMARY | 4       | NULL |  175 | Using where |
+----+-------------+-----------------+-------+--------------------------------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

这条语句执行时间是: 0.89s

还是非常缓慢,看来不是这个原因。
  • step3:会不会是数据的问题呢?
因为从slow的分布看,基本上都是`TagName` ='**高' 的slow,其他的值也没发现,所以开始怀疑value,调整下看看呢

explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = 1  and `TagName` ='%%高'  order by `TagId`  limit 1;
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
| id | select_type | table           | type | possible_keys                        | key      | key_len | ref         | rows | Extra                              |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
|  1 | SIMPLE      | xx | ref  | idx_lc_1,idx_tagName_brokerId_cityId | idx_lc_1 | 773     | const,const |    3 | Using index condition; Using where |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+

这条语句执行时间:0.00s

哇塞,0s就解决战斗,但是这又是为什么呢?

再试一下:将‘**’高,换成‘*高’

explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = 1  and `TagName` ='*高'  order by `TagId`  limit 1;
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
| id | select_type | table           | type | possible_keys                        | key      | key_len | ref         | rows | Extra                              |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
|  1 | SIMPLE      | xx | ref  | idx_lc_1,idx_tagName_brokerId_cityId | idx_lc_1 | 773     | const,const |    3 | Using index condition; Using where |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+

执行计划也正确,执行时间也非常快。

然后笃定的认为问题找到了,竟然是 ‘**’导致的。

当我自己给自己sleep 10s 之后,开始思考,这是为什么呢? 等值匹配跟*有关系吗?

  • step4: 再次调整语句
* 去掉limit呢? 因为limit是执行计划的杀手,这个我想大部分DBA知道的吧。。。

explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = 1  and `TagName` ='*高'  order by `TagId`  ;
+----+-------------+-----------------+------+--------------------------------------+-----------------------------+---------+-------+-------+----------------------------------
------------------+
| id | select_type | table           | type | possible_keys                        | key                         | key_len | ref   | rows  | Extra
                  |
+----+-------------+-----------------+------+--------------------------------------+-----------------------------+---------+-------+-------+----------------------------------
------------------+
|  1 | SIMPLE      | xx | ref  | idx_lc_1,idx_tagName_brokerId_cityId | idx_tagName_brokerId_cityId | 768     | const | 13854 | Using index condition; Using wher
e; Using filesort |
+----+-------------+-----------------+------+--------------------------------------+-----------------------------+---------+-------+-------+----------------------------------
------------------+

惊奇的发现,执行计划再次发生了改变。。。。

idx_tagName_brokerId_cityId 为什么又冒出来了呢?

那我们再回头看看表结构:

PRIMARY KEY (`TagId`),
  KEY `idx_4` (`IsBest`,`TagFlag`,`CommId`),
  KEY `idxnew` (`UpdateDate`),
  KEY `idx_lc_1` (`TagName`,`TagType`,`TagId`),
  KEY `idx_lc_2` (`CommId`,`TagName`,`TagType`),
  KEY `idx_tagName_brokerId_cityId` (`TagName`,`BrokerId`,`cityid`),
  KEY `idx_lc_3` (`SubType`,`TagType`,`cityid`)

去掉干扰项后:

PRIMARY KEY (`TagId`),
`idx_lc_1` (`TagName`,`TagType`,`TagId`),
`idx_tagName_brokerId_cityId` (`TagName`,`BrokerId`,`cityid`),

执行计划竟然没有选择idx_lc_1,而是idx_tagName_brokerId_cityId,那么这个肯定是干扰索引。

所以,就更加清晰的定位到idx_tagName_brokerId_cityId索引的问题,然后开始调整这个索引,主要是第一个字段TagName的干扰,选择性的问题。

将:  KEY `idx_tagName_brokerId_cityId` (`TagName`,`BrokerId`,`cityid`) =>  KEY `idx_tagName_brokerId_cityId` (`BrokerId`,`TagName`,`cityid`)
  • step 5: 再次观察执行计划

explain select `TagId`,`TagType`,`SubType`,`CommId`,`TagFlag`,`TagName`,`OrderId`,`Unum`,`IsBest`,`BrokerId`,`AddDate`,`UpdateDate`,`updatetime`,`tmpnum`,`cityid` from `xx` where `TagType` = 1  and `TagName` ='**高'  order by `TagId`  limit 1;
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
| id | select_type | table           | type | possible_keys                        | key      | key_len | ref         | rows | Extra                              |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+
|  1 | SIMPLE      | xx | ref  | idx_lc_1,idx_tagName_brokerId_cityId | idx_lc_1 | 773     | const,const |    3 | Using index condition; Using where |
+----+-------------+-----------------+------+--------------------------------------+----------+---------+-------------+------+------------------------------------+

sql执行时间:0.00s

总结

  • 至此,问题已经解决,第一个前缀索引是如此的重要。
  • 索引调优是门艺术

展望

  • 以后如何调整和优化类似的索引执行计划呢?
原则: 高索引基数的filed,必须放前面。
  • 希望MySQL的优化器以后越来越强大
时间: 2024-11-10 13:10:03

MySQL5.6:一个神奇的执行计划的相关文章

SQL参数化查询的另一个理由 命中执行计划_MsSql

1概述 SQL语言的本质就是一串伪代码,表达的是做什么,而不是怎么做的意思.如其它语言一样,SQL语句需要编译之后才能运行,所以每一条SQL是需要通过编译器解释才能运行的(在这之间还要做SQL的优化).而这些步骤都是需要运行成本,所以在数据库中有一个叫做执行计划的东西,编译器会将编译过后的SQL存入执行计划当中,当遇到同样的SQL时,就直接调用执行计划来执行,而不需要再次编译. 通过对上面执行计划的认识,为了提高数据库运行的效率,我们需要尽可能的命中执行计划,这样就可以节省运行时间. 2相关SQ

SQL参数化查询的另一个理由 命中执行计划

1概述 SQL语言的本质就是一串伪代码,表达的是做什么,而不是怎么做的意思.如其它语言一样,SQL语句需要编译之后才能运行,所以每一条SQL是需要通过编译器解释才能运行的(在这之间还要做SQL的优化).而这些步骤都是需要运行成本,所以在数据库中有一个叫做执行计划的东西,编译器会将编译过后的SQL存入执行计划当中,当遇到同样的SQL时,就直接调用执行计划来执行,而不需要再次编译. 通过对上面执行计划的认识,为了提高数据库运行的效率,我们需要尽可能的命中执行计划,这样就可以节省运行时间. 2相关SQ

一个执行计划异常变更引发的Oracle性能诊断优化

最近有一个OLTP应用使用的Oracle数据库突然出现性能问题,DBA发现有一些delete语句执行时间骤长,消耗大量系统资源,导致应用响应时间变长积Q.   辅助信息: 应用已经很久未做过更新上线了. 据开发人员反馈,从之前的应用日志看,未出现处理时间逐步变长的现象. 这是一套RAC+DG的环境,11g的版本. 这次突然出现大量执行时间超长的SQL语句,是一条删除语句,delete from table where key1=:1 and key2=:2 and ...(省略此案例不会用到的其

一个执行计划异常变更的案例 - 外传之rolling invalidation

刚做完一次网络切换支持,得空写一篇,其实今儿取了巧,这篇文章是之前写过的,碰巧又是这次"执行计划异常变更"案例涉及的一个知识点,所以再次翻出来. 之前的几篇文章: <一个执行计划异常变更的案例 - 前传> <一个执行计划异常变更的案例 - 外传之绑定变量窥探> <一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法> 做性能测试,有一条SQL,使用了绑定变量,查看V$SQLAREA发现version_count是2, 查看V$SQL,发现有

一个执行计划异常变更的案例 - 正传

之前的几篇文章: <一个执行计划异常变更的案例 - 前传> <一个执行计划异常变更的案例 - 外传之绑定变量窥探> <一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法> <一个执行计划异常变更的案例 - 外传之rolling invalidation> <一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)> <一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法> <一个执

如何产生ORACLE执行计划

五.如何产生执行计划 要为一个语句生成执行计划,可以有3种方法: 1.最简单的办法 Sql> set autotrace on Sql> select * from dual; 执行完语句后,会显示explain plan 与 统计信息. 这个语句的优点就是它的缺点,这样在用该方法查看执行时间较长的sql语句时,需要等待该语句执行成功后,才返回执行计划,使优化的周期大大增长. 如果不想执行语句而只是想得到执行计划可以采用: Sql> set autotrace traceonly 这样,

《Oracle高性能SQL引擎剖析:SQL优化与调优机制详解》一2.5 执行计划中其他信息的含义

2.5 执行计划中其他信息的含义 通过DBMS_XPLAN输出执行计划,除了计划本身外,还可以获得一些其他信息帮助我们进一步分析执行计划及语句性能. 2.5.1 查询块和对象别名 在使用DBMS_XPLAN显示执行计划时,选择'ADVANCED'预定义格式作为参数或者加入'ALIAS'控制字符串,可以在输出中看到以下内容: Query Block Name / Object Alias (identified by operation id): -------------------------

SQL点滴27—性能分析之执行计划

原文:SQL点滴27-性能分析之执行计划 一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<Inside Microsoft SQL Server 2008>,有经验的高手尽管拍砖把.   这个部分将讲解一些性能分析工具,这些性能分许主要关注在执行计划.   缓存执行计划  SQL Server 2008提供了一些服务器对象来分析执行计划Sys.dm_exec_cached_plans:   

SQL Server查询优化器:最佳执行计划

我们知道,查询优化器的基本的目标就是为我们的查询语句找出一个比较高效的执行计划.即使是一个非常简单的查询,也会存在很多的不同方式去访问数 据,而这些不同的方式都是可以得到相同的结果的,所以,查询优化器必须要很"明智的"从这些大量的执行计划中找出了一个"最佳"的出来. 前一篇:浅析SQL Server查询优化器的工作原理 为了得到最好的计划,查询优化器必须在某些条件的限制下,尽可能多的创建和评估大量的候选执行计划.看到这里,就有一点需要注意了"查询优化器是尽