本示例通過(guò)MySQL官方案例庫(kù)sakila,下載地址https://dev.mysql.com/doc/index-other.html办成。
壓縮包包括三個(gè)文件sakila-schema.sql、sakila-data.sql椎镣、sakila.mwb诈火,分別是sakila庫(kù)的結(jié)構(gòu)創(chuàng)建、數(shù)據(jù)插入状答、sakila的MySQL Workbench數(shù)據(jù)模型(可以在MySQL工作臺(tái)打開(kāi)查看數(shù)據(jù)庫(kù)模型)冷守。
通過(guò)show status命令了解各種SQL的執(zhí)行頻率
show status 命令的幫助信息:
SHOW [GLOBAL | SESSION] STATUS
[LIKE 'pattern' | WHERE expr]
示例:
mysql> show status like 'Com_%';
+-------------------------------------+-------+
| Variable_name | Value |
+-------------------------------------+-------+
| Com_admin_commands | 0 |
| Com_assign_to_keycache | 0 |
| Com_alter_db | 0 |
| Com_alter_event | 0 |
| Com_alter_function | 0 |
| Com_alter_instance | 0 |
| Com_alter_procedure | 0 |
...
Com_xxx 表示每個(gè) xxx 語(yǔ)句的執(zhí)行次數(shù),我們通常比較關(guān)心的試以下幾個(gè)統(tǒng)計(jì)參數(shù)惊科。
- Com_select: 執(zhí)行SELECT操作的次數(shù)拍摇,一次查詢(xún)只累加1。
- Com_insert: 執(zhí)行INSERT操作的次數(shù)馆截,對(duì)于批量插入的INSERT操作充活,只累加一次。
- Com_update: 執(zhí)行UPDATE操作的次數(shù)蜡娶。
- Com_delete: 執(zhí)行DELETE操作的次數(shù)
上面的這些參數(shù)是對(duì)于所有存儲(chǔ)引擎的的表操作都會(huì)進(jìn)行累加混卵,下面這幾個(gè)參數(shù)只針對(duì)InnoDB存儲(chǔ)引擎的,累加的算法也有所不同窖张。
- Innodb_rows_read: SELECT查詢(xún)返回的行數(shù)幕随。
- Innodb_rows_inserted: 執(zhí)行INSERT操作插入的行數(shù)。
- Innodb_rows_updated: 執(zhí)行UPDATE操作更新的行數(shù)宿接。
- Innodb_rows_deleted: 執(zhí)行DELETE操作刪除的行數(shù)赘淮。
通過(guò)以上幾個(gè)參數(shù)辕录,可以很容易了解當(dāng)前數(shù)據(jù)庫(kù)的應(yīng)用是以插入更新為主還是以查詢(xún)操作為主,以及各種類(lèi)型的SQL大致的執(zhí)行比例是多少梢卸。對(duì)于更新操作的計(jì)數(shù)是對(duì)執(zhí)行次數(shù)的計(jì)數(shù)走诞,不論提交還是回滾都會(huì)進(jìn)行累加。
對(duì)于事務(wù)性的應(yīng)用蛤高,通過(guò)Com_commit和Com_rollback可以了解事物提交和回滾的情況蚣旱,對(duì)于回滾操作非常頻繁的數(shù)據(jù)庫(kù),可能意味著應(yīng)用編寫(xiě)存在問(wèn)題戴陡。
此外姻锁,以下幾個(gè)參數(shù)便于用戶(hù)了解數(shù)據(jù)庫(kù)的基本情況。
- Connections: 試圖連接MySQL數(shù)據(jù)庫(kù)的次數(shù)猜欺。
- Uptime: 服務(wù)器工作的時(shí)間。
- Slow_queries: 慢查詢(xún)的次數(shù)
定位執(zhí)行效率低的SQL語(yǔ)句
通過(guò)以下兩種方式定位執(zhí)行效率較低的SQL語(yǔ)句拷窜。
- 通過(guò)慢查詢(xún)?nèi)罩径ㄎ荒切﹫?zhí)行效率較低的SQL語(yǔ)句开皿,用
--log-slow-queries[=file_name]
選項(xiàng)啟動(dòng)時(shí),mysqld
寫(xiě)一個(gè)包含所有執(zhí)行時(shí)間超過(guò)long_query_time
秒的SQL語(yǔ)句的日志文件篮昧。- 慢查詢(xún)?nèi)罩静樵?xún)結(jié)束才記錄赋荆,所以在應(yīng)用反映執(zhí)行效率出現(xiàn)問(wèn)題的時(shí)候查詢(xún)慢查詢(xún)?nèi)罩静⒉荒芏ㄎ粏?wèn)題“米颍可以使用
show processlist
命令查看當(dāng)前MySQL在執(zhí)行的線(xiàn)程窄潭,包括線(xiàn)程的狀態(tài),是否鎖表等酵颁,可以實(shí)時(shí)的查看SQL的執(zhí)行情況嫉你,同時(shí)對(duì)一些鎖表操作進(jìn)行優(yōu)化。
通過(guò)EXPLAIN分析低效SQL的執(zhí)行計(jì)劃
通過(guò)以上步驟查詢(xún)到執(zhí)行效率低的的SQL的語(yǔ)句后躏惋,可以通過(guò)EXPLAIN
或者DESC
命令獲取MySQL如何執(zhí)行SELECT
語(yǔ)句的信息幽污,包括SELECT
語(yǔ)句執(zhí)行過(guò)程中如何連接和連接的順序。
示例:
mysql> explain select sum(amount) from customer a, payment b where a.customer_id = b.customer_id and email = 'MARY.SMITH@sakilacustomer.org'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 599
filtered: 10.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
partitions: NULL
type: ref
possible_keys: idx_fk_customer_id
key: idx_fk_customer_id
key_len: 2
ref: sakila.a.customer_id
rows: 26
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)
對(duì)每個(gè)列做一下簡(jiǎn)單的說(shuō)明
- select_type: 表示 SELECT 的類(lèi)型簿姨,常見(jiàn)的取值有 SIMPLE(簡(jiǎn)單表距误,即不使用表連接和子查詢(xún))、PRIMARY(主查詢(xún)扁位,及外層的查詢(xún))准潭、UNION(UNION中的第二個(gè)或者后面的查詢(xún)語(yǔ)句)、SUBQUERY(子查詢(xún)中第一個(gè)SELECT)等域仇。
- table: 輸出結(jié)果集的表刑然。
- type: 表示 MySQL 在表中找到所需行的方式,或者叫訪(fǎng)問(wèn)類(lèi)型殉簸,常見(jiàn)類(lèi)型有ALL闰集、index沽讹、range、ref武鲁、eq_ref爽雄、const/system、NULL沐鼠,從左到右乘盖,性能從最差到最好憔涉。
- possible_keys: 表示查詢(xún)時(shí)可能使用的索引。
- key: 表示實(shí)際使用的索引。
- key_len: 使用到索引字段的長(zhǎng)度。
- rows: 掃描行的數(shù)量。
- Extra: 執(zhí)行情況的說(shuō)明和描述,包含不適合在其他列中俠士但是對(duì)執(zhí)行計(jì)劃非常重要的額外信息。
type的類(lèi)型:
- type=ALL,全表掃描,MySQL遍歷全表來(lái)找到匹配的行。
- type=index,索引全掃描,MySQL遍歷整個(gè)索引來(lái)查詢(xún)匹配的行。
- type=range,索引范圍掃描,常見(jiàn)于<跺嗽、<=璃吧、>巴元、>=恢总、between等操作符滋戳。
- type=ref,使用唯一索引掃描或者唯一索引的前綴掃描蓄拣,返回匹配某個(gè)單獨(dú)值得記錄行。
- type=eq_ref,類(lèi)似ref荸镊,區(qū)別就在使用的索引是唯一索引宛逗,對(duì)于每個(gè)索引鍵值恭垦,表中只有一條記錄匹配;簡(jiǎn)單來(lái)說(shuō)就是多表連接中使用primary key或者unique index作為關(guān)聯(lián)條件。
- type=const/system玄柏,單表中最多有一個(gè)匹配行襟衰,查詢(xún)起來(lái)非常迅速,所以這個(gè)匹配行中的其他列的值可以被優(yōu)化器在當(dāng)前查詢(xún)中當(dāng)作常量來(lái)處理粪摘,例如瀑晒,根據(jù)主鍵primary key或者唯一索引unique index進(jìn)行的查詢(xún)。
- type=NULL徘意,MySQL不用訪(fǎng)問(wèn)表或者索引苔悦,直接就能得到結(jié)果。
類(lèi)型type還有其他值椎咧,入ref_or_null(與ref類(lèi)似玖详,區(qū)別在于條件中包含對(duì)NULL的查詢(xún))、index_merge(索引合并優(yōu)化)勤讽、unique_subquery(in 的后面是一個(gè)查詢(xún)主鍵字段的字段的子查詢(xún))蟋座、index_subquery(與unique_subquery類(lèi)似,區(qū)別在于 in 的后面是查詢(xún)非唯一索引字段的子查詢(xún))等脚牍。
MySQL中explain
命令加上show warnings
能夠看到在SQL真正被執(zhí)行之前優(yōu)化器做了哪些SQL改寫(xiě)向臀。
mysql> explain select sum(amount) from customer a, payment b where a.customer_id = b.customer_id and email = 'MARY.SMITH@sakilacustomer.org'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 599
filtered: 10.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
partitions: NULL
type: ref
possible_keys: idx_fk_customer_id
key: idx_fk_customer_id
key_len: 2
ref: sakila.a.customer_id
rows: 26
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.01 sec)
mysql> show warnings\G
*************************** 1. row ***************************
Level: Note
Code: 1003
Message: /* select#1 */ select sum(`sakila`.`b`.`amount`) AS `sum(amount)` from `sakila`.`customer` `a` join `sakila`.`payment` `b` where ((`sakila`.`a`.`email` = 'MARY.SMITH@sakilacustomer.org') and (`sakila`.`b`.`customer_id` = `sakila`.`a`.`customer_id`))
1 row in set (0.00 sec)
通過(guò)show profile分析SQL
MySQL從5.0.37版本開(kāi)始增加了對(duì)show profiles
和show profile
語(yǔ)句的支持。通過(guò)have profiling
參數(shù)诸狭,能夠看到當(dāng)前MySQL是否支持profile
飒硅。
mysql> select @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES |
+------------------+
1 row in set, 1 warning (0.00 sec)
默認(rèn)profiling
是關(guān)閉的,可以通過(guò)set
語(yǔ)句在Session 級(jí)別開(kāi)啟profiling
作谚。
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 1 |
+-------------+
1 row in set, 1 warning (0.00 sec)
通過(guò) profile 我們能夠更清楚的了解 SQL 執(zhí)行的過(guò)程。例如庵芭,我們?cè)谝粋€(gè) InnoDB 引擎的付款表 payment 上妹懒,執(zhí)行一個(gè) COUNT(*)
查詢(xún):
mysql> select count(*) from payment;
+----------+
| count(*) |
+----------+
| 16049 |
+----------+
1 row in set (0.29 sec)
然后通過(guò)show profiles
語(yǔ)句,看到當(dāng)前 SQL 的 Query_ID 為6:
mysql> show profiles;
+----------+------------+------------------------------+
| Query_ID | Duration | Query |
+----------+------------+------------------------------+
| 1 | 0.00072200 | select @@profiling |
| 2 | 0.00039400 | select count(*) from payment |
| 3 | 0.00042725 | SELECT DATABASE() |
| 4 | 0.00959875 | show databases |
| 5 | 0.00868875 | show tables |
| 6 | 0.28926100 | select count(*) from payment |
+----------+------------+------------------------------+
6 rows in set, 1 warning (0.00 sec)
通過(guò)show profile for query
語(yǔ)句可以看到執(zhí)行過(guò)程中線(xiàn)程的每個(gè)狀態(tài)和消耗的時(shí)間:
mysql> show profile for query 4;
+----------------------------+----------+
| Status | Duration |
+----------------------------+----------+
| starting | 0.000448 |
| checking permissions | 0.000035 |
| Opening tables | 0.001103 |
| checking permissions | 0.000029 |
| checking permissions | 0.000011 |
| checking permissions | 0.000011 |
| checking permissions | 0.000080 |
| checking permissions | 0.002672 |
| init | 0.000315 |
| checking permissions | 0.000030 |
| checking permissions | 0.000011 |
| checking permissions | 0.000009 |
| checking permissions | 0.000265 |
| System lock | 0.000043 |
| optimizing | 0.000116 |
| statistics | 0.000386 |
| preparing | 0.000407 |
| Creating tmp table | 0.001238 |
| executing | 0.001724 |
| end | 0.000032 |
| query end | 0.000013 |
| waiting for handler commit | 0.000035 |
| removing tmp table | 0.000028 |
| waiting for handler commit | 0.000015 |
| closing tables | 0.000031 |
| freeing items | 0.000268 |
| cleaning up | 0.000249 |
+----------------------------+----------+
27 rows in set, 1 warning (0.00 sec)
在獲取最消耗時(shí)間的線(xiàn)程狀態(tài)后双吆,MySQL 支持進(jìn)一步選擇all眨唬、cpu、block io好乐、context switch匾竿、page faults 等明細(xì)類(lèi)型來(lái)查看MySQL在使用什么資源上消耗了過(guò)高的時(shí)間,例如:
mysql> show profile cpu for query 6;
+--------------------------------+----------+----------+------------+
| Status | Duration | CPU_user | CPU_system |
+--------------------------------+----------+----------+------------+
| starting | 0.000140 | 0.000037 | 0.000100 |
| Executing hook on transaction | 0.000019 | 0.000005 | 0.000013 |
| starting | 0.000021 | 0.000006 | 0.000016 |
| checking permissions | 0.000017 | 0.000004 | 0.000012 |
| Opening tables | 0.000401 | 0.000109 | 0.000294 |
| init | 0.000026 | 0.000007 | 0.000017 |
| System lock | 0.000025 | 0.000006 | 0.000019 |
| optimizing | 0.000016 | 0.000005 | 0.000011 |
| statistics | 0.000032 | 0.000009 | 0.000024 |
| preparing | 0.000037 | 0.000009 | 0.000027 |
| executing | 0.002256 | 0.003559 | 0.000000 |
| end | 0.000089 | 0.000034 | 0.000000 |
| query end | 0.000018 | 0.000016 | 0.000000 |
| waiting for handler commit | 0.000113 | 0.004119 | 0.000000 |
| closing tables | 0.000039 | 0.000035 | 0.000000 |
| freeing items | 0.000454 | 0.000458 | 0.000000 |
| cleaning up | 0.000081 | 0.000076 | 0.000000 |
+--------------------------------+----------+----------+------------+
17 rows in set (0.01 sec)
如果對(duì) MySQL 源碼感興趣蔚万,還可以通過(guò)show profile source for query
查看 SQL 解析執(zhí)行過(guò)程中每個(gè)步驟對(duì)應(yīng)的源碼文件岭妖、函數(shù)名以及具體的源文件行數(shù):
mysql> show profile source for query 6;
+--------------------------------+----------+-------------------------+----------------------+-------------+
| Status | Duration | Source_function | Source_file | Source_line |
+--------------------------------+----------+-------------------------+----------------------+-------------+
| starting | 0.000191 | NULL | NULL | NULL |
| Executing hook on transaction | 0.000019 | launch_hook_trans_begin | rpl_handler.cc | 1119 |
| starting | 0.000020 | launch_hook_trans_begin | rpl_handler.cc | 1121 |
| checking permissions | 0.000019 | check_access | sql_authorization.cc | 2176 |
| Opening tables | 0.000065 | open_tables | sql_base.cc | 5591 |
| init | 0.000020 | execute | sql_select.cc | 677 |
| System lock | 0.000025 | mysql_lock_tables | lock.cc | 331 |
| optimizing | 0.000016 | optimize | sql_optimizer.cc | 282 |
| statistics | 0.000040 | optimize | sql_optimizer.cc | 502 |
| preparing | 0.000037 | optimize | sql_optimizer.cc | 583 |
| executing | 0.003318 | ExecuteIteratorQuery | sql_union.cc | 1409 |
| end | 0.000036 | execute | sql_select.cc | 730 |
| query end | 0.000015 | mysql_execute_command | sql_parse.cc | 4606 |
| waiting for handler commit | 0.000026 | ha_commit_trans | handler.cc | 1589 |
| closing tables | 0.000023 | mysql_execute_command | sql_parse.cc | 4657 |
| freeing items | 0.000444 | mysql_parse | sql_parse.cc | 5330 |
| cleaning up | 0.000058 | dispatch_command | sql_parse.cc | 2184 |
+--------------------------------+----------+-------------------------+----------------------+-------------+
show profile
能夠在做 SQL 優(yōu)化時(shí)幫助我們了解時(shí)間都耗費(fèi)到哪里去了。
通過(guò)trace分析優(yōu)化器如何選擇執(zhí)行計(jì)劃
MySQL 提供了對(duì) SQL 的追蹤 trace ,通過(guò) trace 文件能夠進(jìn)一步了解為什么優(yōu)化器選擇 A 執(zhí)行計(jì)劃而不是 B 執(zhí)行計(jì)劃昵慌,幫助我們更好理解優(yōu)化器的行為假夺。
使用方式:首先打開(kāi) trace,格式設(shè)置為 JSON斋攀,設(shè)置最大能夠使用的內(nèi)存大小已卷,避免解析過(guò)程中因?yàn)槟J(rèn)內(nèi)存過(guò)小而不能完整顯示。
mysql> set OPTIMIZER_TRACE="enabled=on", END_MARKERS_IN_JSON=on;
Query OK, 0 rows affected (0.00 sec)
mysql> set OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;
Query OK, 0 rows affected (0.00 sec)
接下來(lái)執(zhí)行想 trace 的 SQL 語(yǔ)句:
mysql> select rental_id from rental where rental_date >= '2005-05-25 04:00:00' and rental_date <= '2005-05-25 05:00:00' and inventory_id=4466;
+-----------+
| rental_id |
+-----------+
| 39 |
+-----------+
1 row in set (0.05 sec)
最后檢查 INFORMATION_SCHEMA.OPTIMIZER_TRACE 就可以知道 MySQL 是如何執(zhí)行 SQL 語(yǔ)句的淳蔼。
mysql> select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: select rental_id from rental where rental_date >= '2005-05-25 04:00:00' and rental_date <= '2005-05-25 05:00:00' and inventory_id=4466
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `rental`.`rental_id` AS `rental_id` from `rental` where ((`rental`.`rental_date` >= '2005-05-25 04:00:00') and (`rental`.`rental_date` <= '2005-05-25 05:00:00') and (`rental`.`inventory_id` = 4466))"
}
] /* steps */
} /* join_preparation */
},
...
] /* steps */
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)
確定問(wèn)題并采取相應(yīng)的優(yōu)化措施
經(jīng)過(guò)以上步驟侧蘸,基本可以確認(rèn)問(wèn)題出現(xiàn)的原因。此時(shí)可以根據(jù)情況采取相應(yīng)的措施鹉梨,進(jìn)行優(yōu)化提高執(zhí)行的效率讳癌。
后面會(huì)介紹SQL優(yōu)化的具體措施。