MySQL常用SQL語句優化


推薦閱讀這篇博文,索引說的非常詳細到位:http://blog.linezing.com/?p=798#nav-3-2

在數據庫日常維護中,最常做的事情就是SQL語句優化,因為這個才是影響性能的最主要因素。當然還有其他方面的,比如OS優化,硬件優化,MySQL Server優化,數據類型優化,應用層優化,但是這些都沒有SQL語句優化來的重要。下面將介紹INSERT,GROUP BY,LIMIT等的優化方法。

1.優化大批量插入數據

當用load命令導入數據的時候,適當的設置可以提高導入的速度。對於MyISAM存儲引擎的表,可以通過如下方式快速導入大量的數據。

ALTER TABLE tablename DISABLE KEYS;
loading the data;
ALTER TABLE tablename ENABLE KEYS;

DISABLE KEYS和ENABLE KEYS用來關閉或者打開MyISAM表非唯一索引的更新。在導入大量的數據到一個非空的MyISAM表示,通過設置這兩個命令,可以提高導入的效率。對於導入大量數據到一個空的MyISAM表時,默認就是先導入數據然后才創建索引的,所以不用設置。

對於InnoDB存儲引擎表,上面的方式並不能提高導入數據的效率。可以有以下幾種方式提高Innodb表的導入效率

(1)因為InnoDB類型的表是按照主鍵的順序保存的,所以將導入的數據按照主鍵的順序排列,可以有效的提高導入數據的效率。

(2)在導入數據前執行 SET UNIQUE_CHECKS=0,關閉唯一性效驗,在導入數據結束以后執行SET UNIQUE_CHECKS=1,恢復唯一性效驗,可以提高導入效率。

(3)如果使用自動提交的方式,建議在導入前執行SET AUTOCOMMIT=0,關閉自動提交,導入結束后再執行SET AUTOCOMMIT=1,打開自動提交,也可以提高導入的效率。

(4)對於有外鍵約束的表,我們在導入數據之前也可以忽略外鍵檢查,因為innodb的外鍵是即時檢查的,所以對導入的每一行都會進行外鍵的檢查。

set foreign_key_checks = 0;
load data ............
set foreign_key_checks = 1;

2.優化INSERT語句

(1)如果同時從同一客戶端插入大量數據,應該盡量使用多個值的表的INSERT 語句,這種方式將大大減少客戶端與數據庫服務器之間的連接,關閉等消耗,使得效率比分開執行的單個INSERT語句快(大部分情況下,使用多個值表的INSERT語句能比單個INSERT語句快上好幾倍),比如下面一次插入多行:

INSERT INTO VALUES ('yayun',23),('tom',26),('atlas',32),('david',25).......

(2)插入延遲。如果從不同客戶端插入很多行,可以通過使用INSERT  DELAYED語句得到更高的速度。DELAYED的意思是讓INSERT語句馬上執行,其實數據都被放在內存的隊列中,並沒有真正寫入磁盤,這比每條語句分別插入要快的多;LOW_PRIORITY剛好相反,在所有其他用戶對表的讀寫完成后才進行插入。

(3)將索引文件和數據文件放在不同的磁盤(利用建表中的選項)

(4)如果進行批量插入,可以通過增加bulk_insert_buffer_size 變量值的方法來提高速度,這只對MyISAM表有用。

(5)當從一個文本文件裝載一個表時,使用LOAD DATA INFILE。通常比使用很多的INSERT語句快。

 無法使用索引的情況

(1).以%開頭的like查詢
(2).數據類型出現隱式轉換的時候也不會使用索引,特別是當列類型是字符串,那么一定記得在where條件中把字符串常量值用引號引起來,否則即便這個列上有索引,MySQL也不會用到,因為MySQL默認把輸入的常量值進行轉換以后才進行檢索
(3).復合索引的情況下,如果查詢條件不包含索引列的最左邊部分,即不滿足最左前綴原則,則不會使用索引
(4).如果mysql估計使用索引掃描比全表掃描更慢,則不使用索引。(掃描數據超過30%,都會走全表)
(5).用or分割開的條件,如果 or前的條件中的列有索引,而后面的列中沒有索引,那么涉及的索引都不會被用到
(6).字段使用函數,將無法使用索引
(7).Join 語句中 Join 條件字段類型不一致的時候 MySQL 無法使用索引

3.優化ORDER BY語句

通過索引排序是性能最好的,通常如果SQL語句不合理,就無法使用索引排序,以下幾種情況是無法使用索引排序的。

