单表扫描,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='XX@XX.com' 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='CY6016@cyou-inc.com' 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='XX@XX.com';
+----------+
| 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` = 'XX@XX.com') 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` = 'XX@XX.com') 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` = 'XX@XX.com') 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` = 'XX@XX.com') 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` = 'XX@XX.com') 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": "'XX@XX.com'",\
"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":[\
"XX@XX.com <= Fuser <= XX@XX.com"\
]/* 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 */\
三 其他一个相似问题
单表扫描,使用ref和range从索引获取数据一例
http://blog.163.com/li_hx/blog/static/183991413201461853637715/
四 问题的解决方式
遇到单表上有多个索引的时候,在MySQL5.6.20版本之前的版本,需要人工强制使用索引,以达到最好的效果.
单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式的更多相关文章
- 单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式
单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式 一 表结构如下: 万行 CREATE TABLE t_audit_operate_log ( Fid b ...
- MySQL索引选择不正确并详细解析OPTIMIZER_TRACE格式
一 表结构如下: CREATE TABLE t_audit_operate_log ( Fid bigint(16) AUTO_INCREMENT, Fcreate_time int(10) un ...
- MySQL中的全表扫描和索引树扫描
引言 在学习mysql时,我们经常会使用explain来查看sql查询的索引等优化手段的使用情况.在使用explain时,我们可以观察到,explain的输出有一个很关键的列,它就是type属性,ty ...
- 图解MySQL索引(三)—如何正确使用索引?
MySQL使用了B+Tree作为底层数据结构,能够实现快速高效的数据查询功能.工作中可怕的是没有建立索引,比这更可怕的是建好了索引又没有使用到.本文将围绕着如何优雅的使用索引,图文并茂地和大家一起探讨 ...
- 如何优雅的使用 参数 is null而不导致全表扫描(破坏索引)
相信大家在很多实际业务中(特别是后台系统)会使用到各种筛选条件来筛选结果集 首先添加测试数据 ), Age INT) go CREATE INDEX idx_age ON TempList (Age) ...
- 表数据量影响MySQL索引选择
现象 新建了一张员工表,插入了少量数据,索引中所有的字段均在where条件出现时,正确走到了idx_nap索引,但是where出现部分自左开始的索引时,却进行全表扫描,与MySQL官方所说的最左匹配原 ...
- MySQL索引选择及规则整理
索引选择性就是结果个数与总个数的比值. 用sql语句表示为: SELECT COUNT(*) FROM table_name WHERE column_name/SELECT COUNT(*) FRO ...
- 七、mysql索引选择
.myisam,bdb,innodb,memory 单表至少支持16个索引 .create index id_index on emp (id) 为emp表创建一个名为id_index的id字段的索引 ...
- MySQL索引选择及添加原则
索引选择性就是结果个数与总个数的比值. 用sql语句表示为: SELECT COUNT(*) FROM table_name WHERE column_name/SELECT COUNT(*) FRO ...
随机推荐
- Spring 整合 Shiro
一.引入依赖 <!-- spring start --> <dependency> <groupId>org.springframework</groupId ...
- React Antd中样式的修改
如果需要对antd的样式进行修改, 进入你要修改的页面 注意:不能直接在自己的文件下面,加入一个css,修改这个class的样式,应该 加入global限定,global {} , 在{}里面写入 . ...
- C++学习009预处理器指令符号 # ## #@ 符号的使用
# ## #@ 符号是预处理器指令符号. 当预处理器遇到#指令符号时,会将#之后的部分用双引号括起来 当预处理去遇到##指令符号时,直接将##前后部分连接起来 当预处理器遇到#@指令符号,将#@之后的 ...
- 问题 D: 约数的个数
问题 D: 约数的个数 时间限制: 1 Sec 内存限制: 32 MB提交: 272 解决: 90[提交][状态][讨论版][命题人:外部导入] 题目描述 输入n个整数,依次输出每个数的约数的个数 ...
- php常见安全问题
XSS攻击原理: XSS又叫CSS (Cross Site Script) ,跨站脚本攻击.它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会 ...
- HTML如何给table添加滚动条
HTML如何给table添加滚动条 要给table添加滚动条其实很简单,主要是给table放到一个div里去,然后再设置div显示滚动条即可.示例代码如下所示: <!--div比table大小要 ...
- Mac下使用Charles抓包Android
原文地址:http://fanjiajia.cn/2018/11/21/Mac%E4%B8%8B%E4%BD%BF%E7%94%A8Charles%E6%8A%93%E5%8C%85Android/ ...
- Mybatis学习系列(三)动态SQL
在mapper配置文件中,有时需要根据查询条件选择不同的SQL语句,或者将一些使用频率高的SQL语句单独配置,在需要使用的地方引用.Mybatis的一个特性:动态SQL,来解决这个问题. mybati ...
- Java中IO——NIO
一.引入 当引入一些新功能的时候,那说明之前的设计可能还需要完善. 1.阻塞式 在传统的IO输入输出中,如果我们从流中去读数据,而数据源中没有数据时,程序就会阻塞该线程.阻塞式线程的一种基本状态,可以 ...
- SpringBoot 入门学习(HelloWord)
前置知识 1.会利用 maven 构建项目 2.了解 Spring 注解 3.了解 RESTful API 的基本理论 4.SpringBoot 是 SpringMVC 的升级版,但两者没有必然的联系 ...