单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式

一 表结构如下: 

MySQL  5.5.30  5.6.20 版本, 表大概有815万行

CREATE TABLE t_audit_operate_log (
  Fid bigint(16) AUTO_INCREMENT,
  Fcreate_time int(10) unsigned NOT NULL DEFAULT ‘0‘,
  Fuser varchar(50) DEFAULT ‘‘,
  Fip bigint(16) DEFAULT NULL,
  Foperate_object_id bigint(20) DEFAULT ‘0‘,
  PRIMARY KEY (Fid),
  KEY indx_ctime (Fcreate_time),
  KEY indx_user (Fuser),
  KEY indx_objid (Foperate_object_id),
  KEY indx_ip (Fip)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

执行查询:

mysql> explain select count(*) from t_audit_operate_log where Fuser=‘[email protected]‘ and Fcreate_time>=1407081600 and Fcreate_time<=1407427199\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: t_audit_operate_log

type: ref

possible_keys: indx_ctime,indx_user

key: indx_user

key_len: 153

ref: const

rows: 2007326

Extra: Using where

发现,使用了一个不合适的索引, 不是很理想,于是改成指定索引:

mysql> explain select count(*) from t_audit_operate_log use index(indx_ctime) where Fuser=‘[email protected]‘ and Fcreate_time>=1407081600 and Fcreate_time<=1407427199\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: t_audit_operate_log

type: range

possible_keys: indx_ctime

key: indx_ctime

key_len: 5

ref: NULL

rows: 670092

Extra: Using where

实际执行耗时,后者比前者快了接近10

问题: 很奇怪,优化器为何不选择使用 indx_ctime 索引,而选择了明显会扫描更多行的 indx_user索引。

分析2个索引的数据量如下:  两个条件的唯一性对比:

select count(*) from t_audit_operate_log where Fuser=‘[email protected]‘;
+----------+
| count(*) |
+----------+
| 1238382 | 
+----------+

select count(*) from t_audit_operate_log where Fcreate_time>=1407254400 and Fcreate_time<=1407427199;
+----------+
| count(*) |
+----------+
| 198920 | 
+----------+

显然,使用索引indx_ctime好于indx_user,但MySQL却选择了indx_user. 为什么?

于是,使用 OPTIMIZER_TRACE进一步探索.

二  OPTIMIZER_TRACE的过程说明

以本处事例简要说明OPTIMIZER_TRACE的过程.


{\
  "steps": [\
    {\
      "join_preparation": {\  ---优化准备工作
        "select#": 1,\
        "steps": [\
          {\
            "expanded_query": "/* select#1 */ select count(0) AS `count(*)` from `t_audit_operate_log` where ((`t_audit_operate_log`.`Fuser` = ‘[email protected]‘) and (`t_audit_operate_log`.`Fcreate_time` >= 1407081600) and (`t_audit_operate_log`.`Fcreate_time` <= 1407427199))"\
          }\
        ] /* steps */\
      } /* join_preparation */\
    },\
    {\
      "join_optimization": {\  ---优化工作的主要阶段,包括逻辑优化和物理优化两个阶段
        "select#": 1,\
        "steps": [\  ---优化工作的主要阶段, 逻辑优化阶段
          {\
            "condition_processing": {\  ---逻辑优化,条件化简
              "condition": "WHERE",\
              "original_condition": "((`t_audit_operate_log`.`Fuser` = ‘[email protected]‘) and (`t_audit_operate_log`.`Fcreate_time` >= 1407081600) and (`t_audit_operate_log`.`Fcreate_time` <= 1407427199))",\
              "steps": [\
                {\
                  "transformation": "equality_propagation",\  ---逻辑优化,条件化简,等式处理
                  "resulting_condition": "((`t_audit_operate_log`.`Fuser` = ‘[email protected]‘) and (`t_audit_operate_log`.`Fcreate_time` >= 1407081600) and (`t_audit_operate_log`.`Fcreate_time` <= 1407427199))"\
                },\
                {\
                  "transformation": "constant_propagation",\  ---逻辑优化,条件化简,常量处理
                  "resulting_condition": "((`t_audit_operate_log`.`Fuser` = ‘[email protected]‘) and (`t_audit_operate_log`.`Fcreate_time` >= 1407081600) and (`t_audit_operate_log`.`Fcreate_time` <= 1407427199))"\
                },\
                {\
                  "transformation": "trivial_condition_removal",\  ---逻辑优化,条件化简,条件去除
                  "resulting_condition": "((`t_audit_operate_log`.`Fuser` = ‘[email protected]‘) and (`t_audit_operate_log`.`Fcreate_time` >= 1407081600) and (`t_audit_operate_log`.`Fcreate_time` <= 1407427199))"\
                }\
              ] /* steps */\
            } /* condition_processing */\
          },\  ---逻辑优化,条件化简,结束
          {\
            "table_dependencies": [\  ---逻辑优化, 找出表之间的相互依赖关系. 非直接可用的优化方式.
              {\
                "table": "`t_audit_operate_log`",\
                "row_may_be_null": false,\
                "map_bit": 0,\
                "depends_on_map_bits": [\
                ] /* depends_on_map_bits */\
              }\
            ] /* table_dependencies */\
          },\
          {\
            "ref_optimizer_key_uses": [\   ---逻辑优化,  找出备选的索引
              {\
                "table": "`t_audit_operate_log`",\
                "field": "Fuser",\
                "equals": "‘[email protected]‘",\
                "null_rejecting": false\
              }\
            ] /* ref_optimizer_key_uses */\
          },\
          {\
            "rows_estimation": [\   ---逻辑优化, 估算每个表的元组个数. 单表上进行全表扫描和索引扫描的代价估算. 每个索引都估算索引扫描代价
              {\
                "table": "`t_audit_operate_log`",\
                "range_analysis": {\
                  "table_scan": {\---逻辑优化, 估算每个表的元组个数. 单表上进行全表扫描的代价
                    "rows": 8150516,\
                    "cost": 1.73e6\
                  } /* table_scan */,\
                  "potential_range_indices": [\ ---逻辑优化, 列出备选的索引. 后续版本字符串变为potential_range_indexes
                    {\
                      "index": "PRIMARY",\---逻辑优化, 本行表明主键索引不可用
                      "usable": false,\
                      "cause": "not_applicable"\
                    },\
                    {\
                      "index": "indx_ctime",\---逻辑优化, 索引indx_ctime
                      "usable": true,\
                      "key_parts": [\
                        "Fcreate_time",\
                        "Fid"\
                      ] /* key_parts */\
                    },\
                    {\
                      "index": "indx_user",\---逻辑优化, 索引indx_user
                      "usable": true,\
                      "key_parts": [\
                        "Fuser",\
                        "Fid"\
                      ] /* key_parts */\
                    },\
                    {\
                      "index": "indx_objid",\---逻辑优化, 索引
                      "usable": false,\
                      "cause": "not_applicable"\
                    },\
                    {\
                      "index": "indx_ip",\---逻辑优化, 索引
                      "usable": false,\
                      "cause": "not_applicable"\
                    }\
                  ] /* potential_range_indices */,\
                  "setup_range_conditions": [\ ---逻辑优化, 如果有可下推的条件,则带条件考虑范围查询
                  ] /* setup_range_conditions */,\
                  "group_index_range": {\---逻辑优化, 如带有GROUPBY或DISTINCT,则考虑是否有索引可优化这种操作. 并考虑带有MIN/MAX的情况
                    "chosen": false,\
                    "cause": "not_group_by_or_distinct"\
                  } /* group_index_range */,\
                  "analyzing_range_alternatives": {\---逻辑优化,开始计算每个索引做范围扫描的花费(等值比较是范围扫描的特例)
                    "range_scan_alternatives": [\
                      {\
                        "index": "indx_ctime",\ ---[A]
                        "ranges": [\
                          "1407081600 <= Fcreate_time <= 1407427199"\
                        ] /* ranges */,\
                        "index_dives_for_eq_ranges": true,\
                        "rowid_ordered": false,\
                        "using_mrr": true,\
                        "index_only": false,\
                        "rows": 688362,\
                        "cost": 564553,\ ---逻辑优化,这个索引的代价最小
                        "chosen": true\ ---逻辑优化,这个索引的代价最小,被选中. (比前面的table_scan 和其他索引的代价都小)
                      },\
                      {\
                        "index": "indx_user",\
                        "ranges": [\
                          "[email protected] <= Fuser <= [email protected]"\
                        ] /* ranges */,\
                        "index_dives_for_eq_ranges": true,\
                        "rowid_ordered": true,\
                        "using_mrr": true,\
                        "index_only": false,\
                        "rows": 1945894,\
                        "cost": 1.18e6,\
                        "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": "indx_ctime",\
                      "rows": 688362,\
                      "ranges": [\
                        "1407081600 <= Fcreate_time <= 1407427199"\
                      ] /* ranges */\
                    } /* range_access_plan */,\
                    "rows_for_plan": 688362,\
                    "cost_for_plan": 564553,\
                    "chosen": true\    -- 这里看到的cost和rows都比 indx_user 要来的小很多---这个和[A]处是一样的,是信息汇总.
                  } /* chosen_range_access_summary */\
                } /* range_analysis */\
              }\
            ] /* rows_estimation */\ ---逻辑优化, 估算每个表的元组个数. 行估算结束
          },\
          {\
            "considered_execution_plans": [\ ---物理优化, 开始多表连接的物理优化计算
              {\
                "plan_prefix": [\
                ] /* plan_prefix */,\
                "table": "`t_audit_operate_log`",\
                "best_access_path": {\
                  "considered_access_paths": [\
                    {\
                      "access_type": "ref",\ ---物理优化, 计算indx_user索引上使用ref方查找的花费,
                      "index": "indx_user",\
                      "rows": 1.95e6,\
                      "cost": 683515,\
                      "chosen": true\
                    },\ ---物理优化, 本应该比较所有的可用索引,即打印出多个格式相同的但索引名不同的内容,这里却没有。推测是bug--没有遍历每一个索引.
                    {\
                      "access_type": "range",\---物理优化,猜测对应的是indx_time(没有实例可进行调试,对比5.7的跟踪信息猜测而得)
                      "rows": 516272,\
                      "cost": 702225,\---物理优化,代价大于了ref方式的683515,所以没有被选择
                      "chosen": false\   -- cost比上面看到的增加了很多,但rows没什么变化 ---物理优化,此索引没有被选择
                    }\
                  ] /* considered_access_paths */\
                } /* best_access_path */,\
                "cost_for_plan": 683515,\ ---物理优化,汇总在best_access_path 阶段得到的结果
                "rows_for_plan": 1.95e6,\
                "chosen": true\   -- cost比上面看到的竟然小了很多?虽然rows没啥变化  ---物理优化,汇总在best_access_path 阶段得到的结果
              }\
            ] /* considered_execution_plans */\
          },\
          {\
            "attaching_conditions_to_tables": {\---逻辑优化,尽量把条件绑定到对应的表上
              } /* attaching_conditions_to_tables */\
          },\
          {\
            "refine_plan": [\
              {\
                "table": "`t_audit_operate_log`",\---逻辑优化,下推索引条件"pushed_index_condition";其他条件附加到表上做为过滤条件"table_condition_attached"
              }\
            ] /* refine_plan */\
          }\
        ] /* steps */\
      } /* join_optimization */\ \---逻辑优化和物理优化结束
    },\
    {\
      "join_explain": {} /* join_explain */\
    }\
  ] /* steps */\

 
时间: 2024-08-04 03:05:11

单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式的相关文章

单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式

单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式 一 表结构如下:  MySQL  5.5.30  5.6.20 版本, 表大概有815万行 CREATE TABLE t_audit_operate_log (  Fid bigint(16) AUTO_INCREMENT,  Fcreate_time int(10) unsigned NOT NULL DEFAULT '0',  Fuser varchar(50) DEFAULT '',  Fip bigint

七、mysql索引选择

1.myisam,bdb,innodb,memory 单表至少支持16个索引 2.create index id_index on emp (id) 为emp表创建一个名为id_index的id字段的索引 3.drop index id_index on emp 删除emp表的id_index 索引 4.explain xxxxxxx,可以查看相关的执行结果,用于优化表用 5.最合适的索引列是“条件列”,而不是“选择列” 比如 select name from emp where year >=

如何优雅的使用 参数 is null而不导致全表扫描(破坏索引)

相信大家在很多实际业务中(特别是后台系统)会使用到各种筛选条件来筛选结果集 首先添加测试数据 CREATE TABLE TempList(Id int IDENTITY,Name VARCHAR(12), Age INT) go CREATE INDEX idx_age ON TempList (Age) GO DECLARE @i INT; SET @i=0; WHILE @i<10000 BEGIN INSERT INTO TempList (Name, Age)VALUES(CAST(@i

MySQL索引选择及规则整理

索引选择性 索引选择性就是结果个数与总个数的比值. 用sql语句表示为: SELECT COUNT(*) FROM table_name WHERE column_name/SELECT COUNT(*) FROM table_name 一般来说(例如书 “SQL Tuning“),如果选择性超过 20% 那么全表扫描比使用索引性能更优. 但MySQL是没有计算索引的选择性的,只是预测逻辑IO操作的数量,因此对于MySQL索引要慎重选择. 举个栗子,tinyint类型的列,用以保存性别,就算用上

MySQL索引选择及添加原则

索引选择性就是结果个数与总个数的比值. 用sql语句表示为: SELECT COUNT(*) FROM table_name WHERE column_name/SELECT COUNT(*) FROM table_name 一般来说(例如书 "SQL Tuning"),如果选择性超过 20% 那么全表扫描比使用索引性能更优. 但MySQL是没有计算索引的选择性的,只是预测逻辑IO操作的数量,因此对于MySQL索引要慎重选择. 索引几大原则: 1.选择唯一性索引 唯一性索引的值是唯一的

10 MySQL索引选择与使用

索引概述 每种存储引擎对每个表至少支持16个索引,总索引长度至少256字节. MyISAM和InnoDB的表默认创建BTREE索引.MEMORY引擎默认使用HASH索引,但也支持BTREE MySQL目前不支持函数索引,但支持前缀索引. MyISAM支持的前缀索引最大长度为1000字节;InnoDB支持的前缀索引最大程度是767字节. CREATE TABLE 创建索引时是按照字符数计量的,所以对于多字节的字符集,要考虑字符和字节的关系. MySQL还支持FULLTEXT(全文本)索引,用于全文

SQL 数据优化索引建suo避免全表扫描

首先什么是全表扫描和索引扫描?全表扫描所有数据过一遍才能显示数据结果,索引扫描就是索引,只需要扫描一部分数据就可以得到结果.如果数据没建立索引. 无索引的情况下搜索数据的速度和占用内存就会比用索引的检索慢和高.下面是一个例子 1:无索引的情况 Product表,里面没有任何索引,如下图: 从上图中,我悲剧的看到了,物理读是9次,也就说明走了9次硬盘,你也可以想到,走硬盘的目的是为了拿数据,逻辑读有1636次,要注意的是这里 的”次“是“页”的意思,也就是在内存中走了1636个数据页,我用dbcc

Mysql 索引 转载

转自 :http://blog.csdn.net/wud_jiyanhui/article/details/7403375 什么是索引 索引时一种特殊的文件,他们包涵着对数据表里所有记录的引用指针. 当对数据表记录进行更新后,都会对索引进行刷新. 索引会占用相当大的空间,应该只为经常查询和最经常排序的数据列建立索引. 索引类型   ①普通索引:这是最基本的索引类型,而且它没有唯一性之类的限制.普通索引可以通过以下几种方式创建:  I.创建索引 例如:CREATE INDEX <索引的名字> O

MySQL 索引设计概要

在关系型数据库中设计索引其实并不是复杂的事情,很多开发者都觉得设计索引能够提升数据库的性能,相关的知识一定非常复杂. 然而这种想法是不正确的,索引其实并不是一个多么高深莫测的东西,只要我们掌握一定的方法,理解索引的实现就能在不需要 DBA 的情况下设计出高效的索引. 本文会介绍 数据库索引设计与优化 中设计索引的一些方法,让各位读者能够快速的在现有的工程中设计出合适的索引. 磁盘 IO 一个数据库必须保证其中存储的所有数据都是可以随时读写的,同时因为 MySQL 中所有的数据其实都是以文件的形式