MySQL數據庫SQL語句優化原理專題(三)

需求

做過開發的同學,對分頁肯定不會陌生,因為很多前臺頁面展示,為了更好的展示數據,就會用到分頁,所以如何在MySQL數據庫中寫一個高性能的分頁SQL語句,是每一個開發人員需要掌握的技能。

MySQL數據庫SQL語句優化原理專題(三)

分頁SQL優化原理

這裡給大家寫一個分頁SQL語句。

<code>mysql> show create table sbtest1\G;
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `k` int(10) unsigned NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_sbtest1_k_pad` (`k`,`pad`)
) ENGINE=InnoDB AUTO_INCREMENT=5000001 DEFAULT CHARSET=utf8 MAX_ROWS=1000000
1 row in set (0.00 sec)

這裡準備了一張500W記錄的測試表
mysql> select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|  5000000 |
+----------+
1 row in set (36.23 sec)

測試分頁SQL語句
select a.* from sbtest1 a where k>=10 order by k limit 1000000,10;/<code>

DBA看一條SQL語句是否有性能問題,首先會看看SQL語句的執行計劃,這裡我們也一起先看看執行計劃

<code>mysql> explain select a.* from sbtest1 a where k>=10 order by k limit 1000000,10;
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys         | key  | key_len | ref  | rows    | filtered | Extra                       |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-----------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | k_1,idx_sbtest1_k_pad | NULL | NULL    | NULL | 4804854 |    50.00 | Using where; Using filesort |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-----------------------------+
1 row in set, 1 warning (0.36 sec)/<code>

從執行計劃上可以看到,possible_keys有k_1,idx_sbtest1_k_pad 兩個,可是SQL語句真正執行的時候,並沒有使用到索引,從key為NULL就可以知道,不走索引,性能基本會有問題,怎麼辦。

有的朋友會說,讓SQL走索引啊,可是MySQL數據庫的優化器,為什麼不讓SQL走索引呢。原來優化器會在索引存在的情況下,通過符合RANGE範圍的條數和總數的比例來選擇是使用索引還是進行全表遍歷,當需要讀取的數據超過一個臨界值時,優化器會放棄從索引中讀取而改為進行全表掃描。

有一定技術儲備的朋友就會開始支招,強制走索引,那麼來看看強制走索引的效果。

<code>mysql> explain select a.* from sbtest1 a FORCE INDEX(k_1) where k>=10 order by k limit 1000000,10;
+----+-------------+-------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
|  1 | SIMPLE      | a     | NULL       | range | k_1           | k_1  | 4       | NULL | 2402427 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
1 row in set, 1 warning (0.35 sec)/<code>

看執行計劃,已經走了索引(k_1),那真實的執行時間來一起看看。

<code>mysql> select a.* from sbtest1 a FORCE INDEX(k_1) where k>=10 order by k limit 1000000,10;
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
| id      | k       | c                                                                                                                       | pad                                                         |
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
|  625795 | 2491354 | 97204650565-29444916083-97115589676-40957844519-94449833558-65804998462-60532777657-14143069112-49633654712-45111409020 | 62512103246-48607606334-30155359433-33452555729-53895828100 |
|  699397 | 2491354 | 32339675223-04668542998-80231026645-48395677535-02765586559-55831506063-42378875097-54580937047-37599981559-17618207647 | 29107597440-02396388801-60803416516-20183514546-47217028215 |
|  710717 | 2491354 | 90067622196-22944775363-10477154003-65718440755-24111522288-05149673185-81902474191-32337446442-26659698531-19766723590 | 86804024266-77223258212-31659403967-01772860244-99013209389 |
|  743592 | 2491354 | 93848584940-19743659878-81447583142-56888444937-65254387827-69794611882-41783362491-17920606195-80837332876-61005171709 | 18905422789-64525703285-24594762685-66972415320-18032736842 |
|  857338 | 2491354 | 65447608668-53055131627-49484988592-56750024449-62428854104-09419771096-35386440425-85674387129-76043542898-38871555063 | 00988589677-01502672421-94556913009-28600249646-25815466940 |
|  865647 | 2491354 | 40627346200-31156486805-14979452789-43576795710-31471455633-50576965996-46658943363-33235956457-29071759844-01216446475 | 77768402076-66310138953-89244891269-40818622962-29528856272 |
|  867735 | 2491354 | 60189133283-00412480472-85765718780-99930014238-27634346893-05196495869-38494141485-78725336333-97665769760-66018168229 | 51330855469-21834479110-70983834788-18568325279-18729638102 |
|  911459 | 2491354 | 45484218970-22406621124-16181607460-87980700413-18848748487-38186811906-83866179899-48052521037-07022164880-32639610985 | 66781406596-73096831064-93546863231-94196105489-63929163328 |
|  936676 | 2491354 | 94497954531-98116538211-42837637393-90318550621-31404186605-85978752098-54751837452-40263829756-21584452095-09911571729 | 57946842290-70811751667-96407003836-80311371855-21528783577 |
| 1117598 | 2491354 | 01895171691-22358407775-28637603773-13518829168-90455880119-28672508798-65059848567-86474578422-79051749776-25498873626 | 19312889757-23713866145-22328278494-54535848675-44626652413 |
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
10 rows in set (1 min 36.66 sec)/<code>

看到實際執行時間(1 min 36.66 sec),是不是不敢相信,走索引了,怎麼還會要1分36秒啊,簡直不能接受。我來用一副圖,來展示一下SQL語句執行過程,你就會明白,為什麼執行時間長了。

MySQL數據庫SQL語句優化原理專題(三)

從上圖,可以很明顯的看出,性能瓶頸在哪裡了吧,是回表查詢操作耗時,因為要回表查詢大約500W次,所以怎麼減少回表操作,就是優化的重點。

那可不可以只查詢要返回的10條記錄的ID,最後只做10次回表操作呢,答案,當然是可以的,優化之後的SQL如下所示。

<code>mysql> explain select a.* from sbtest1 a inner join (select id from sbtest1 where k>=10 order by k limit 1000000,10) b using (id); 
+----+-------------+------------+------------+--------+-----------------------+---------+---------+------+---------+----------+--------------------------+
| id | select_type | table      | partitions | type   | possible_keys         | key     | key_len | ref  | rows    | filtered | Extra                    |
+----+-------------+------------+------------+--------+-----------------------+---------+---------+------+---------+----------+--------------------------+
|  1 | PRIMARY     |  | NULL       | ALL    | NULL                  | NULL    | NULL    | NULL | 1000010 |   100.00 | NULL                     |
|  1 | PRIMARY     | a          | NULL       | eq_ref | PRIMARY               | PRIMARY | 4       | b.id |       1 |   100.00 | NULL                     |
|  2 | DERIVED     | sbtest1    | NULL       | range  | k_1,idx_sbtest1_k_pad | k_1     | 4       | NULL | 2402427 |   100.00 | Using where; Using index |
+----+-------------+------------+------------+--------+-----------------------+---------+---------+------+---------+----------+--------------------------+
3 rows in set, 1 warning (0.02 sec)/<code>

看執行計劃,效果非常好,而且用的是覆蓋索引,怎麼看出是用了覆蓋索引,因為Extra列同時出現了Using where和Using index。

來看看實際執行時間

<code>mysql> select a.* from sbtest1 a inner join (select id from sbtest1 where k>=10 order by k limit 1000000,10) b using (id); 
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
| id      | k       | c                                                                                                                       | pad                                                         |
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
|  625795 | 2491354 | 97204650565-29444916083-97115589676-40957844519-94449833558-65804998462-60532777657-14143069112-49633654712-45111409020 | 62512103246-48607606334-30155359433-33452555729-53895828100 |
|  699397 | 2491354 | 32339675223-04668542998-80231026645-48395677535-02765586559-55831506063-42378875097-54580937047-37599981559-17618207647 | 29107597440-02396388801-60803416516-20183514546-47217028215 |
|  710717 | 2491354 | 90067622196-22944775363-10477154003-65718440755-24111522288-05149673185-81902474191-32337446442-26659698531-19766723590 | 86804024266-77223258212-31659403967-01772860244-99013209389 |
|  743592 | 2491354 | 93848584940-19743659878-81447583142-56888444937-65254387827-69794611882-41783362491-17920606195-80837332876-61005171709 | 18905422789-64525703285-24594762685-66972415320-18032736842 |
|  857338 | 2491354 | 65447608668-53055131627-49484988592-56750024449-62428854104-09419771096-35386440425-85674387129-76043542898-38871555063 | 00988589677-01502672421-94556913009-28600249646-25815466940 |
|  865647 | 2491354 | 40627346200-31156486805-14979452789-43576795710-31471455633-50576965996-46658943363-33235956457-29071759844-01216446475 | 77768402076-66310138953-89244891269-40818622962-29528856272 |
|  867735 | 2491354 | 60189133283-00412480472-85765718780-99930014238-27634346893-05196495869-38494141485-78725336333-97665769760-66018168229 | 51330855469-21834479110-70983834788-18568325279-18729638102 |
|  911459 | 2491354 | 45484218970-22406621124-16181607460-87980700413-18848748487-38186811906-83866179899-48052521037-07022164880-32639610985 | 66781406596-73096831064-93546863231-94196105489-63929163328 |
|  936676 | 2491354 | 94497954531-98116538211-42837637393-90318550621-31404186605-85978752098-54751837452-40263829756-21584452095-09911571729 | 57946842290-70811751667-96407003836-80311371855-21528783577 |
| 1117598 | 2491354 | 01895171691-22358407775-28637603773-13518829168-90455880119-28672508798-65059848567-86474578422-79051749776-25498873626 | 19312889757-23713866145-22328278494-54535848675-44626652413 |
+---------+---------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
10 rows in set (0.31 sec)/<code>

效果也是非常棒的,310毫秒就查詢出了結果。

進階

在這裡優化之後的SQL語句如下所示

<code>select a.* from sbtest1 a inner join (select id from sbtest1 where k>=10 order by k limit 1000000,10) b using (id);/<code>

如果order by後面需要加上pad列進行排序呢,變成如下所示

<code>select a.* from sbtest1 a inner join (select id from sbtest1 where k>=10 order by k,pad limit 1000000,10) b using (id);/<code>

上述分頁優化核心思想就是覆蓋索引,很顯然加pad列之後,就不能用覆蓋索引解決問題了,因為不滿足使用覆蓋索引的條件。

<code>mysql> explain select id from sbtest1 where k>=10 order by k,pad limit 1000000,10;
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-----------------------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                       |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-----------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | ALL  | k_1           | NULL | NULL    | NULL | 4804854 |    50.00 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-----------------------------+
1 row in set, 1 warning (0.01 sec)/<code>

這個時候要如何優化呢,需要在k和pad列上創建複合索引,就可以解決問題。

最後給大家留一個SQL,大家看看怎麼優化。

<code>select a.id from sbtest1 a where k>=10 order by k desc,pad asc limit 1000000,10;/<code>
MySQL數據庫SQL語句優化原理專題(三)

SQL優化系列文章

MySQL數據庫SQL語句優化原理專題(一)

MySQL數據庫SQL語句優化原理專題(二)

關注

1.如果您喜歡這篇文章,請點贊+轉發。

2.如果您特別喜歡,請加關注。


分享到:


相關文章: