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选择的执行计划性能底下原因分析--实战案例分析的更多相关文章

  1. MySQL优化-》执行计划和常见索引

    MySql的explain执行计划 explain是一个Mysql性能显示的工具,它显示了MySQL如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句.在开发当 ...

  2. mysql 中语句执行的顺序以及查询处理阶段的分析

    原文链接:http://www.php.cn/mysql-tutorials-408865.html 本篇文章给大家带来的内容是关于mysql中语句执行的顺序以及查询处理阶段的分析,有一定的参考价值, ...

  3. MySQL Index--关联条件列索引缺失导致执行计划性能不佳

    某系统反馈慢SQL影响生产,查看SLOW LOG发现下面慢SQL: SELECT COUNT(DISTINCT m.batch_no) FROM ob_relation r INNER JOIN ob ...

  4. 学会使用MySQL的Explain执行计划,SQL性能调优从此不再困难

    上篇文章讲了MySQL架构体系,了解到MySQL Server端的优化器可以生成Explain执行计划,而执行计划可以帮助我们分析SQL语句性能瓶颈,优化SQL查询逻辑,今天就一块学习Explain执 ...

  5. MySQL优化从执行计划开始(explain超详细)

    前言 小伙伴一定遇到过这样反馈:这页面加载数据太慢啦,甚至有的超时了,用户体验极差,需要赶紧优化: 反馈等同于投诉啊,多有几次,估计领导要找你谈话啦. 于是不得不停下手里头的活,赶紧进行排查,最终可能 ...

  6. MySql 的SQL执行计划查看,判断是否走索引

    在select窗口中,执行以下语句: set profiling =1; -- 打开profile分析工具show variables like '%profil%'; -- 查看是否生效show p ...

  7. MySQL优化之执行计划

    前言 研究SQL性能问题,其实本质就是优化索引,而优化索引,一个非常重要的工具就是执行计划(explain),它可以模拟SQL优化器执行SQL语句,从而让开发人员知道自己编写的SQL的运行情况. 执行 ...

  8. mysql数据库备份执行计划

    为什么需要数据备份?如果数据库因为人为或其他不可控的因素导致数据库数据丢失或损坏,导致的后果将会非常严重. 为什么需要执行计划?备份操作如果每天人工管理的话,将会非常麻烦,需要借助工具来制定执行计划, ...

  9. (4) MySQL中EXPLAIN执行计划分析

    一. 执行计划能告诉我们什么? SQL如何使用索引 联接查询的执行顺序 查询扫描的数据函数 二. 执行计划中的内容 SQL执行计划的输出可能为多行,每一行代表对一个数据库对象的操作 1. ID列 ID ...

随机推荐

  1. pat1044. Shopping in Mars (25)

    1044. Shopping in Mars (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Shop ...

  2. web.xml文件配置详解以及实例说明

    1.web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是web应用的配置文件. 2.部署描述符文件就像所有XML文件一样,必须以一个XML头开始.这个头声明可以使用的XML版本并给出 ...

  3. [转]【无私分享:ASP.NET CORE 项目实战(第十四章)】图形验证码的实现

    本文转自:http://www.cnblogs.com/yuangang/p/6000460.html 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 很长时间没有来更新博客 ...

  4. EFCodeFirst 数据迁移问题~

    问题描述:将项目从TFS载下来  然后敲update-database 进行数据迁移 提示:Update-Database : 无法将“Update-Database”项识别为 cmdlet.函数.脚 ...

  5. mysql-查询的案例

    查询每个专业的男生人数和女生人数分别是多少 #方式一: select count(*) 个数,sex,majorid from student group by sex,majorid; #方式二: ...

  6. lunix重启service network restart错误Job for network.service failed. See 'system 或Failed to start LSB: Bring

    1.mac地址不对 通过ip addr查看mac地址,然后修改cd /etc/sysconfig/network-scripts/目录下的文件里面的mac地址 2.通过以下方法 systemctl s ...

  7. SQL SERVER 错误代码 0x534

    解决办法就是修改一下登陆名: ALTER LOGIN [G-PC\zqwang]   WITH NAME=[新的机器名\zqwang]; 然后查询一下 Service Broker 队列, 里面已经有 ...

  8. ecommerce学习

     http://blog.csdn.net/dhx20022889/article/details/8977121 

  9. Swagger2:常用注解说明

    Swagger2常用注解说明 Spring Boot : Swagger 2使用教程:https://www.cnblogs.com/JealousGirl/p/swagger.html 这里只讲述@ ...

  10. jQuery + Stimulsoft.Report 选择gridview多行打印

    jQuery + Stimulsoft.Report 报表空间打印多个ID split函数,多行ID 1. 获取选择多行ID(前台) //按钮-打印 function PrinterWorkQuali ...