order by limit 造成优化器选择索引错误

Posted 渔夫数据库笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了order by limit 造成优化器选择索引错误相关的知识,希望对你有一定的参考价值。

 1.order by limit 选错索引示例

在日常工作中,经常发现一些简单的查询语句因为加了 order by limit 造成优化器选择索引错误。例如如下sql(此处就不造数据了,只是列出一个sql示例)

select * from test_table where name='xxxxx' order by id limit 1;

#id 列为表的主键,name列有索引。有可能该查询最高效的路径应该是使用 name 索引(name 列为前缀的组合索引)访问,但是优化器最后却选择通过主键id进行扫描。这样就可能导致查询很低效(当然查询也可能很高效,这个跟表中数据的具体分布有关)

2.order by limit 为什么会导致选错索引

 在 optimize trace中我们看到有这样一块


            "reconsidering_access_paths_for_index_ordering": 
              "clause": "ORDER BY",
              "steps": [
              ],
              "index_order_summary": 
                "table": "`t_test_part12`",
                "index_provides_order": true,
                "order_direction": "desc",
                "index": "idx_create_time",
                "plan_changed": true,       ##在 reconsider之前选择的是条件列上的索引,只需要扫描部分数据,这里的true表示访问路径发生了改变(通过排序索引顺序扫描)
                "access_type": "index"
              
            
          

##这里的重的选择索引在源码中对应到sql/sql_select.cc中如下

test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
                         key_map usable_keys,  int ref_key,
                         ha_rows select_limit,
                         int *new_key, int *new_key_direction,
                         ha_rows *new_select_limit, uint *new_used_key_parts,
                         uint *saved_best_key_parts)
sql_select.cc 在5.7时就有4400多行,我们不需要刻意去撸通它,但是关于 test_if_cheaper_ordering 有一段注释值得我们去关注,这段注释如下:
 /*
           We assume that each of the tested indexes is not correlated
           with ref_key. Thus, to select first N records we have to scan
           N/selectivity(ref_key) index entries.
           selectivity(ref_key) = #scanned_records/#table_records =
           refkey_rows_estimate/table_records.
           In any case we can't select more than #table_records.
           N/(refkey_rows_estimate/table_records) > table_records
           <=> N > refkey_rows_estimate.
          */

##首先假设被检查的索引同ref_key没有关系。其次假设数据是均匀分布的。如果数据是均匀分布的,那么通过排序索引访问前N行数据(order by limit N)需要扫描的行数为 N*(table_records/distinct(ref_key))
这两个假设是估算通过排序索引来访问cost 的前提(但是现实生产中这两个假设在绝大多数场景中都是不成立的,所以就造成多数场景下索引选择错误)


3.问题处理

  第二部分中我们知道,因为 test_if_cheaper_ordering() 假设的前提在生产环境中通常不会成立,所以就造成该类sql很多时候生成的都不是最优执行计划

##生产上遇到过通过条件索引过滤执行时间为几十毫秒,但是通过索引排序扫描耗时1小时的案例

3.1 处理方案1

主动关闭该优化器开关  prefer_ordering_index

## 该问题已经被确认为bug,但是mysql目前为止没有给出具体的优化方案,但是他们提供了一个优化器开关(mysql 8.0.21及之后版本),让用户自己选择是否需要该类型的优化,如果用户认为该类优化会导致错误的执行计划,可以主动关闭该类优化。优化器开关名为 (默认打开,可以根据需要进行关闭)

3.2 处理方案2

重写 sql ,比如上面提到的 sql ,select * from test_table where name='xxxxx' order by id limit 1;

我们业务逻辑可以分两步处理

1)select min(id) from test_table where name='xxxxx';  ##取出符合条件的主键id

2)通过第一步中得到的主键id 去获取相关信息

select * from test_table where id=11;

3.3 处理方案3

通过 hint 固化执行计划。比如通过 force index 指定使用的索引

4.相关文档

on ORDER BY optimization – domas mituzas

MySQL Bugs: #97001: Dangerous optimization reconsidering_access_paths_for_index_ordering

MySQL :: WL#13929: Introduce new optimizer switch to disable limit optimization

MySQL · 捉虫动态 · order by limit 造成优化器选择索引错误

mysql回表致索引失效_祈雨v的博客-CSDN博客_回表走索引吗

 

以上是关于order by limit 造成优化器选择索引错误的主要内容,如果未能解决你的问题,请参考以下文章

MySQL优化:order by和limit

MySQL实验 内连接优化order by+limit 以及添加索引再次改进

MySQL 进阶 索引 -- SQL优化(插入数据优化:导入本地文件数据主键优化order by优化group by优化limit优化count优化update优化)

MYSQL性能调优05_覆盖索引索引下推如何选择合适的索引Order by与Group by优化索引设计原则

使用 LIMIT 1 索引 ORDER BY

MySQL-SQL优化:主键,order by,group by,limit,count,update