11.mysql SQL優化之SQL問題定位
3.1 檢視SQL執行頻率
MySQL 客戶端連線成功後,通過 show [session|global] status 命令可以提供伺服器狀態資訊。show [session|global] status 可以根據需要加上引數“session”或者“global”來顯示 session 級(當前連線)的計結果和 global 級(自資料庫上次啟動至今)的統計結果。如果不寫,預設使用引數是“session”。
下面的命令顯示了當前 session 中所有統計引數的值:
show status like 'Com_______';
show status like 'Innodb_rows_%';
Com_*** : 這些引數對於所有儲存引擎的表操作都會進行累計。
Innodb_*** : 這幾個引數只是針對InnoDB 儲存引擎的,累加的演算法也略有不同。
Com_xxx 表示每個 xxx 語句執行的次數,我們通常比較關心的是以下幾個統計引數。
含義 | |
---|---|
Com_select | 執行 select 操作的次數,一次查詢只累加 1。 |
Com_insert | 執行 INSERT 操作的次數,對於批量插入的 INSERT 操作,只累加一次。 |
Com_update | 執行 UPDATE 操作的次數。 |
Com_delete | 執行 DELETE 操作的次數。 |
Innodb_rows_read | select 查詢返回的行數。 |
Innodb_rows_inserted | 執行 INSERT 操作插入的行數。 |
Innodb_rows_updated | 執行 UPDATE 操作更新的行數。 |
Innodb_rows_deleted | 執行 DELETE 操作刪除的行數。 |
Connections | 試圖連線 MySQL 伺服器的次數。 |
Uptime | 伺服器工作時間。 |
Slow_queries |
可以通過以下兩種方式定位執行效率較低的 SQL 語句。
-
慢查詢日誌 : 通過慢查詢日誌定位那些執行效率較低的 SQL 語句,用--log-slow-queries[=file_name]選項啟動時,mysqld 寫一個包含所有執行時間超過 long_query_time 秒的 SQL 語句的日誌檔案。具體可以檢視本書第 26 章中日誌管理的相關部分。
-
show processlist : 慢查詢日誌在查詢結束以後才紀錄,所以在應用反映執行效率出現問題的時候查詢慢查詢日誌並不能定位問題,可以使用show processlist命令檢視當前MySQL在進行的執行緒,包括執行緒的狀態、是否鎖表等,可以實時地檢視 SQL 的執行情況,同時對一些鎖表操作進行優化。
1) id列,使用者登入mysql時,系統分配的"connection_id",可以使用函式connection_id()檢視 2) user列,顯示當前使用者。如果不是root,這個命令就只顯示使用者許可權範圍的sql語句 3) host列,顯示這個語句是從哪個ip的哪個埠上發的,可以用來跟蹤出現問題語句的使用者 4) db列,顯示這個程序目前連線的是哪個資料庫 5) command列,顯示當前連線的執行的命令,一般取值為休眠(sleep),查詢(query),連線(connect)等 6) time列,顯示這個狀態持續的時間,單位是秒 7) state列,顯示使用當前連線的sql語句的狀態,很重要的列。state描述的是語句執行中的某一個狀態。一個sql語句,以查詢為例,可能需要經過copying to tmp table、sorting result、sending data等狀態才可以完成 8) info列,顯示這個sql語句,是判斷問題語句的一個重要依據
3.3 explain分析執行計劃
通過以上步驟查詢到效率低的 SQL 語句後,可以通過 EXPLAIN或者 DESC命令獲取 MySQL如何執行 SELECT 語句的資訊,包括在 SELECT 語句執行過程中表如何連線和連線的順序。
explain select * from tb_item where id = 1;
explain select * from tb_item where title = '阿爾卡特 (OT-979) 冰川白 聯通3G手機3';
含義 | |
---|---|
id | select查詢的序列號,是一組數字,表示的是查詢中執行select子句或者是操作表的順序。 |
select_type | 表示 SELECT 的型別,常見的取值有 SIMPLE(簡單表,即不使用表連線或者子查詢)、PRIMARY(主查詢,即外層的查詢)、UNION(UNION 中的第二個或者後面的查詢語句)、SUBQUERY(子查詢中的第一個 SELECT)等 |
table | 輸出結果集的表 |
type | 表示表的連線型別,效能由好到差的連線型別為( system ---> const -----> eq_ref ------> ref -------> ref_or_null----> index_merge ---> index_subquery -----> range -----> index ------> all ) |
possible_keys | 表示查詢時,可能使用的索引 |
key | 表示實際使用的索引 |
key_len | 索引欄位的長度 |
rows | 掃描行的數量 |
extra |
CREATE TABLE `t_role` ( `id` varchar(32) NOT NULL, `role_name` varchar(255) DEFAULT NULL, `role_code` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_role_name` (`role_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `t_user` ( `id` varchar(32) NOT NULL, `username` varchar(45) NOT NULL, `password` varchar(96) NOT NULL, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_user_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user_role` ( `id` int(11) NOT NULL auto_increment , `user_id` varchar(32) DEFAULT NULL, `role_id` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_ur_user_id` (`user_id`), KEY `fk_ur_role_id` (`role_id`), CONSTRAINT `fk_ur_role_id` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_ur_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `t_user` (`id`, `username`, `password`, `name`) values('1','super','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','超級管理員'); insert into `t_user` (`id`, `username`, `password`, `name`) values('2','admin','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','系統管理員'); insert into `t_user` (`id`, `username`, `password`, `name`) values('3','itcast','$2a$10$8qmaHgUFUAmPR5pOuWhYWOr291WJYjHelUlYn07k5ELF8ZCrW0Cui','test02'); insert into `t_user` (`id`, `username`, `password`, `name`) values('4','stu1','$2a$10$pLtt2KDAFpwTWLjNsmTEi.oU1yOZyIn9XkziK/y/spH5rftCpUMZa','學生1'); insert into `t_user` (`id`, `username`, `password`, `name`) values('5','stu2','$2a$10$nxPKkYSez7uz2YQYUnwhR.z57km3yqKn3Hr/p1FR6ZKgc18u.Tvqm','學生2'); insert into `t_user` (`id`, `username`, `password`, `name`) values('6','t1','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','老師1'); INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('5','學生','student','學生'); INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('7','老師','teacher','老師'); INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('8','教學管理員','teachmanager','教學管理員'); INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('9','管理員','admin','管理員'); INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('10','超級管理員','super','超級管理員'); INSERT INTO user_role(id,user_id,role_id) VALUES(NULL, '1', '5'),(NULL, '1', '7'),(NULL, '2', '8'),(NULL, '3', '9'),(NULL, '4', '8'),(NULL, '5', '10') ;
3.3.2 explain 之 id
id 欄位是 select查詢的序列號,是一組數字,表示的是查詢中執行select子句或者是操作表的順序。id 情況有三種 :
1) id 相同表示載入表的順序是從上到下。
explain select * from t_role r, t_user u, user_role ur where r.id = ur.role_id and u.id = ur.user_id ;
2) id 不同id值越大,優先順序越高,越先被執行。
EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'))
3) id 有相同,也有不同,同時存在。id相同的可以認為是一組,從上往下順序執行;在所有的組中,id的值越大,優先順序越高,越先執行。
EXPLAIN SELECT * FROM t_role r , (SELECT * FROM user_role ur WHERE ur.`user_id` = '2') a WHERE r.id = a.role_id ;
3.3.3 explain 之 select_type
表示 SELECT 的型別,常見的取值,如下表所示:
select_type | 含義 |
---|---|
SIMPLE | 簡單的select查詢,查詢中不包含子查詢或者UNION |
PRIMARY | 查詢中若包含任何複雜的子查詢,最外層查詢標記為該標識 |
SUBQUERY | 在SELECT 或 WHERE 列表中包含了子查詢 |
DERIVED | 在FROM 列表中包含的子查詢,被標記為 DERIVED(衍生) MYSQL會遞迴執行這些子查詢,把結果放在臨時表中 |
UNION | 若第二個SELECT出現在UNION之後,則標記為UNION ; 若UNION包含在FROM子句的子查詢中,外層SELECT將被標記為 : DERIVED |
UNION RESULT | 從UNION表獲取結果的SELECT |
3.3.4 explain 之 table
3.3.5 explain 之 type
type 顯示的是訪問型別,是較為重要的一個指標,可取值為:
type | 含義 |
---|---|
NULL | MySQL不訪問任何表,索引,直接返回結果 |
system | 表只有一行記錄(等於系統表),這是const型別的特例,一般不會出現 |
const | 表示通過索引一次就找到了,const 用於比較primary key 或者 unique 索引。因為只匹配一行資料,所以很快。如將主鍵置於where列表中,MySQL 就能將該查詢轉換為一個常亮。const於將 "主鍵" 或 "唯一" 索引的所有部分與常量值進行比較 |
eq_ref | 類似ref,區別在於使用的是唯一索引,使用主鍵的關聯查詢,關聯查詢出的記錄只有一條。常見於主鍵或唯一索引掃描 |
ref | 非唯一性索引掃描,返回匹配某個單獨值的所有行。本質上也是一種索引訪問,返回所有匹配某個單獨值的所有行(多個) |
range | 只檢索給定返回的行,使用一個索引來選擇行。 where 之後出現 between , < , > , in 等操作。 |
index | index 與 ALL的區別為 index 型別只是遍歷了索引樹, 通常比ALL 快, ALL 是遍歷資料檔案。 |
all | 將遍歷全表以找到匹配的行 |
結果值從最好到最壞以此是:
NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system > const > eq_ref > ref > range > index > ALL
==一般來說, 我們需要保證查詢至少達到 range 級別, 最好達到ref 。==
3.3.6 explain 之 key
possible_keys : 顯示可能應用在這張表的索引, 一個或多個。
key : 實際使用的索引, 如果為NULL, 則沒有使用索引。
key_len : 表示索引中使用的位元組數, 該值為索引欄位最大可能長度,並非實際使用長度,在不損失精確性的前提下, 長度越短越好 。
掃描行的數量。
3.3.8 explain 之 extra
其他的額外的執行計劃資訊,在該列展示 。
extra | 含義 |
---|---|
using filesort | 說明mysql會對資料使用一個外部的索引排序,而不是按照表內的索引順序進行讀取, 稱為 “檔案排序”, 效率低。 |
using temporary | 使用了臨時表儲存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於 order by 和 group by; 效率低 |
using index | 表示相應的select操作使用了覆蓋索引, 避免訪問表的資料行, 效率不錯。 |
3.4 show profile分析SQL
Mysql從5.0.37版本開始增加了對 show profiles 和 show profile 語句的支援。show profiles 能夠在做SQL優化時幫助我們瞭解時間都耗費到哪裡去了。
通過 have_profiling 引數,能夠看到當前MySQL是否支援profile:
預設profiling是關閉的,可以通過set語句在Session級別開啟profiling:
set profiling=1; //開啟profiling 開關;
通過profile,我們能夠更清楚地瞭解SQL執行的過程。
首先,我們可以執行一系列的操作,如下圖所示:
show databases; use demo_02; show tables; select * from tb_item where id < 5; select count(*) from tb_item;
執行完上述命令之後,再執行show profiles 指令, 來檢視SQL語句執行的耗時:
通過show profile for query query_id 語句可以檢視到該SQL執行過程中每個執行緒的狀態和消耗的時間:
TIP :
Sending data 狀態表示MySQL執行緒開始訪問資料行並把結果返回給客戶端,而不僅僅是返回個客戶端。由於在Sending data狀態下,MySQL執行緒往往需要做大量的磁碟讀取操作,
所以經常是整各查詢中耗時最長的狀態。
在獲取到最消耗時間的執行緒狀態後,MySQL支援進一步選擇all、cpu、block io 、context switch、page faults等明細型別類檢視MySQL在使用什麼資源上耗費了過高的時間。例如,選擇檢視CPU的耗費時間 :
含義 | |
---|---|
Status | sql 語句執行的狀態 |
Duration | sql 執行過程中每一個步驟的耗時 |
CPU_user | 當前使用者佔有的cpu |
CPU_system |
MySQL5.6提供了對SQL的跟蹤trace, 通過trace檔案能夠進一步瞭解為什麼優化器選擇A計劃, 而不是選擇B計劃。
開啟trace , 設定格式為 JSON,並設定trace最大能夠使用的記憶體大小,避免解析過程中因為預設記憶體過小而不能夠完整展示。
SET optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
執行SQL語句 :
select * from tb_item where id < 4;
最後, 檢查information_schema.optimizer_trace就可以知道MySQL是如何執行SQL的 :
select * from information_schema.optimizer_trace\G;
*************************** 1. row ***************************
QUERY: select * from tb_item where id < 4
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `tb_item`.`id` AS `id`,`tb_item`.`title` AS `title`,`tb_item`.`price` AS `price`,`tb_item`.`num` AS `num`,`tb_item`.`categoryid` AS `categoryid`,`tb_item`.`status` AS `status`,`tb_item`.`sellerid` AS `sellerid`,`tb_item`.`createtime` AS `createtime`,`tb_item`.`updatetime` AS `updatetime` from `tb_item` where (`tb_item`.`id` < 4)"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`tb_item`.`id` < 4)",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`tb_item`.`id` < 4)"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`tb_item`.`id` < 4)"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`tb_item`.`id` < 4)"
}
] /* steps */
} /* condition_processing */
},
{
"table_dependencies": [
{
"table": "`tb_item`",
"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": "`tb_item`",
"range_analysis": {
"table_scan": {
"rows": 9816098,
"cost": 2.04e6
} /* table_scan */,
"potential_range_indices": [
{
"index": "PRIMARY",
"usable": true,
"key_parts": [
"id"
] /* key_parts */
}
] /* potential_range_indices */,
"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": "PRIMARY",
"ranges": [
"id < 4"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 3,
"cost": 1.6154,
"chosen": true
}
] /* 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": "PRIMARY",
"rows": 3,
"ranges": [
"id < 4"
] /* ranges */
} /* range_access_plan */,
"rows_for_plan": 3,
"cost_for_plan": 1.6154,
"chosen": true
} /* chosen_range_access_summary */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`tb_item`",
"best_access_path": {
"considered_access_paths": [
{
"access_type": "range",
"rows": 3,
"cost": 2.2154,
"chosen": true
}
] /* considered_access_paths */
} /* best_access_path */,
"cost_for_plan": 2.2154,
"rows_for_plan": 3,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": {
"original_condition": "(`tb_item`.`id` < 4)",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`tb_item`",
"attached": "(`tb_item`.`id` < 4)"
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`tb_item`",
"access_type": "range"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
explain select * from tb_item where title = '阿爾卡特 (OT-979) 冰川白 聯通3G手機3';