MySQL索引選擇不正確並詳細解析OPTIMIZER_TRACE格式
轉載原文:https://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’,
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的過程.
檢視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;
{
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版本之前的版本,需要人工強制使用索引,以達到最好的效果。