mysql 執行計劃分析三看, explain,profiling,optimizer_trace
http://blog.csdn.net/xj626852095/article/details/52767963
step 1
使用explain 查看執行計劃, 5.6後可以加參數 explain format=json xxx 輸出json格式的信息
step 2
使用profiling詳細的列出在每一個步驟消耗的時間,前提是先執行一遍語句。
#打開profiling 的設置 SET profiling = 1; SHOW VARIABLES LIKE ‘%profiling%‘; #查看隊列的內容 show profiles; #來查看統計信息 show profile block io,cpu for query 3;
step 3
Optimizer trace是MySQL5.6添加的新功能,可以看到大量的內部查詢計劃產生的信息, 先打開設置,然後執行一次sql,最後查看`information_schema`.`OPTIMIZER_TRACE`的內容
#打開設置 SET optimizer_trace=‘enabled=on‘; #最大內存根據實際情況而定, 可以不設置 SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000; SET END_MARKERS_IN_JSON=ON; SET optimizer_trace_limit = 1; SHOW VARIABLES LIKE ‘%optimizer_trace%‘; #執行所需sql後,查看該表信息即可看到詳細的執行過程 SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`;
MySQL索引選擇不正確並詳細解析OPTIMIZER_TRACE格式
http://blog.csdn.net/melody_mr/article/details/48950601
一 表結構如下:
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‘,
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 [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 [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 [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的過程.
查看OPTIMIZER_TRACE方法:
1.set optimizer_trace=‘enabled=on‘; --- 開啟trace
2.set optimizer_trace_max_mem_size=1000000; --- 設置trace大小
3.set end_markers_in_json=on; --- 增加trace中註釋
4.select * from information_schema.optimizer_trace\G;
[plain] view plain copy
- {\
- "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 */\
三 其他一個相似問題
單表掃描,使用ref和range從索引獲取數據一例
http://blog.163.com/li_hx/blog/static/183991413201461853637715/
四 問題的解決方式
遇到單表上有多個索引的時候,在MySQL5.6.20版本之前的版本,需要人工強制使用索引,以達到最好的效果.
mysql 執行計劃分析三看, explain,profiling,optimizer_trace