寫法不同而功能徹底相同的兩條 SQL 的在性能方面的差別。
示例一
需求:取出某個 group(假設 id 爲 100)下的用戶編號(id),用戶暱稱(nick_name)、用戶性別
( sexuality ) 、 用 戶 籤 名 ( sign ) 和 用 戶 生 日 ( birthday ) , 並 按 照 加 入 組 的 時 間
(user_group.gmt_create)來進行倒序排列,取出前 20 個。
解決方案1、
SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1
and user_group.user_id = user.id
limit 100,20;
解決方案2、
SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1
ORDER BY gmt_create desc
limit 100,20) t,user
WHERE t.user_id = user.id;
咱們先來看看執行計劃:
sky@localhost : example 10:32:13> explain
-> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_group
type: ref
possible_keys: user_group_uid_gid_ind,user_group_gid_ind
key: user_group_gid_ind
key_len: 4
ref: const
rows: 31156
Extra: Using where; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: user
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: example.user_group.user_id
rows: 1
Extra:
sky@localhost : example 10:32:20> explain
-> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id\G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: <derived2>
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20
Extra:
*************************** 2. row ***************************
id: 1
select_type: PRIMARY
table: user
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: t.user_id
rows: 1
Extra:
*************************** 3. row ***************************
id: 2
select_type: DERIVED
table: user_group
type: ref
possible_keys: user_group_gid_ind
key: user_group_gid_ind
key_len: 4
ref: const
rows: 31156
Extra: Using filesort
執行計劃對比分析:
解決方案一中的執行計劃顯示 MySQL 在對兩個參與 Join 的表都利用到了索引,user_group 表利用了
user_group_gid_ind 索 引 ( key: user_group_gid_ind ) , user 表 利 用 到 了 主 鍵 索 引 ( key:
PRIMARY),在參與 Join 前 MySQL 經過 Where 過濾後的結果集與 user 表進行 Join,最後經過排序取出
Join 後結果的「limit 100,20」條結果返回。
解決方案二的 SQL 語句利用到了子查詢,因此執行計劃會稍微複雜一些,首先能夠看到兩個表都和
解決方案 1 同樣都利用到了索引(所使用的索引也徹底同樣),執行計劃顯示該子查詢以 user_group 爲
驅動,也就是先經過 user_group 進行過濾並立刻進行這一論的結果集排序,也就取得了 SQL 中的
「limit 100,20」條結果,而後與 user 表進行 Join,獲得相應的數據。這裏可能有人會懷疑在自查詢中
從 user_group 表所取得與 user 表參與 Join 的記錄條數並非 20 條,而是整個 group_id=1 的全部結果。
那麼清你們看看該執行計劃中的第一行,該行內容就充分說明了在外層查詢中的全部的 20 條記錄所有被
返回。
經過比較兩個解決方案的執行計劃,咱們能夠看到第一中解決方案中須要和 user 表參與 Join 的記錄
數 MySQL 經過統計數據估算出來是 31156,也就是經過 user_group 表返回的全部知足 group_id=1 的記錄
數(系統中的實際數據是 20000)。而第二種解決方案的執行計劃中,user 表參與 Join 的數據就只有 20
條,二者相差很大,經過本節最初的分析,咱們認爲第二中解決方案應該明顯優於第一種解決方案。
下面咱們經過對比兩個解決覺方案的 SQL 實際執行的 profile 詳細信息,來驗證咱們上面的判斷。由
於 SQL 語句執行所消耗的最大兩部分資源就是 IO 和 CPU,因此這裏爲了節約篇幅,僅列出 BLOCK IO 和 CPU
兩項 profile 信息(Query Profiler 的詳細介紹將在後面章節中獨立介紹):
先打開 profiling 功能,而後分別執行兩個解決方案的 SQL 語句:
sky@localhost : example 10:46:43> set profiling = 1;
Query OK, 0 rows affected (0.00 sec)
sky@localhost : example 10:46:50> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20;
+--------+-----------+
| id | nick_name |
+--------+-----------+
| 990101 | 990101 |
| 990102 | 990102 |
| 990103 | 990103 |
| 990104 | 990104 |
| 990105 | 990105 |
| 990106 | 990106 |
| 990107 | 990107 |
| 990108 | 990108 |
| 990109 | 990109 |
| 990110 | 990110 |
| 990111 | 990111 |
| 990112 | 990112 |
| 990113 | 990113 |
| 990114 | 990114 |
| 990115 | 990115 |
| 990116 | 990116 |
| 990117 | 990117 |
| 990118 | 990118 |
| 990119 | 990119 |
| 990120 | 990120 |
+--------+-----------+
20 rows in set (1.02 sec)
sky@localhost : example 10:46:58> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id;
+--------+-----------+
| id | nick_name |
+--------+-----------+
| 990101 | 990101 |
| 990102 | 990102 |
| 990103 | 990103 |
| 990104 | 990104 |
| 990105 | 990105 |
| 990106 | 990106 |
| 990107 | 990107 |
| 990108 | 990108 |
| 990109 | 990109 |
| 990110 | 990110 |
| 990111 | 990111 |
| 990112 | 990112 |
| 990113 | 990113 |
| 990114 | 990114 |
| 990115 | 990115 |
| 990116 | 990116 |
| 990117 | 990117 |
| 990118 | 990118 |
| 990119 | 990119 |
| 990120 | 990120 |
+--------+-----------+
20 rows in set (0.96 sec)
查看系統中的 profile 信息,剛剛執行的兩個 SQL 語句的執行 profile 信息已經記錄下來了:
sky@localhost : example 10:47:07> show profiles\G
*************************** 1. row ***************************
Query_ID: 1
Duration: 1.02367600
Query: SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1
and user_group.user_id = user.id
ORDER BY user_group.gmt_create desc
limit 100,20
*************************** 2. row ***************************
Query_ID: 2
Duration: 0.96327800
Query: SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1
ORDER BY gmt_create desc
limit 100,20) t,user
WHERE t.user_id = user.id
2 rows in set (0.00 sec)
sky@localhost : example 10:47:34> SHOW profile CPU,BLOCK IO io FOR query 1;性能
16 rows in set (0.00 sec)ui
sky@localhost : example 10:47:40> SHOW profile CPU,BLOCK IO io FOR query 2;blog
咱們先看看兩條 SQL 執行中的 IO 消耗,二者區別就在於「Sorting result」,咱們回
顧一下前面執行計劃的對比,兩個解決方案的排序過濾數據的時機不同,排序後須要取
得的數據量一個是 20000,一個是 20,正好和這裏的 profile 信息吻合,第一種解決方案的
「Sorting result」的 IO 值是第二種解決方案的將近 500 倍。
而後再來看看 CPU 消耗,全部消耗中,消耗最大的也是「Sorting result」這一項,第
一個消耗多出的原因和上面 IO 消耗差別是同樣的。
結論:
經過上面兩條功能徹底相同的 SQL 語句的執行計劃分析,以及經過實際執行後的
profile 數據的驗證,都證實了第二種解決方案優於第一種解決方案。排序