MySQL选择的执行计划性能底下原因分析--实战案例分析
MySQL是自动会选择它认为好的执行划,但是MySQL毕竟是程序,还没有达到像人类思考这么智能,还是通过一些按部就班的算法实现最优执行计划(基于cost)的选择。下面就是一个真实的案例,带你来看看MySQL也有失误的时候,这种情况不在少数。 注意:一下分析是在MySQl5.6.16版本下,其它版本未验证。 表结构:
CREATE TABLE `test_tab` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token` varchar(100) NOT NULL,
`user_id` int(10) NOT NULL DEFAULT '',
`a` int(10) NOT NULL DEFAULT '',
`b` int(10) NOT NULL,
`t_logo` varchar(255) NOT NULL DEFAULT '',
`t_name` varchar(50) NOT NULL,
`t_contact_name` varchar(20) NOT NULL,
`z` varchar(20) NOT NULL,
`c` varchar(30) DEFAULT NULL,
`d` varchar(100) NOT NULL DEFAULT '',
`e` varchar(100) NOT NULL DEFAULT '',
`t_province` varchar(50) DEFAULT NULL,
`f` varchar(50) DEFAULT NULL,
`t_district` varchar(50) DEFAULT NULL,
`g` varchar(100) NOT NULL,
`t_info` text NOT NULL,
`h` char(1) NOT NULL DEFAULT '',
`i` tinyint(3) DEFAULT '',
`j` decimal(10,2) NOT NULL DEFAULT '0.00' ,
`t_add_time` int(10) NOT NULL DEFAULT '' ,
`t_update_time` int(10) NOT NULL DEFAULT '' ',
`t_begin_time` int(10) NOT NULL DEFAULT '0' ,
`t_end_time` int(10) NOT NULL DEFAULT '0' ,
`k` char(1) NOT NULL DEFAULT '1' ,
`t_is_check` char(1) NOT NULL DEFAULT '0' ,
`l` int(10) NOT NULL DEFAULT '0',
`u` int(10) NOT NULL DEFAULT '0',
`is_delete` tinyint(1) unsigned NOT NULL DEFAULT '0' ,
`p` varchar(50) DEFAULT NULL,
`sort` int(11) NOT NULL DEFAULT '999',
PRIMARY KEY (`id`),
KEY `z` (`z`),
KEY `t_name` (`t_name`),
KEY `token` (`token`,`sort`),
KEY `idx_0` (`token`,`user_id`,`is_delete`),
KEY `idx_doc_time` (`t_add_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
表记录数: select count(*) from user.`test_tab`;
+----------+
| count(*) |
+----------+
| 2865011 |
+----------+
执行sql:
SELECT `id`,`t_name` FROM user.`test_tab` WHERE `token` = 'xx_8cilc73a22hm' AND `t_is_check` = 1 AND `is_delete` = 0 ORDER BY id desc LIMIT 5;
+---------+--------------------------------+
| id | t_name |
+---------+--------------------------------+
| 2983295 | 《管理之道》 |
| 2983289 | 《气功是怎么炼成丹》 |
| 2925294 | 小推车配送 |
| 2925292 | 特色小拉面 |
| 1101709 | 惠世本源 |
+---------+--------------------------------+
5 rows in set (1.25 sec) --可以看到这个sql花了1.25秒查询出来了结果, 这是不可接受的速度 我们来看看执行计划:
explain SELECT `id`,`t_name` FROM user.`test_tab` WHERE `token` = 'xx_8cilc73a22hm' AND `t_is_check` = 1 AND `is_delete` = 0 ORDER BY id desc LIMIT 5
+----+-------------+--------------------+-------+-----------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------------+-------+-----------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | test_tab | index | token,idx_0 | PRIMARY | 4 | NULL | 1185 | Using where |
+----+-------------+--------------------+-------+-----------------+---------+---------+------+------+-------------+
说明:执行计划看着很正常,使用了主键索引,没啥大问题,为什么速度这么慢呢?因为主键字段没有范围条件限制,要把所有数据读出来,所以慢了!!! 我们换个索引看看执行效率:
SELECT `id`,`t_name` FROM user.`test_tab` force index(token) WHERE `token` = 'xx_8cilc73a22hm' AND `t_is_check` = 1 AND `is_delete` = 0 ORDER BY id desc LIMIT 5;
+---------+--------------------------------+
| id | t_name |
+---------+--------------------------------+
| 2983295 | 《管理之道》 |
| 2983289 | 《气功是怎么炼成丹》 |
| 2925294 | 小推车配送 |
| 2925292 | 特色小拉面 |
| 1101709 | 惠世本源 |
+---------+--------------------------------+
5 rows in set (0.03 sec) --速度这么快,比走主键索引快多了 看下执行计划: explain SELECT `id`,`t_name` FROM user.`test_tab` force index(token) WHERE `token` = 'xx_8cilc73a22hm' AND `t_is_check` = 1 AND `is_delete` = 0 ORDER BY id desc LIMIT 5
+----+-------------+--------------------+------+---------------+-------+---------+-------+-------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------------+------+---------------+-------+---------+-------+-------+----------------------------------------------------+
| 1 | SIMPLE | test_tab | ref | token | token | 302 | const | 11506 | Using index condition; Using where; Using filesort |
+----+-------------+--------------------+------+---------------+-------+---------+-------+-------+----------------------------------------------------+
说明:看着执行计划有Using filesort为什么速度会快呢?看看Using index condition这个原来使用了索引下推,也就是说从从存储引擎获得的数据只有5行,然后再进行排序,所以速度很快,哈哈!!这也就是为什么快的原因了。 那为什么MySQL没有选择这个执行token索引呢?
下面我们看看具体原因,如果要看详细执行计划,我们之前是不是说过要看什么呢?optimizer_trace!!回答正确。
set optimizer_trace=1;
sql语句
set
optimizer_trace=0;
select * from information_schema.optimizer_trace; 下面就是我们要看的走主键索引慢的详细的执行计划:
| SELECT `id`,`t_name` FROM user.`test_tab` WHERE `token` = 'xx_8cilc73a22hm' AND `t_is_check` = 1 AND `is_delete` = 0 ORDER BY id desc LIMIT 5 | {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `user`.`test_tab`.`id` AS `id`,`user`.`test_tab`.`t_name` AS `t_name` from `user`.`test_tab` where ((`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1) and (`user`.`test_tab`.`is_delete` = 0)) order by `user`.`test_tab`.`id` desc limit 5"
}
]
}
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "((`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1) and (`user`.`test_tab`.`is_delete` = 0))",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "((`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1) and multiple equal(0, `user`.`test_tab`.`is_delete`))"
},
{
"transformation": "constant_propagation",
"resulting_condition": "((`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1) and multiple equal(0, `user`.`test_tab`.`is_delete`))"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "((`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1) and multiple equal(0, `user`.`test_tab`.`is_delete`))"
}
]
}
},
{
"table_dependencies": [
{
"table": "`user`.`test_tab`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
]
}
]
},
{
"ref_optimizer_key_uses": [
{
"table": "`user`.`test_tab`",
"field": "token",
"equals": "'xx_8cilc73a22hm'",
"null_rejecting": false
},
{
"table": "`user`.`test_tab`",
"field": "token",
"equals": "'xx_8cilc73a22hm'",
"null_rejecting": false
}
]
},
{
"rows_estimation": [
{
"table": "`user`.`test_tab`",
"range_analysis": {
"table_scan": {
"rows": 2597778,
"cost": 572230
},
"potential_range_indices": [
{
"index": "PRIMARY",
"usable": false, --可以看到根据范围条件选择,主键索引是false的,也就是范围条件选择主键是不好,那为啥实际为啥还使用主键呢?往下看。。。
"cause": "not_applicable"
},
{
"index": "z",
"usable": false,
"cause": "not_applicable"
},
{
"index": "t_name",
"usable": false,
"cause": "not_applicable"
},
{
"index": "token",
"usable": true, --范围查询意向选择token索引。这没有问题啊!!!再往下看
"key_parts": [
"token",
"sort",
"id"
]
},
{
"index": "idx_0",
"usable": true,
"key_parts": [
"token",
"user_id",
"is_delete",
"id"
]
},
{
"index": "idx_doc_time",
"usable": false,
"cause": "not_applicable"
}
],
"setup_range_conditions": [
],
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
},
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "token",
"ranges": [
"xx_8cilc73a22hm <= token <= xx_8cilc73a22hm"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"rows": 11506,
"cost": 13808,
"chosen": true
},
{
"index": "idx_0",
"ranges": [
"xx_8cilc73a22hm <= token <= xx_8cilc73a22hm"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"rows": 10960,
"cost": 13153,
"chosen": true
}
],
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
},
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "idx_0",
"rows": 10960,
"ranges": [
"xx_8cilc73a22hm <= token <= xx_8cilc73a22hm"
]
},
"rows_for_plan": 10960,
"cost_for_plan": 13153,
"chosen": true
}
}
}
]
},
{
"considered_execution_plans": [
{
"plan_prefix": [
],
"table": "`user`.`test_tab`",
"best_access_path": {
"considered_access_paths": [
{
"access_type": "ref",
"index": "token",
"rows": 11506,
"cost": 13807,
"chosen": true
},
{
"access_type": "ref",
"index": "idx_0",
"rows": 10960,
"cost": 13152,
"chosen": true
},
{
"access_type": "range",
"cause": "heuristic_index_cheaper",
"chosen": false
}
]
},
"cost_for_plan": 13152,
"rows_for_plan": 10960,
"chosen": true
}
]
},
{
"attaching_conditions_to_tables": {
"original_condition": "((`user`.`test_tab`.`is_delete` = 0) and (`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1))",
"attached_conditions_computation": [
],
"attached_conditions_summary": [
{
"table": "`user`.`test_tab`",
"attached": "((`user`.`test_tab`.`is_delete` = 0) and (`user`.`test_tab`.`token` = 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1))"
}
]
}
},
{
"clause_processing": {
"clause": "ORDER BY",
"original_clause": "`user`.`test_tab`.`id` desc",
"items": [
{
"item": "`user`.`test_tab`.`id`"
}
],
"resulting_clause_is_simple": true,
"resulting_clause": "`user`.`test_tab`.`id` desc"
}
},
{
"refine_plan": [
{
"table": "`user`.`test_tab`",
"pushed_index_condition": "((`user`.`test_tab`.`is_delete` = 0) and (`user`.`test_tab`.`token` = 'xx_8cilc73a22hm'))",
"table_condition_attached": "(`user`.`test_tab`.`t_is_check` = 1)"
}
]
},
{
"added_back_ref_condition": "((`user`.`test_tab`.`token` <=> 'xx_8cilc73a22hm') and (`user`.`test_tab`.`t_is_check` = 1))"
},
{
"reconsidering_access_paths_for_index_ordering": {
"clause": "ORDER BY",
"index_order_summary": {
"table": "`user`.`test_tab`",
"index_provides_order": true, --关键来了,在判断排序的时候发现主键字段id能够排序,所以MySQL认为排序是花费很大的操作,使用这个主键字段是有序的,不用排序了,就使用它吧!!!!!我靠这不就是错了吗。虽然排序消耗时间,但是你为啥不判断下排序结果集大小啊,扫描所有数据(2865011)和排序5行(这里是排序1000多行还是5行不确定,就当mysql最优5行吧)数据哪个消耗更低?
"order_direction": "desc",
"disabled_pushed_condition_on_old_index": true,
"index": "PRIMARY",
"plan_changed": true,
"access_type": "index_scan"
}
}
}
]
}
},
{
"join_execution": {
"select#": 1,
"steps": [
]
}
}
]
}
总结下:
1.MySQL在进行分析的时候会把等值、范围的执行消耗都分析出来。
2.在最后选择上选择了不用排序的主键索引。 为什么会选择错误?我推测原因:可能下索引条件下推还没有完善到执行计划中,没有判断索引下推的情况。 给我们的启发:不要完全相信MySQL,需要自己去验证,发现问题,MySQL不是十全十美的,还需要有很多改善!!!!!!
MySQL选择的执行计划性能底下原因分析--实战案例分析的更多相关文章
- MySQL优化-》执行计划和常见索引
MySql的explain执行计划 explain是一个Mysql性能显示的工具,它显示了MySQL如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句.在开发当 ...
- mysql 中语句执行的顺序以及查询处理阶段的分析
原文链接:http://www.php.cn/mysql-tutorials-408865.html 本篇文章给大家带来的内容是关于mysql中语句执行的顺序以及查询处理阶段的分析,有一定的参考价值, ...
- MySQL Index--关联条件列索引缺失导致执行计划性能不佳
某系统反馈慢SQL影响生产,查看SLOW LOG发现下面慢SQL: SELECT COUNT(DISTINCT m.batch_no) FROM ob_relation r INNER JOIN ob ...
- 学会使用MySQL的Explain执行计划,SQL性能调优从此不再困难
上篇文章讲了MySQL架构体系,了解到MySQL Server端的优化器可以生成Explain执行计划,而执行计划可以帮助我们分析SQL语句性能瓶颈,优化SQL查询逻辑,今天就一块学习Explain执 ...
- MySQL优化从执行计划开始(explain超详细)
前言 小伙伴一定遇到过这样反馈:这页面加载数据太慢啦,甚至有的超时了,用户体验极差,需要赶紧优化: 反馈等同于投诉啊,多有几次,估计领导要找你谈话啦. 于是不得不停下手里头的活,赶紧进行排查,最终可能 ...
- MySql 的SQL执行计划查看,判断是否走索引
在select窗口中,执行以下语句: set profiling =1; -- 打开profile分析工具show variables like '%profil%'; -- 查看是否生效show p ...
- MySQL优化之执行计划
前言 研究SQL性能问题,其实本质就是优化索引,而优化索引,一个非常重要的工具就是执行计划(explain),它可以模拟SQL优化器执行SQL语句,从而让开发人员知道自己编写的SQL的运行情况. 执行 ...
- mysql数据库备份执行计划
为什么需要数据备份?如果数据库因为人为或其他不可控的因素导致数据库数据丢失或损坏,导致的后果将会非常严重. 为什么需要执行计划?备份操作如果每天人工管理的话,将会非常麻烦,需要借助工具来制定执行计划, ...
- (4) MySQL中EXPLAIN执行计划分析
一. 执行计划能告诉我们什么? SQL如何使用索引 联接查询的执行顺序 查询扫描的数据函数 二. 执行计划中的内容 SQL执行计划的输出可能为多行,每一行代表对一个数据库对象的操作 1. ID列 ID ...
随机推荐
- 硬盘和显卡的访问与控制(一)——《x86汇编语言:从实模式到保护模式》读书笔记01
本文是<x86汇编语言:从实模式到保护模式>(电子工业出版社)的读书实验笔记. 这篇文章我们先不分析代码,而是说一下在Bochs环境下如何看到实验结果. 需要的源码文件 第一个文件是加载程 ...
- Linux下的NFS快速配置教程与安全策略
[51CTO专稿]在Linux下实现文件共享有多种方式,NFS就是其中之一.网络文件系统(NFS)协议是由Sun MicroSystem在20世纪80年代为了提供对共享文件的远程访问而设计和实现的.该 ...
- pat1080. Graduate Admission (30)
1080. Graduate Admission (30) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue It ...
- HDU 4342——History repeat itself——————【数学规律】
History repeat itself Time Limit: 1000ms Memory Limit: 32768KB This problem will be judged on HDU. O ...
- 如何在Chrome粘贴图片直接上传
背景 截图或页面复制图片,可以直接通过Ctrl+v 粘贴上传图片 原理 操作:复制(截图)=>粘贴=>上传 监听粘贴事件=>获取剪贴板里的内容=>发请求上传 浏览器:Chrom ...
- CSS中的样式层叠机制Cascade
CSS中的样式层叠机制Cascade 一.样式冲突 样式冲突是CSS渲染过程要解决的一个关键问题,样式冲突主要由两个原因造成: 元素包含了不同对象所赋予的样式:浏览器.用户.作者.其中,浏览器样式 ...
- Android setUserVisibleHint-- fragment真正的onResume和onPause方法
这个情况仅适合与多个fragment之间切换时统计,而非activity和fragment同时交互,因当时项目为首页4个fargment时长统计,因此适合,经下面网友评论指出,特在这里写出此问题,因最 ...
- 图片延迟插件 Jquery.lazyload.min.js
当一个页面打开的图片太多,我们可以用jquery的一个延迟加载插件.名为:jquery.lazyload.min.js 使用非常简单,如下: <div style="height:70 ...
- XCode 如何真机运行别人的demo项目
iOS应用安装到真机需要证书和mobileprovision 文件,拿到别人的项目 是没有这些的 ,也就运行不起来. 要想运行起来, 需要选中项目, target - > 修改 bundlei ...
- hibernate 注解不给提示
1.alt + / 会给提示 2.上面这种稍微麻烦一点,如果需要写了@就直接给提示,就需要设置一下: a)Window - preferences b)搜 content assist,选中 Java ...