MySQL 5.6 order by limit 语句优化器选择索引错误

问题描述

bug 触发条件如下:

优化器先选择了 where 条件中字段的索引,该索引过滤性较好;SQL 中必须有 order by limit 从而引导优化器尝试使用 order by 字段上的索引进行优化,最终因代价问题没有成功。

复现case

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的全扫描。

问题分析

MySQL下有个了解优化器工作过程的利器,其就是optimizer_trace。使用方法为:

SET OPTIMIZER_TRACE_MAX_MEM_SIZE=268435456; 
SET optimizer_trace="enabled=on";

select id from t1 where a<3 and b in (1, 13) and c>=3 order by c desc limit 2;
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE\G

MySQL5.6的输出:

"steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `t1`.`id` AS `id` from `t1` where ((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3)) order by `t1`.`c` limit 2"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "table_dependencies": [
              {
                "table": "`t1`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`t1`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 32,
                    "cost": 9.5
                  } /* table_scan */,
                  "potential_range_indices": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "iabc",
                      "usable": true,
                      "key_parts": [
                        "a",
                        "b",
                        "c",
                        "id"
                      ] /* key_parts */
                    },
                    {
                      "index": "ic",
                      "usable": true,
                      "key_parts": [
                        "c",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indices */,
                  "best_covering_index_scan": {
                    "index": "iabc",
                    "cost": 7.4718,
                    "chosen": true
                  } /* best_covering_index_scan */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "iabc",
                        "ranges": [
                          "NULL < a < 3"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": true,
                        "rows": 3,
                        "cost": 1.6146,
                        "chosen": true
                      },
                      {
                        "index": "ic",
                        "ranges": [
                          "3 <= c"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 17,
                        "cost": 21.41,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "iabc",
                      "rows": 3,
                      "ranges": [
                        "NULL < a < 3"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 3,
                    "cost_for_plan": 1.6146,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "range",
                      "rows": 3,
                      "cost": 2.2146,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "cost_for_plan": 2.2146,
                "rows_for_plan": 3,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))",
              "attached_conditions_computation": [
                {
                  "table": "`t1`",
                  "rechecking_index_usage": {
                    "recheck_reason": "low_limit",
                    "limit": 2,
                    "row_estimate": 3,
                    "range_analysis": {
                      "table_scan": {
                        "rows": 32,
                        "cost": 40.4
                      } /* table_scan */,
                      "potential_range_indices": [
                        {
                          "index": "PRIMARY",
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "iabc",
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "ic",
                          "usable": true,
                          "key_parts": [
                            "c",
                            "id"
                          ] /* key_parts */
                        }
                      ] /* potential_range_indices */,
                      "best_covering_index_scan": {
                        "index": "iabc",
                        "cost": 7.4718,
                        "chosen": true
                      } /* best_covering_index_scan */,
                      "setup_range_conditions": [
                      ] /* setup_range_conditions */,
                      "group_index_range": {
                        "chosen": false,
                        "cause": "not_group_by_or_distinct"
                      } /* group_index_range */,
                      "analyzing_range_alternatives": {
                        "range_scan_alternatives": [
                          {
                            "index": "ic",
                            "ranges": [
                              "3 <= c"
                            ] /* ranges */,
                            "index_dives_for_eq_ranges": true,
                            "rowid_ordered": false,
                            "using_mrr": false,
                            "index_only": false,
                            "rows": 17,
                            "cost": 21.41,
                            "chosen": false,
                            "cause": "cost"
                          }
                        ] /* range_scan_alternatives */,
                        "analyzing_roworder_intersect": {
                          "usable": false,
                          "cause": "too_few_roworder_scans"
                        } /* analyzing_roworder_intersect */
                      } /* analyzing_range_alternatives */
                    } /* range_analysis */
                  } /* rechecking_index_usage */
                }
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`t1`",
                  "attached": "((`t1`.`a` < 3) and (`t1`.`b` in (1,13)) and (`t1`.`c` >= 3))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "clause_processing": {
              "clause": "ORDER BY",
              "original_clause": "`t1`.`c`",
              "items": [
                {
                  "item": "`t1`.`c`"
                }
              ] /* items */,
              "resulting_clause_is_simple": true,
              "resulting_clause": "`t1`.`c`"
            } /* clause_processing */
          },
          {
            "refine_plan": [
              {
                "table": "`t1`",
                "access_type": "index_scan"
              }
            ] /* refine_plan */
          },
          {
            "reconsidering_access_paths_for_index_ordering": {
              "clause": "ORDER BY",
              "index_order_summary": {
                "table": "`t1`",
                "index_provides_order": false,
                "order_direction": "undefined",
                "index": "unknown",
                "plan_changed": false
              } /* index_order_summary */
            } /* reconsidering_access_paths_for_index_ordering */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
          {
            "filesort_information": [
              {
                "direction": "asc",
                "table": "`t1`",
                "field": "c"
              }
            ] /* filesort_information */,
            "filesort_priority_queue_optimization": {
              "limit": 2,
              "rows_estimate": 936,
              "row_size": 22,
              "memory_available": 8388608,
              "chosen": true
            } /* filesort_priority_queue_optimization */,
            "filesort_execution": [
            ] /* filesort_execution */,
            "filesort_summary": {
              "rows": 3,
              "examined_rows": 32,
              "number_of_tmp_files": 0,
              "sort_buffer_size": 90,
              "sort_mode": "<sort_key, additional_fields>"
            } /* filesort_summary */
          }
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */

结合OPTIMIZER_TRACE的输出和MySQL5.6的代码,来复盘这个bug。由于MySQL优化器是个复杂的系统,这里不去解释每一行TRACE的输出,而是和该bug密切联系的部分,会做重点的分析。rows_estimation是用来计算一个表在不同的访问路径下(全表扫描、索引扫描、范围扫描等),数据库所要付出的代价。

table_scan:cost=io_cost+cpu_cost=(pages+1.1)+(rows*ROW_EVALUATE_COST+1 )=(1+1.1) + (32*0.2+1)=9.5

索引iabc作为覆盖索引,计算出代价为

cover_index:cost=io_cost+cpu_cost=(rows+base)/base+rows*ROW_EVALUATE_COST=7.4718

其中base的计算方式为 page_size/2/(length(iabc))

其中 length(iabc)=length(a)+length(b)+length(c)+length(id)+3

依次带入计算获得

cover_index:cost=7.4718
range:iabc:cost=io_cost+cpu_cost=(rows+base)/base + rows*ROW_EVALUATE_COST=(3+base)/base+3*0.2=1.6146
range:ic:cost=io_cost+cpu_cost=(rows+1)+rows*ROW_EVALUATE_COST=(17+1)+17*0.2=21.4

因此从代价角度来说,使用索引iabc,进行范围查询是代价最低的。现在看来一切正常

"rows_estimation": [
              {
                "table": "`t1`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 32,
                    "cost": 9.5
                  } /* table_scan */,
                  "potential_range_indices": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "iabc",
                      "usable": true,
                      "key_parts": [
                        "a",
                        "b",
                        "c",
                        "id"
                      ] /* key_parts */
                    },
                    {
                      "index": "ic",
                      "usable": true,
                      "key_parts": [
                        "c",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indices */,
                  "best_covering_index_scan": {
                    "index": "iabc",
                    "cost": 7.4718,
                    "chosen": true
                  } /* best_covering_index_scan */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "iabc",
                        "ranges": [
                          "NULL < a < 3"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": true,
                        "rows": 3,
                        "cost": 1.6146,
                        "chosen": true
                      },
                      {
                        "index": "ic",
                        "ranges": [
                          "3 <= c"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 17,
                        "cost": 21.41,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "iabc",
                      "rows": 3,
                      "ranges": [
                        "NULL < a < 3"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 3,
                    "cost_for_plan": 1.6146,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ]

下面进一步计算该方案的代价

"considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "range",
                      "rows": 3,
                      "cost": 2.2146,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "cost_for_plan": 2.2146,
                "rows_for_plan": 3,
                "chosen": true
              }
            ]

由于iabc只使用了a列去取数据,而整个查询还有其他条件,需要进一步过滤,其计算代价方式为

cost_no_sort=range:iabc:cost+rows*ROW_EVALUATE_COST
cost_no_sort=1.6146+3*0.2=2.2146

问题原因

MySQL5.6遇到order by limit时,由于只考虑能够优化 order by的索引,而放弃了其他更好的索引,导致的bug,那么MySQL5.7的解决办法是进入low_limit后,不是只考虑优化order by的索引,而是考虑了全部索引。

索引ic的代价为
range:ic:cost=io_cost+cpu_cost=(rows+1)+rows*ROW_EVALUATE_COST=(17+1)+17*0.2=21.4
其甚至高出了cover:iabc:cost,因此在test_quick_select中放弃使用了range索引。
最后由make_join_select选择了一个index scan作为最终的执行计划。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容