(1).查詢使用了兩種不同的排序方向,但是索引列都是正序排序的;
(2).查詢的where和order by中的列無法組合成索引的最左前綴;
(3).查詢在索引列的第一列上是范圍條件;
(4)查詢條件上有多個等於條件。對排序來說,這也是一種范圍查詢
  在優化ORDER BY語句之前,先來看看MySQL中排序的方式。先看看MySQL官方提供的示例數據庫sakila中customer表上的索引情況。
mysql> show index from customer;
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name          | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| customer |          0 | PRIMARY           |            1 | customer_id | A         |         577 |     NULL | NULL   |      | BTREE      |         |               |
| customer |          1 | idx_fk_store_id   |            1 | store_id    | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| customer |          1 | idx_fk_address_id |            1 | address_id  | A         |         577 |     NULL | NULL   |      | BTREE      |         |               |
| customer |          1 | idx_last_name     |            1 | last_name   | A         |         577 |     NULL | NULL   |      | BTREE      |         |               |
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.00 sec)

mysql> 

1.MySQL中有兩種排序方式

第一種通過有序索引順序掃描直接返回有序數據,這種方式在使用explain分析查詢時顯示為Using Index,不需要額外的排序,性能是最優的。

mysql> explain select customer_id from customer order by store_id ;
+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+
| id | select_type | table    | type  | possible_keys | key             | key_len | ref  | rows | Extra       |
+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+
|  1 | SIMPLE      | customer | index | NULL          | idx_fk_store_id | 1       | NULL |  577 | Using index |
+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql> 

因為查詢主鍵,然后store_id列是輔助索引(二級索引),輔助索引上存放了索引鍵值+對應行的主鍵,所以直接掃描輔助索引返回有序數據。

mysql> explain select * from customer order by customer_id;
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | customer | index | NULL          | PRIMARY | 2       | NULL |  577 |       |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
1 row in set (0.00 sec)

mysql> 

這種排序方式直接使用了主鍵,也可以說成是使用了聚集索引。因為innodb是索引組織表(index-organized table),通過主鍵聚集數據,數據都是按照主鍵排序存放。而聚集索引就是按照沒張表的主鍵構造一顆B+樹,同時葉子節點中存放的即為正張表的行記錄數據,也將聚集索引的葉子節點稱為數據頁。聚集索引的這個特性決定了索引組織表中的數據也是索引的一部分。

第二種是通過對返回數據進行排序,也就是通常說的Filesort排序,所有不是通過索引直接返回排序結果的排序都叫Filesort排序。Filesort並不代表通過磁盤文件進行排序,而只是說明進行了一個排序操作,至於排序操作是否使用了磁盤文件或者臨時表,取決於mysql服務器對排序參數的設置和需要排序數據的大小。

mysql> explain select * from customer order by store_id ;                                      
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | customer | ALL  | NULL          | NULL | NULL    | NULL |  671 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql> 

那么這里優化器為什么不使用store_id列上的輔助索引進行排序呢?

當通過輔助索引來查找數據時,innodb存儲引擎會遍歷輔助索引並通過葉級別的指針獲得指向主鍵索引的主鍵,然后通過主鍵索引來找到一個完整的行記錄。舉例來說,如果在一棵高度為3的輔助索引樹中查找數據,那么需要對這個輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣為3,那么還需要對聚集索引樹進行3次查找,最終找到一個完整的行數所在的頁,因此一共需要6次邏輯IO訪問以得到最終的一個數據頁。

使用mysql5.6 的trace功能來查看一下強制使用輔助索引和全表掃描的開銷。(mysql5.6的trace真心不錯,給個贊^_^)

 {
   "rows_estimation": [
     {
       "table": "`customer` FORCE INDEX (`idx_fk_store_id`)",
       "table_scan": { "rows": 599, "cost": 5
       } /* table_scan */
     }
   ] /* rows_estimation */
 },
 {
   "considered_execution_plans": [
     {
       "plan_prefix": [
       ] /* plan_prefix */,
       "table": "`customer` FORCE INDEX (`idx_fk_store_id`)",
       "best_access_path": {
         "considered_access_paths": [
           {
             "access_type": "scan", "rows": 599, "cost": 719.8,           "chosen": true,
             "use_tmp_table": true
           }

可以清楚的看見優化器使用全表掃描開銷更小。

再來看一種情況

mysql> alter table customer add key idx_stored_email ( store_id , email );
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> 
mysql> explain select store_id , email from customer order  by email ;            
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+
| id | select_type | table    | type  | possible_keys | key              | key_len | ref  | rows | Extra                       |
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | customer | index | NULL          | idx_stored_email | 154     | NULL |  671 | Using index; Using filesort |
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

mysql> 

這里為什么又是filesort呢?不是使用了using index嗎?雖然使用了覆蓋索引(只訪問索引的查詢,即查詢只需要訪問索引,而無須訪問數據行,最簡單的理解,比如翻開一本書,從目錄頁查找某些內容,但是目錄就寫的比較詳細,我們在目錄就找到了自己想看的內容)。但是請別忘記了,idx_stored_email是復合索引,必須遵循最左前綴的原則。

我們改成如下SQL,就可以看見效果了:

mysql> explain select store_id , email from customer order  by store_id ;              
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+
| id | select_type | table    | type  | possible_keys | key              | key_len | ref  | rows | Extra       |
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+
|  1 | SIMPLE      | customer | index | NULL          | idx_stored_email | 154     | NULL |  671 | Using index |
+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql> 

Filesort是通過相應的排序算法,將取得的數據在sort_buffer_size系統變量設置的內存排序區中進行排序,如果內存裝載不下,它就會將磁盤上的數據進行分塊,再對各個數據塊進行排序,然后將各個塊合並成有序的結果集。sort_buffer_size設置的排序區是每個線程獨占的,所以在同一個時刻,mysql中存在多個sort buffer排序區。該值不要設置的太大,避免耗盡服務器內存。

簡單來說,盡量減少額外排序,通過索引直接返回有序數據。where條件和order by使用相同的索引,並且order by的順序和索引順序相同。並且order by的字段都是升序或者降序。否則肯定需要額外的排序操作,這樣就會出現Filesort。

mysql> explain select store_id , email , customer_id from customer where store_id =1 order by email desc;
+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+--------------------------+
| id | select_type | table    | type | possible_keys                    | key              | key_len | ref   | rows | Extra                    |
+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | customer | ref  | idx_fk_store_id,idx_stored_email | idx_stored_email | 1       | const |  325 | Using where; Using index |
+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

mysql> 

針對Filesort優化MySQL Server

通過創建合適的索引當然能夠減少Filesort的出現。但是在某些特殊情況下,條件限制不能讓Filesort消失,那就需要想辦法加快Filesort的操作。對於Filesort,mysql有兩種排序算法。

1.兩次掃描算法(Two Passes)

首先 根據條件取出排序字段和行指針信息,之后在排序區sort buffer中排序。如果sort buffer不夠,則在臨時表Temporary Table中存儲排序結果。完成排序后根據行指針回表讀取記錄。該算法是MySQL 4.1之前采用的算法,需要兩次訪問數據,第一次獲取排序字段和行指針信息,第二次根據行指針記錄獲取記錄,尤其是第二次讀取操作可能導致大量隨機I/O操作,優點是排序的時候內存開銷比較小。

2.一次掃描算法(Single passes)

一次性取出滿足條件的行的所有字段,然后在排序區sort buffer中排序后直接輸出結果集。排序的時候內存開銷比較大,但是排序效率比兩次掃描算法高。

MySQL通過比較系統變量max_length_for_sort_data的大小和Query語句取出的字段總大小來判斷使用哪種排序算法。如果max_length_for_sort_data更大,那么使用第二種排序算法,否則使用第一種。

適當加大系統變量max_length_for_sort_data的值,能夠讓MySQL選擇更優化的排序算法,即第二種算法。當然設置max_length_for_sort_data 過大,會造成CPU利用率過低和磁盤I/O過高,CPU和I/O利用平衡就足夠了。

適當加大sort_buffer_size排序區,盡量讓排序在內存中完成,而不是通過創建臨時表放在文件中進行,當然也不能無限制加大sort_buffer_size排序區,因為sort_buffer_szie參數是每個線程獨占,設置過大,會導致服務器SWAP嚴重。

盡量只使用必要的字段,SELECT具體的字段名稱,而不是SELECT * 選擇所有字段,這樣可以減少排序區的使用。提高SQL性能。

4.優化GROUP BY 語句

默認情況下,mysql對所有GROUP BY col1,col2,的字段進行排序。這與在查詢中指定ORDER BY col1,col2類似。因此,如果顯式包括一個 包含相同列的ORDER BY子句,則對mysql的實際性能沒有什么影響。如果查詢包括GROUP BY,但我們想要避免排序帶來的性能損耗,則可以指定ORDER BY NULL禁止排序,示例如下:

mysql> explain select payment_date , sum(amount) from payment group by payment_date;                  
+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | payment | ALL  | NULL          | NULL | NULL    | NULL | 15422 | Using temporary; Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+
1 row in set (0.00 sec)

mysql> 

可以看見使用了Filesort,還使用了內存臨時表,這條SQL嚴重影響性能,所以需要優化:

首先禁止排序,ORDER BY NULL

mysql> explain select payment_date , sum(amount) from payment group by payment_date order by null;    
+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows  | Extra           |
+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+
|  1 | SIMPLE      | payment | ALL  | NULL          | NULL | NULL    | NULL | 15422 | Using temporary |
+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+
1 row in set (0.00 sec)

mysql> 

可以看見已經沒有使用Filesort,但是還是使用了內存臨時表,這是我們可以創建一個復合索引來優化性能

mysql> alter table payment add key idx_pal (payment_date,amount,last_update);                                     
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select payment_date , sum(amount) from payment group by payment_date;              
+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows  | Extra       |
+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+
|  1 | SIMPLE      | payment | index | NULL          | idx_pal | 15      | NULL | 15422 | Using index |
+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+
1 row in set (0.01 sec)

mysql> 

5.優化子查詢

MySQL 4.1開始支持SQL的子查詢。這個技術可以使用SELECT語句來創建一個單列的查詢結果,然后把這個結果作為過濾條件用在另外一個SELECT語句中。使用子查詢可以一次性完成很多邏輯上需要多個步驟才能完成的SQL操作,同時也可以避免事務或者表鎖死,並且寫起來也非常easy,but,在有些情況下,子查詢效率非常低下,我們可以使用比較高大上的寫法,那就是連接(JOIN)取而代之.^_^,下面是一個列子:

mysql> explain select * from customer where customer_id not in ( select customer_id from payment);
+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+
| id | select_type        | table    | type           | possible_keys      | key                | key_len | ref  | rows | Extra       |
+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+
|  1 | PRIMARY            | customer | ALL            | NULL               | NULL               | NULL    | NULL |  671 | Using where |
|  2 | DEPENDENT SUBQUERY | payment  | index_subquery | idx_fk_customer_id | idx_fk_customer_id | 2       | func |   12 | Using index |
+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+
2 rows in set (0.00 sec)

mysql> 

我解釋一下這里的執行計划:

第二行,id為2,說明優先級最高,最先執行,DEPENDENT SUBQUERY子查詢中的第一個SELECT(意味着select依賴於外層查詢中的數據),type為index_subquery,與unique_subquery類似,區別在於in的后面是查詢非唯一索引字段的子查詢,using index使用了覆蓋索引。

第一行,id為1,說明優先級最低,可以看見select_type列是PRIMARY,意思是最外層的SELECT查詢,可以看見使用了全表掃描。

如果使用連接(join)來完成這個查詢,速度將會快很多。尤其是連接條件有索引的情況下:

mysql> explain select * from customer left join payment on customer.customer_id = payment.customer_id where  payment.customer_id is null;
+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+
| id | select_type | table    | type | possible_keys      | key                | key_len | ref                         | rows | Extra                   |
+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+
|  1 | SIMPLE      | customer | ALL  | NULL               | NULL               | NULL    | NULL                        |  671 |                         |
|  1 | SIMPLE      | payment  | ref  | idx_fk_customer_id | idx_fk_customer_id | 2       | sakila.customer.customer_id |   12 | Using where; Not exists |
+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+
2 rows in set (0.00 sec)

mysql> 

從執行計划看出查詢關聯類型從index_subquery調整為了ref,在mysql5.5(包含mysql5.5),子查詢效率還是不如關聯查詢(join),連接之所以更有效率,是因為MySQL不需要在內存中創建臨時表來完成這個邏輯上需要兩個步驟的查詢工作。

 6.優化OR條件

對於含有OR的查詢語句,則無法使用單列索引,但是可以使用復合索引

mysql> explain select * from film where language_id=1 or title ='ACADEMY DINOSAUR';
+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys                | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | ALL  | idx_title,idx_fk_language_id | NULL | NULL    | NULL | 1133 | Using where |
+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql> 

下面看一個較簡單明了的例子:

mysql> show create table tt\G
*************************** 1. row ***************************
       Table: tt
Create Table: CREATE TABLE `tt` (
  `id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  KEY `id` (`id`),
  KEY `age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> select * from tt;
+------+------+
| id   | age  |
+------+------+
|    1 |   23 |
|    2 |   36 |
+------+------+
2 rows in set (0.00 sec)

mysql> 

可以看見表tt有兩個單列索引,我們使用如下SQL查詢,看是否會使用索引

mysql> explain select * from tt where id=1 or age=36;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tt    | ALL  | id,age        | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.01 sec)

mysql> 

可以看見雖然顯示有id,age索引可用,但是沒有使用,即全表掃描。我們可以這樣優化:

mysql> explain select * from tt where id=1 union all select *  from tt where age=36;
+----+--------------+------------+------+---------------+------+---------+-------+------+-------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref   | rows | Extra       |
+----+--------------+------------+------+---------------+------+---------+-------+------+-------------+
|  1 | PRIMARY      | tt         | ref  | id            | id   | 5       | const |    1 | Using where |
|  2 | UNION        | tt         | ref  | age           | age  | 5       | const |    1 | Using where |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL  | NULL |             |
+----+--------------+------------+------+---------------+------+---------+-------+------+-------------+
3 rows in set (0.00 sec)

mysql> 

可以看見已經使用了索引,至於這里的執行計划,我就不再說明。有機會我會寫一篇mysql執行計划的文章。

看看使用復合索引查詢的情況:

mysql> show create table tt\G
*************************** 1. row ***************************
       Table: tt
Create Table: CREATE TABLE `tt` (
  `id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  KEY `idx_id_age` (`id`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> explain select * from tt where id=1 or age=36;
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | tt    | index | idx_id_age    | idx_id_age | 10      | NULL |    2 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

mysql> 

 7.優化分頁查詢(LIMIT)

一般分頁查詢時,通過創建覆蓋索引能夠比較好的提高性能。比較常見的一個分頁查詢是limit 1000,20,這種最蛋碎了,此時mysql排序出前1020條記錄后僅僅返回第1001到1020條記錄,前1000條記錄都會被拋棄,查詢和排序的代價非常高。

在索引上完成排序分頁操作,最后根據主鍵關聯回表查詢所需要的其他列內容。(使用到了自連接)例如下面的SQL語句:

mysql> explain select film_id , description from film order by title limit 50,5;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL | 1133 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql> 

可以看見實際上使用了全表掃描,如果表有上百萬記錄,那么這將是一條致命SQL

我們改寫成按照索引分頁后回表讀取行的方式,從執行計划中看不到全表掃描了

mysql> explain select a.film_id , a.description from film a inner join (select film_id from film order by title limit 50,5) b on a.film_id=b.film_id; 
+----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+
| id | select_type | table      | type   | possible_keys | key       | key_len | ref       | rows | Extra       |
+----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL      | NULL    | NULL      |    5 |             |
|  1 | PRIMARY     | a          | eq_ref | PRIMARY       | PRIMARY   | 2       | b.film_id |    1 |             |
|  2 | DERIVED     | film       | index  | NULL          | idx_title | 767     | NULL      |   55 | Using index |
+----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+
3 rows in set (0.00 sec)

mysql> 

這里我大概解釋一下執行計划:

第三行:

id為2,優先級最高,最先執行
select_type為DERIVED 用來表示包含在from子句中的子查詢的select,mysql會遞歸執行並將結果放到一個臨時表中。服務器內部稱為“派生表”,因為該臨時表是從子查詢中派生出來的。
type列為index表示索引樹全掃描,mysql遍歷整個索引來查詢匹配的行,這里就把film_id查詢出來了。
Extra列為using index 表示使用覆蓋索引

第二行:
select_type為PRIMARY,即復雜查詢的最外層,當然這里還不算是最最外層。
table列為a,即film表的別名a,
type列為eq_ref,類似ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連接中使用primary key或者 unique key作為關聯條件

第一行:
select_type列的primary表示該查詢為外層查詢
table列被標記為<derived2>,表示查詢結果來自一個衍生表,其中2代表該查詢衍生自第2個select查詢,即id為2的select

7.其他優化手段

當然還有其他的優化手段,比如索引提示,我這里簡單列舉一下就行了,因為大部分的時候mysql 優化器都工作的很好。

USE INDEX

提供給優化器參考的索引列表(優化器不一定給你面子哦)

IGNORE INDEX

提示優化器忽略一個或者多個索引

FORCE INDEX

強制優化器使用某個特定索引

 

總結一下:

其實SQL語句優化的過程中,無非就是對mysql的執行計划理解,以及B+樹索引的理解,其實只要我們理解執行計划和B+樹以后,優化SQL語句還是比較簡單的,當然還有特別復雜的SQL,我這里只是一些簡單例子,當然再復雜的SQL,還是逃脫不了原理性的東西。呵呵。^_^


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM