MySQL · 捉虫动态 · 5.6中ORDER BY + LIMIT 错选执行计划

简介: 问题描述create table t1(id int auto_increment primary key, a int, b int, c int, v varchar(1000), key iabc(a,b,c), key ic(c)) engine = innodb;insert into t1 select null,null,null,null,null;insert into

问题描述

create table t1(id int auto_increment primary key, a int, b int, c int, v varchar(1000), key iabc(a,b,c), key ic(c)) engine = innodb;

insert into t1 select null,null,null,null,null;
insert into t1 select null,null,null,null,null from t1;
insert into t1 select null,null,null,null,null from t1;
insert into t1 select null,null,null,null,null from t1;
insert into t1 select null,null,null,null,null from t1;
insert into t1 select null,null,null,null,null from t1;

update t1 set a=id/2, b=id/4, c=6-id/8, v=repeat('a',1000);

explain select id from t1 where a<3 and b in (1, 13) and c>=3 order by c desc limit 2;
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                                    |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+
|  1 | SIMPLE      | t1    | index | iabc,ic       | iabc | 15      | NULL |   32 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+

explain select id from t1 force index (iabc) where a<3 and b in (1, 13) and c>=3 order by c desc limit 2;
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                                    |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+
|  1 | SIMPLE      | t1    | range | iabc          | iabc | 5       | NULL |    3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------------+

从SELECT语句中可以看出,同样的语句,使用同样的INDEX,但使用了FORCE INDEX之后选择的执行计划不一样。当然如果数据量大的话,实际的执行性能也会差别很大。使用RANGE scan显然要优于INDEX scan的全扫描。

另外此bug引发的另一个问题是,由于使用了LIMIT语句,导致选择的INDEX不是最优的INDEX。

问题分析:

使用如下命令打开Optimizer trace

SET OPTIMIZER_TRACE_MAX_MEM_SIZE=268435456(i.e. 256M);
SET optimizer_trace="enabled=on";

执行上面的查询语句,可以看到optimizer trace的输出结果如下,请注意里面重点部位的注释(以’//’开头部分):

"considered_execution_plans": [\
              {\
                "plan_prefix": [\
                ],\
                "table": "`t1`",\
                "best_access_path": {\
                  "considered_access_paths": [\
                    {\
                      "access_type": "range",\  
					  // 这里我们可以看到,优化器通过代价选择的最佳访问方式是RANGE scan
                      "rows": 3,\
                      "cost": 2.2146,\
                      "chosen": true\
                    }\
                  ]\
                },\
                "cost_for_plan": 2.2146,\
                "rows_for_plan": 3,\
                "chosen": true\
              }\
            ]\
但是接下来我们可以看到:
       "refine_plan": [\
              {\
                "table": "`t1`",\
				
                "access_type": "index_scan"\ //这里强制使用了INDEX scan
              }\
            ]\

这里显示的是optimizer在执行优化器的第四个阶段,PLAN REFINEMENT的时候,最后选择了INDEX scan。所以我们可以大致确定错误发生的地方。另外有问题的query带有LIMIT,因此基本可以确定问题发生在了make_join_select函数中。

make_join_select函数中有下面一段逻辑:

if (!tab->const_keys.is_clear_all() &&               // 有依赖于常量的索引条件表达式
                   i == join->const_tables &&        // 是第一个非常量表
                   (join->unit->select_limit_cnt <
                    tab->position->records_read) &&  
					// 有Limit条件且需要返回的行数比估计扫描的行数少
                   !(join->select_options & OPTION_FOUND_ROWS))  // 没有SQL_CALC_FOUND_ROWS
            recheck_reason= LOW_LIMIT;  // 这里MySQL开始对Limit语句进行优化
			...
// 检查是否有RANGE scan可以使用
if ((recheck_reason != DONT_RECHECK) &&
                sel->test_quick_select(thd, usable_keys,
                                       used_tables & ~tab->table->map,
                                       (join->select_options &
                                        OPTION_FOUND_ROWS ?
                                        HA_POS_ERROR :
                                        join->unit->select_limit_cnt),
                                       false,   // don't force quick range
                                       interesting_order) < 0)
            {
这里usable_keys是描述可以用来对ORDER BY列进行索引排序的可能的所有索引的MAP。上面的函数会查找这些可用的索引是否可以进行更高效RANGE
扫描。但是通过问题query的条件表达式,这里没有找到对应的RANGE扫描,所以最后的执行计划输出只是使用了一个COVERING index.

问题解决

解决方式是需要将原来已经选好的RANGE scan与用来进行排序的索引扫描代价进行比较,比较哪种扫描方式对于增加ORDER BY操作后的代价更低,进而选择一个代价最优的扫描方式。下面是一个相关的patch。

if (!tab->const_keys.is_clear_all() &&               // 有依赖于常量的索引条件表达式
                   i == join->const_tables &&        // 是第一个非常量表
                   (join->unit->select_limit_cnt <
                    tab->position->records_read) &&  // 有Limit条件且需要返回的行数比估计的扫描的行数少
                   !(join->select_options & OPTION_FOUND_ROWS))  // 没有SQL_CALC_FOUND_ROWS
            recheck_reason= LOW_LIMIT;  //这里MySQL会去对Limit语句进行优化
			...
+              if (recheck_reason != DONT_RECHECK)
               {
-                recheck_reason= DONT_RECHECK;
+                int best_key= -1;
+                ha_rows select_limit= join->unit->select_limit_cnt;
+
+                // 对所有可用的INDEX计算排序代价,选择一个代价最优的INDEX
				 // 注意:这里的usable_keys包含所有可用索引,而不只是原来版本中只包含可以用来排序的索引
+                test_if_cheaper_ordering(tab, join->order, tab->table,
+                                         usable_keys, -1, select_limit,
+                                         &best_key, &read_direction,
+                                         &select_limit);
				 // 如果没有找到任何可用的INDEX,那就默认使用原来的扫描方式
+                if (best_key < 0)
+                  recheck_reason= DONT_RECHECK; // No usable keys
+                else
+                {
+                  // 找到一个最优的INDEX,我们只需要设置可用的INDEX,接下来查看一下是否有RANGE scan即可
+                  usable_keys.clear_all();
+                  usable_keys.set_bit(best_key);
+                  interesting_order= (read_direction == -1 ? ORDER::ORDER_DESC :
+                                      ORDER::ORDER_ASC);
+                }
               }
             }
if ((recheck_reason != DONT_RECHECK) &&
                sel->test_quick_select(thd, usable_keys,
                                       used_tables & ~tab->table->map,
                                       (join->select_options &
                                        OPTION_FOUND_ROWS ?
                                        HA_POS_ERROR :
                                        join->unit->select_limit_cnt),
                                       false,   // don't force quick range
                                       interesting_order) < 0)
            {
			...

可以看到最终效果是:

EXPLAIN SELECT id FROM t1 WHERE a<3 AND b IN (1, 13) AND c>=3 ORDER BY c DESC LIMIT 2;
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	SIMPLE		t1		range	iabc,ic			iabc	5	NULL	4	Using index condition; Using filesort
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
10月前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
缓存 关系型数据库 MySQL
MySQL执行计划选择策略:揭秘查询优化的艺术
【10月更文挑战第15天】 在数据库性能优化中,选择最优的执行计划是提升查询效率的关键。MySQL作为一个强大的关系型数据库管理系统,提供了复杂的查询优化器来生成执行计划。本文将深入探讨如何选择合适的执行计划,以及为什么某些计划更优。
382 2
|
11月前
|
监控 关系型数据库 MySQL
|
9月前
|
SQL 算法 搜索推荐
mysql 之order by工作流程
本文深入解析了MySQL中`ORDER BY`的排序机制,通过具体示例展示了排序过程及性能优化方法。文章首先分析了基于内存和磁盘的排序方式,包括`sort_buffer_size`的影响以及临时文件的使用场景。接着介绍了`rowid`排序算法,该算法通过减少参与排序的数据量来提升性能,并对比了其与传统排序的区别。此外,还探讨了随机查询`ORDER BY RAND()`的执行流程及其优化策略。最后提到了MySQL 5.6引入的优先队列排序算法,适用于仅需部分有序结果的场景。文章结合`optimizer_trace`工具详细说明了各配置参数对排序行为的影响,为优化查询提供了实用指导。
147 1
mysql 之order by工作流程
|
SQL 缓存 关系型数据库
MySQL Limit实现原理
本文深入解析了MySQL中`LIMIT`子句的实现原理及其在分页、性能优化等场景下的应用技巧。文章详细介绍了`LIMIT`的基本语法、MySQL内部处理流程,以及如何通过索引优化、覆盖索引等策略提升分页查询的性能,并提供了实践建议。
844 3
|
缓存 关系型数据库 MySQL
MySQL执行计划深度解析:如何做出最优选择
【10月更文挑战第23天】 在数据库查询性能优化中,执行计划的选择至关重要。MySQL通过查询优化器来生成执行计划,但有时不同的执行计划会导致性能差异。理解如何选择合适的执行计划,以及为什么某些计划更优,对于数据库管理员和开发者来说是一项必备技能。
767 2
|
关系型数据库 MySQL PostgreSQL
postgresql和mysql中的limit使用方法
postgresql和mysql中的limit使用方法
469 1
|
SQL 关系型数据库 MySQL
美团面试:Mysql如何选择最优 执行计划,为什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴面试美团时遇到了关于MySQL执行计划的面试题:“MySQL如何选择最优执行计划,为什么?”由于缺乏系统化的准备,小伙伴未能给出满意的答案,面试失败。为此,尼恩为大家系统化地梳理了MySQL执行计划的相关知识,帮助大家提升技术水平,展示“技术肌肉”,让面试官“爱到不能自已”。相关内容已收录进《尼恩Java面试宝典PDF》V175版本,供大家参考学习。
|
SQL 搜索推荐 关系型数据库
MySQL 如何实现 ORDER BY 排序?
本文详细解析了MySQL中`ORDER BY`的实现原理及优化方法。通过解析与优化、执行及多种优化技术,如索引利用、内存排序、外部排序等,帮助你提升排序性能。了解其背后的机制,可显著优化查询效率。
750 4
|
SQL 搜索推荐 关系型数据库
MySQL 如何实现 ORDER BY 排序?
在实际开发中,我们经常会使用 MySQL 的 `ORDER BY`进行排序,那么,`ORDER BY`是如何实现的排序的?我们该如何优化 `ORDER BY`的排序性能?这篇文章,我们来聊一聊。
181 4

相关产品

  • 云数据库 RDS MySQL 版
  • 推荐镜像

    更多