1. 程式人生 > 實用技巧 >深入淺出Mysql優化效能提升

深入淺出Mysql優化效能提升

SQL 優化

在應用的的開發過程中,由於初期資料量小,開發人員寫 SQL 語句時更重視功能上的實現, 但是當應用系統正式上線後,隨著生產資料量的急劇增長,很多 SQL 語句開始逐漸顯露出 效能問題,對生產的影響也越來越大,此時這些有問題的 SQL 語句就成為整個系統性能的 瓶頸,因此我們必須要對它們進行優化,本章將詳細介紹在 MySQL 中優化 SQL 語句的方法。

1、優化SQL語句的一般步驟

1.1 通過 show status 命令瞭解各種SQL的執行頻率

MySQL 客戶端連線成功後,通過 show [session | global] status 命令可以提供伺服器狀態資訊,也可以在作業系統上使用 mysqladmin extended-status

命令獲得這些訊息。show [session | global] status 可以根據需要加上引數 session 或者 global 來顯示 session 級(當前連線)的統計結果和 global 級(自資料庫上次啟動至今)的統計結果。如果不寫,預設使用引數是session

下面的命令顯示了當前 session中所有的統計引數的值:

mysql> show status like 'Com_%'
+--------------------------+-------+ | Variable_name | Value |
+--------------------------+-------+
| Com_admin_commands | 0 |
| Com_alter_db | 0 |
| Com_alter_event | 0 |
| Com_alter_table | 0 |
| Com_analyze | 0 |
| Com_backup_table | 0 |
| Com_begin | 0 |
| Com_change_db | 1 |
| Com_change_master | 0 |
| Com_check | 0 |
| Com_checksum | 0 |
| Com_commit | 0 |
……

Com_xxx表示每個 xxx 語句執行的次數,我們通常比較關心的是以下幾個統計引數。

  • Com_select:執行 SELECT 操作的次數,一次查詢只累加 1。
  • Com_insert:執行 INSERT 操作的次數,對於批量插入的 INSERT 操作,只累加一次。
  • Com_update:執行 UPDATE 操作的次數。
  • Com_delete:執行 DELETE 操作的次數。

上面這些引數對於所有儲存引擎的表操作都會進行累計。下面這幾個引數只是針對 InnoDB 儲存引擎的,累加的演算法也略有不同。

  • Innodb_rows_readSELECT 查詢返回的行數。
  • Innodb_rows_inserted
    :執行 INSERT 操作插入的行數。
  • Innodb_rows_update:執行 UPDATE 操作更新的行數。
  • Innodb_rows_deleted:執行 DELETE 操作刪除的行數。

通過以上幾個引數,可以很容易地瞭解當前資料庫的應用是以插入更新為主還是以查詢操作為主,以及各種型別的 SQL 大致的執行比例是多少。對於更新操作的計數,是對執行次數的計數,不論提交還是回滾都會進行累加。

對於事務型的應用,通過 Com_commitCom_rollback可以瞭解事務提交和回滾的情況,對於回滾操作非常頻繁的資料庫,可能意味著應用編寫存在問題。

  • Connections:試圖連線 MySQL 伺服器的次數。
  • Uptime:伺服器工作時間。
  • Slow_queries:慢查詢的次數。

1.2 定位執行效率較低的 SQL 語句

可以通過以下兩種方式定位執行效率較低的 SQL 語句。

  • 通過慢查詢日誌定位那些執行效率較低的 SQL 語句,用--log-slow-queries[=file_name] 選項啟動時,mysqld 寫一個包含所有執行時間超過long_query_time秒的 SQL 語句的日誌檔案。
  • 慢查詢日誌在查詢結束以後才記錄,所以在應用反映執行效率出現問題的時候查詢慢查詢日誌並不能定位問題,可以使用show processlist命令檢視當前 MySQL 在進行的執行緒,包括執行緒的狀態、是否鎖表等,可以實時地檢視 SQL 的執行情況,同時對一些鎖表操作進行優化。

1.3 通過 EXPLAIN 分析低效 SQL 的執行計劃

通過以上步驟查詢到效率低的 SQL 語句後,可以通過 EXPLAIN或者DESC命令獲取 MySQL 如何執行 SELECT 語句的資訊,包括在 SELECT 語句執行過程中表如何連線和連線的順序。例如想計算 2006 年所有公司的銷售額,需要關聯 sales 表和 company 表,並且對 moneys 欄位做求和(sum)操作,相應的 SQL 的執行計劃如下:

mysql> explain select sum(moneys) from sales a,compnay b where a.company_id = b.id and a.year = 2006\G;
*************************** 1. row ***************************
             id: 1
    select_type: SIMPLE
          table: a
           type: ALL
  possible_keys: NULL
            key: NULL
        key_len: NULL
            ref: NULL
           rows: 1000
          Extra: Using where
*************************** 2. row ***************************
             id: 1
    select_type: SIMPLE
          table: b
           type: ref
  possible_keys: ind_company_id
            key: ind_company_id
        key_len: 5
            ref: sakila.a.company_id
           rows: 1
          Extra: Using where; Using index
2 rows in set (0.00 sec)

每個列的簡單解釋如下:

  • select_type:表示 SELECT 的型別,常見的取值有 :
    • SIMPLE(簡單表,即不使用表連線或者子查詢);
    • PRIMARY (主查詢,即外層查詢);
    • UNIONUNION 中的第二個或者後面的查詢語句);
    • SUBQUERY(子查詢中的第一個 SELECT)等。
  • table:輸出結果集的表。
  • type:表示表的連線型別,效能由好到差的連線型別為
    • system(表中僅有一行,即常量表);
    • const(單表中最多有一個匹配行,例如 primary key 或者 unique index);
    • eq_ref(對於前面的每一行,在此表中只查詢一條記錄,簡單來說,就是多表連線中使用 primary key 或者 unique index);
    • ref(與 eq_ref 類似,區別在於不是使用 primary key 或者 unique index,而是使用普通的索引);
    • ref_or_null(與 ref 類似,區別在於條件中包含對 NULL 的查詢);
    • index_merge(索引合併優化);
    • unique_subqueryin 的後面是一個查詢主鍵欄位的子查詢);
    • index_subquery(與 unique_subquery 類似、區別在於 in 的後面是查詢非唯一索引欄位的子查詢);
    • range(單表中的範圍查詢);
    • index(對於前面的每一行,都通過查詢索引來得到資料);
    • all(對於前面的每一行,都通過全表掃描來得到資料)。
  • possible_keys:表示查詢時,可能使用的索引。
  • key:表示實際使用的索引。
  • key_len:索引欄位的長度。
  • rows:掃描行的數量。
  • Extra:執行情況的說明和描述。
#### 1.4 確定問題並採取相應的優化措施

一般通過建立對應的索引可以達到查詢優化的目的。

上面的例子中,已經可以確認是對 a 表的全表掃描導致效率的不理想,那麼對 a 表的 year 欄位建立索引,具體如下:

mysql> create index idx_sales2_year on sales2(year);

2、索引的問題

索引是資料庫優化中最常用也是最重要的手段之一,通過索引通常可以幫助使用者解決大多數 的 SQL 效能問題。本節將對 MySQL 中的索引的分類、儲存、使用方法做詳細的介紹。

2.1 索引的儲存分類

MyISAM 儲存引擎的表的資料和索引是自動分開儲存的,各自是獨立的一個檔案;InnoDB 儲存引擎的表的資料和索引是儲存在同一個表空間裡面,但可以有多個檔案組成。

MySQL 中索引的儲存型別目前只有兩種(BTREEHASH),具體和表的儲存引擎相關:MyISAMInnoDB 儲存引擎都只支援 BTREE 索引;MEMORY/HEAP 儲存引擎可以支援 HASHBTREE 索引。

MySQL 目前不支援函式索引,但是能對列的前面某一部分進索引,例如 name 欄位,可以只取 name 的前 4 個字元進行索引,這個特性可以大大縮小索引檔案的大小,使用者在設計表結構的時候也可以對文字列根據此特性進行靈活設計。下面是建立字首索引的一個例子:

mysql> create index idx_company2_name on compnay2(name(4));
Query OK, 1000 rows affected (0.03 sec)
Records: 1000 Duplicates: 0 Warnings: 0

2.2 MySQL 如何使用索引

索引用於快速找出在某個列中有一特定值的行。對相關列使用索引是提高 SELECT 操作效能的最佳途徑。

查詢要使用索引最主要的條件是查詢條件中需要使用索引關鍵字,如果是多列索引,那麼只有查詢條件使用了多列關鍵字最左邊的字首時,才可以使用索引,否則將不能使用索引。

1. 使用索引

MySQL 中,下列幾種情況下有可能使用到索引。

  1. 對於建立的多列索引,只要查詢的條件中用到了最左邊的列,索引一般就會被使用,舉例說明如下:

    首先按 company_idmoneys 的順序建立一個複合索引,具體如下:

    mysql> create index idx_sales2_compnayid_moneys on sale2(company_id, moneys);
    

    然後按 company_id 進行表查詢,具體如下:

    mysql> explain select * from sales2 where company_id = 2006\G;
    *************************** 1. row ***************************
            id: 1
      select_type: SIMPLE
         table: sales2
          type: ref
    possible_keys: ind_sales2_companyid_moneys
       key_len: 5
           ref: const
          rows: 1
         Extra: Using where
    1 row in set (0.00 sec)
    

    可以發現即便 where 條件中不是用的 company_idmoneys 的組合條件,索引仍然能用到,這就是索引的 字首特性。但是如果只按 moneys 條件查詢表,那麼索引就不會被用到,具體如下:

    mysql> explain select * from sales2 where moneys = 1\G;
    *************************** 1. row ***************************
            id: 1
      select_type: SIMPLE
         table: sales2
          type: ALL
    possible_keys: NULL
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 1000
         Extra: Using where
    1 row in set (0.00 sec)
    
  2. 對於 like 的查詢,後面如果是常量並且只有 % 號不在第一個字元,索引才可能會被使用,來看下面兩個執行計劃:

    mysql> explain select * from company2 where name like '%3'\G;
            id: 1
      select_type: SIMPLE
            table: company2
             type: ALL
    possible_keys: NULL
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 1000
         Extra: Using where
    1 row in set (0.00 sec)
    
    mysql> explain select * from company2 where name like '3%'\G;
    *************************** 1. row ***************************
            id: 1
      select_type: SIMPLE
         table: company2
             type: range
    possible_keys: ind_company2_name
           key: ind_company2_name
       key_len: 11
           ref: NULL
          rows: 103
         Extra: Using where
    1 row in set (0.00 sec)
    

    可以發現第一個例子沒有使用索引,而第二個例子就能夠使用索引,區別在於 "%" 的位置不同,前者把 "%" 放到第一位就不能用到索引,而後者沒有放到第一位就使用了索引。

    另外,如果 like 後面跟的是一個列的名字,那麼索引也不會被使用。

  3. 如果對大的文字進行搜尋,使用全文索引而不用使用 like '%%'

  4. 如果列名是索引,使用 column_name is null 將使用索引。如下例中查詢 namenull 的記錄就用到了索引:

    mysql> explain select * from company2 where name is null\G;
    *************************** 1. row ***************************
            id: 1
      select_type: SIMPLE
         table: company2
          type: ref
    possible_keys: ind_company2_name
           key: ind_company2_name
       key_len: 11
           ref: const
          rows: 1
         Extra: Using where
    1 row in set (0.00 sec)
    
2. 存在索引但不使用索引

在下列的情況下,雖然存在索引,但是 MySQL 並不會使用相應的索引。

  1. 如果 MySQL 估計使用索引比全表掃描更慢,則不使用索引。例如如果列 key_part1 均勻分佈在 1 和 100 之間,下列查詢中使用索引就不是很好:

    select * from table_name where key_part1 > 1 and key_part1 < 90;
    
  2. 如果使用 MEMORY/HEAP 表並且 where 條件中不使用 “=” 進行索引列,那麼不會用到索引。heap 表只有在 "=" 的條件下才會使用索引。

  3. or 分割開的條件,如果 or 前的條件中的列有索引,而後面的列中沒有索引,那麼涉及到的索引都不會被用到,例如:

    mysql> show index from sales\G;
    *************************** 1. row ***************************
        Table: sales
      Non_unique: 1
     Key_name: ind_sales_year
    Seq_in_index: 1
     Column_name: year
       Collation: A
     Cardinality: NULL
        Sub_part: NULL
       Packed: NULL
         Null: 
      Index_type: BTREE
      Comment: 
    1 row in set (0.00 sec)
    

    從上面可以發現只有 year 列上面有索引,來看如下的執行計劃:

    mysql> explain select * from where year = 2001 or country = 'China'\G;
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
         table: sales
          type: ALL
    possible_keys: ind_sales_year
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 12
         Extra: Using where
    1 row in set (0.00 sec)
    

    可見雖然在 year 這個列上存在索引 ind_sales_year,但是這個 SQL 語句並沒有用到這個索引,原因就是 or 中有一個條件中的列沒有索引。

  4. 如果不是索引列的第一部分,如下例子:

    mysql> explain select * from sales2 where moneys = 1\G;
            id: 1
      select_type: SIMPLE
         table: sales2
          type: ALL
    possible_keys: NULL
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 1000
         Extra: Using where
    1 row in set (0.00 sec)
    

    可見雖然在 money 上面建有複合索引,但是由於 money 不是索引的第一列,那麼在查詢中這個索引也不會被 MySQL 採用。

  5. 如果 like 是以 % 開始,例如:

    mysql> explain select * from company2 where name like '%3'\G;
    *************************** 1. row ***************************
            id: 1
      select_type: SIMPLE
         table: company2
          type: ALL
    possible_keys: NULL
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 1000
         Extra: Using where
    1 row in set (0.00 sec)
    

    可見雖然在 name 上建有索引,但是由於 where 條件中 like 的值的 “%" 在第一位了,那麼 MySQL 也不會採用這個索引。

  6. 如果列型別是字串,那麼一定記得在 where 條件中把字元常量值用引號引起來,否則的話即便這個列上有索引,MySQL 也不會用到的,因為, MySQL 預設把輸入的常量值進行轉換以後才進行檢索。如下面的例子中 company2 表中的 name 欄位是字元型的,但是 SQL 語句中的條件值 294 是一個數值型值,因此即便在 name 上有索引,MySQL 也不能正確地用上索引,而是繼續進行全表掃描。

    mysql> explain select * from company2 where name = 294\G;
    *************************** 1. row ***************************
             id: 1
       select_type: SIMPLE
          table: company2
           type: ALL
     possible_keys: ind_company2_name
            key: NULL
        key_len: NULL
            ref: NULL
           rows: 1000
          Extra: Using where
    1 row in set (0.00 sec)
    mysql> explain select * from company2 where name = '294'\G;
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: company2
            type: ref
      possible_keys: ind_company2_name
                 key: ind_company2_name
         key_len: 23
             ref: const
            rows: 1
           Extra: Using where
    1 row in set (0.00 sec)
    

    從上面的例子中可以看到,第一個 SQL 語句中把一個數值型常量賦值給了一個字元型的列 name,那麼雖然在 name 列上有索引,但是也沒有用到;而第二個 SQL 語句就可以正確使用索引。

3. 檢視索引使用情況

如果索引正在工作,Handler_read_key 的值將很高,這個值代表了一個行被索引值讀的次數,很低的值表明增加索引得到的效能改善不高,因為索引並不經常使用。
Handler_read_rnd_next的值高則意味著查詢執行低效,並且應該建立索引補救。這個值的含義是在資料檔案中讀下一行的請求數。如果正進行大量的表掃描,Handler_read_rnd_next的值較高,則通常說明表索引不正確或寫入的查詢沒有利用索引,具體如下。

   mysql> show status like 'Handler_read%'
   +-----------------------+-------+
   | Variable_name | Value |
   +-----------------------+-------+
   | Handler_read_first | 0 |
   | Handler_read_key | 5 |
   | Handler_read_next | 0 |
   | Handler_read_prev | 0 |
   | Handler_read_rnd | 0 |
   | Handler_read_rnd_next | 2055 |
   +-----------------------+-------+
   6 rows in set (0.00 sec)

從上面的例子中可以看出,目前使用的 MySQL 資料庫的索引情況並不理想。

3. 兩個簡單實用的優化方法

1. 定期分析表和檢查表

分析表的語法如下:

ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

本語句用於分析和儲存表的關鍵字分佈,分析結果將可以使得系統得到準確的統計資訊,使得 SQL 能夠生成正確的執行計劃。如果使用者感覺實際執行計劃並不是預期的執行計劃,執行一次分析表可能會解決問題。在分析期間,使用一個讀取鎖定對錶進行鎖定。這對於 MyISAMBDBInnoDB 表有作用。對於 MyISAM 表,本語句與使用 myisamchk -a 相當,下例中對錶 sales 做了表分析:

  mysql> analyze table sales;
  +--------------+---------+----------+----------+
  | Table | Op | Msg_type | Msg_text |
  +--------------+---------+----------+----------+
  | sakila.sales | analyze | status | OK |
  +--------------+---------+----------+----------+
  1 row in set (0.00 sec)

檢查表的語法如下:

  CHECK TABLE tbl_name [,tbl_name]...[option]... option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}

檢查表的作用是檢查一個或多個表是否有錯誤。CHECK TABLEMyISAMInnoDB 表有作用。

對於 MyISAM 表,關鍵字統計資料被更新,例如:

  mysql> check table sales;
  +--------------+-------+----------+----------+
  | Table | Op | Msg_type | Msg_text |
  +--------------+-------+----------+----------+
  | sakila.sales | check | status | OK |
  +--------------+-------+----------+----------+
  1 row in set (0.00 sec)

CHECK TABLE 也可以檢查檢視是否有錯誤,比如在檢視定義中被引用的表已不存在。

  mysql> create view sales_view3 as select * from sales3;
  Query OK, 0 rows affected (0.00 sec)
  mysql> check table sales_view3;
  +--------------------+-------+----------+----------+
  | Table | Op | Msg_type | Msg_text |
  +--------------------+-------+----------+----------+
  | sakila.sales_view3 | check | status | OK |
  +--------------------+-------+----------+----------+
  1 row in set (0.00 sec)
  
  mysql> drop table sales3;
  Query OK, 0 rows affected (0.00 sec)
  
  mysql> check table sales_view3\G;
  *************************** 1. row ***************************
     Table: sakila.sales_view3
        Op: check
  Msg_type: error
  Msg_text: View 'sakila.sales_view3' references invalid table(s) or column(s) or function(s) 
  or definer/invoker of view lack rights to use them
  1 row in set (0.00 sec)

2. 定期優化表

優化表的語法如下:

OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

如果已經刪除了表的一大部分,或者如果已經對包含有可變長度行的表(含有 VARCHARBLOBTEXT 列的表)進行了很多更改,則應使用 OPTIMIZE TABLE 命令來進行表優化。這個命令可以將表中的空間碎片進行合併,並且可以刪除由於刪除或者更新造成的空間浪費,但 OPTIMIZE TABLE 命令只對 MyISAMBDBInnoDB 表起作用。

以下例子顯示了優化表 sales 的過程:

  mysql> optimize table sales;
  +--------------+----------+----------+----------+
  | Table | Op | Msg_type | Msg_text |
  +--------------+----------+----------+----------+
  | sakila.sales | optimize | status | OK |
  +--------------+----------+----------+----------+
  1 row in set (0.00 sec)

注意:ANALYZECHECKOPTIMIZE 執行期間將對錶進行鎖定,因此一定注意要在資料庫不繁忙的時候執行相關的操作。

4. 常用的 SQL 優化

1. 大批量的插入資料

當用 load 命令匯入資料的時候,適當的設定可以 提高匯入的速度。

對於 MyISAM 儲存引擎的表,可以通過如下方式快速的匯入大量的資料。

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

DISABLE KEYSENABLE KEYS用來開啟或者關閉 MyISAM 表非唯一索引的更新。在匯入大量的資料到一個非空的 MyISAM 表時,通過設定這兩個命令,可以提高匯入的效率。對於匯入大量資料到一個空的 MyISAM 表,預設就是先匯入資料然後才建立索引的,所以不用進行設定。

下面的例子中,用 LOAD 語句匯入資料耗時 115.12 秒:

mysql> laod data infile '/home/mysql/film_test.txt' into table film_test2;
Query OK, 529056 rows affected (1 min 55.12 sec)
Records: 529056 Deleted: 0 Skipped: 0 Warnings: 0

而用 alter table tbl_name disable keys 方式總耗時 6.34 + 12.25 = 18.59秒,提高了6倍多。

mysql> alter table film_test2 disable keys;
Query OK, 0 rows affected (0.00 sec)
mysql> load data infile '/home/mysql/film_test.txt' into table film_test2;
Query OK, 529056 rows affected (6.34 sec)
Records: 529056 Deleted: 0 Skipped: 0 Warnings: 0
mysql> alter table film_test2 enable keys;
Query OK, 0 rows affected (12.25 sec)

上面是對 MyISAM 表進行資料匯入時的優化措施,對於 InnoDB 型別的表,這種方式並不能提高匯入資料的效率,可以有以下幾種方式提高 InnoDB 表的匯入效率。

  1. 因為 InnoDB 型別的表是按照主鍵的順序儲存的,所以將匯入的資料按照主鍵的順序排列,可以有效地提高匯入資料的效率。
  2. 在匯入資料前執行 SET UNIQUE_CHECKS=0,關閉唯一性校驗,在匯入結束後執行 SET UNIQUE_CHECKS=1,恢復唯一性校驗,可以提高匯入的效率。
  3. 如果應用使用自動提交的方式,建議在匯入前執行 SET AUTOCOMMIT=0,關閉自動提交,匯入結束後再執行 SET AUTOCOMMIT=1,開啟自動提交,也可以提高匯入的效率。
2. 優化 INSERT 語句

當進行資料 INSERT 的時候,可以考慮採用以下幾種優化方式。

  • 如果同時從同一客戶插入很多行,儘量使用多個值表的 INSERT 語句,這種方式將大大縮減客戶端與資料庫之間的連線、關閉等消耗,使得效率比分開執行的單個 INSERT 語句塊(在一些情況中幾倍)。下面是一次插入多值的一個例子:

    INSERT INTO test VALUES(1,2),(1,3),(1,4)...
    
    
  • 如果從不同的客戶插入很多行,能通過使用 INSERT DELAYED 語句得到更高的速度。

    DELAYED 的含義是讓 INSERT 語句馬上執行,其實資料都被放在記憶體的佇列中,並沒有真正寫入磁碟,這比每條語句分別插入要快的多;LOW_PRIORITY 剛好相反,在所有其他使用者對錶的讀寫完後才進行插入;

  • 將索引檔案和資料檔案分在不同的磁碟上存放(利用建表中的選項);

  • 如果進行批量插入,可以增加 bulk_insert_buffer_size 變數值的方法來提高速度,但是,這針對 MyISAM 表使用;

  • 當從一個文字檔案裝載一個表時,使用 LOAD DATA INFILE。這通常比使用很多 INSERT 語句快20倍。

3. 優化 GROUP BY 語句

預設情況下,MySQL 對所有 GROUP BY col1, col2... 的欄位進行排序。這與在查詢中指定 ORDER BY col1, col2... 類似。因此,如果顯式包括一個包含相同的列的 ORDER BY 子句,則對 MySQL 的實際執行效能沒有什麼影響。

如果查詢包括 GROUP BY 但使用者想要避免排序結果的消耗,則可以指定 ORDER BY NULL 禁止排序。如下面的例子:

mysql> explain select id,sum(moneys) from sales2 group by id\G;
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: sales2
        type: ALL
  possible_keys: NULL
         key: NULL
     key_len: NULL
         ref: NULL
        rows: 1000
       Extra: Using temporary; Using filesort
1 row in set (0.00 sec)

mysql> explain select id,sum(moneys) from sales2 group by id order by null\G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: ALL
possible_keys: NULL
       key: NULL
   key_len: NULL
       ref: NULL
      rows: 1000
     Extra: Using temporary
1 row in set (0.00 sec)

從上面的例子可以看出第一個 SQL 語句需要進行 “filesort”,而第二個 SQL 由於 ORDER BY NULL 不需要進行 “filesort”,而 filesort 往往非常耗費時間。

4. 優化 ORDER BY 語句

在某些情況下,MySQL 可以使用一個索引來滿足 ORDER BY 子句,而不需要額外的排序。WHERE 條件和 ORDER BY 使用相同的索引,並且 ORDER BY 的順序和索引順序相同,並且 ORDER BY 的欄位都是升序或者都是降序。

例如,下列 SQL 可以使用索引。

SELECT * FROM t1 ORDER BY key_part1,key_part2,...;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC,key_part2 DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;

但是在以下幾種情況下則不使用索引:

SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
-- order by 的欄位混合ASC和DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
-- 用於查詢行的關鍵字與 ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2;
-- 對不同的關鍵字使用 ORDER BY

5. 優化巢狀查詢

MySQL 4.1開始支援 SQL 的子查詢。這個技術可以使用 SELECT 語句來建立一個單列的查詢結果,然後把這個結果作為過濾條件用在另一個查詢中。使用子查詢可以一次性地完成很多邏輯上需要多個步驟才能完成的 SQL 操作,同時也可以避免事務或者表鎖死,並且寫起來也很容易。但是,有些情況下,子查詢可以被更有效率的連線(JOIN)替代。

在下面的例子中,要從 sales2 表中找到那些在 company2 表中不存在的所有公司的資訊:

mysql> explain select * from sales2 where company_id not in ( select id from company2 )\G;
*************************** 1. row ***************************
        id: 1
  select_type: PRIMARY
     table: sales2
      type: ALL
possible_keys: NULL
       key: NULL
   key_len: NULL
       ref: NULL
      rows: 1000
     Extra: Using where
*************************** 2. row ***************************
        id: 2
  select_type: DEPENDENT SUBQUERY
     table: company2
      type: index_subquery
possible_keys: ind_company2_id
       key: ind_company2_id
   key_len: 5
       ref: func
      rows: 2
     Extra: Using index
2 rows in set (0.00 sec)

如果使用連線 (JOIN)來完成這個查詢工作,速度將會快很多。尤其是當 company2 表中有對 id 建有索引的話,效能將會更好,具體查詢如下:

mysql> explain select * from sales2 left join company2 on sales2.company_id = company2.id where sales2.company_id is null\G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: ref
possible_keys: ind_sales2_companyid_moneys
       key: ind_sales2_companyid_moneys
   key_len: 5
       ref: const
      rows: 1
     Extra: Using where
*************************** 2. row ***************************
        id: 1
  select_type: SIMPLE
     table: company2
      type: ref
possible_keys: ind_company2_id
       key: ind_company2_id
   key_len: 5
       ref: sakila.sales2.company_id
      rows: 1
     Extra: 
2 rows in set (0.00 sec)

從執行計劃中可以明顯看出查詢掃描的記錄範圍和使用索引的情況都有了很大的改善。連線 (JOIN)之所以更有效率一些,是因為 MySQL 不需要在記憶體中建立臨時表來完成這個邏輯上的需要兩個步驟的查詢工作。

6. MySQL 如何優化 OR 條件

對於含有 OR 的查詢子句,如果要利用索引,則 OR 之間的每個條件列都必須用到索引;如果沒有索引,則應該考慮增加索引。

例如,首先使用 show index 命令查看錶 sales2 的索引,可知它有 3個索引,在idyear 兩個欄位上分別有一個獨立的索引,在 company_idyear 欄位上有一個複合索引。

mysql> show index from sales2\G;
*************************** 1. row ***************************
       Table: sales2
  Non_unique: 1
    Key_name: ind_sales2_id
Seq_in_index: 1
 Column_name: id
   Collation: A
 Cardinality: 1000
    Sub_part: NULL
      Packed: NULL
     Null: YES
  Index_type: BTREE
  Comment: 
*************************** 2. row ***************************
       Table: sales2
  Non_unique: 1
    Key_name: ind_sales2_year
Seq_in_index: 1
 Column_name: year
   Collation: A
 Cardinality: 250
    Sub_part: NULL
   Packed: NULL
     Null: YES
  Index_type: BTREE
  Comment: 
*************************** 3. row ***************************
    Table: sales2
  Non_unique: 1
 Key_name: ind_sales2_companyid_moneys
Seq_in_index: 1
 Column_name: company_id
   Collation: A
 Cardinality: 1000
    Sub_part: NULL
      Packed: NULL
        Null: YES
  Index_type: BTREE
  Comment: 
*************************** 4. row ***************************
       Table: sales2
  Non_unique: 1
    Key_name: ind_sales2_companyid_moneys
Seq_in_index: 2
 Column_name: year
   Collation: A
 Cardinality: 1000
 Sub_part: NULL
   Packed: NULL
     Null: YES
  Index_type: BTREE
  Comment: 
4 rows in set (0.00 sec)

然後在兩個獨立索引上面做 OR 操作,具體如下:

mysql> explain select * from sales2 where id = 2 or year = 1998\G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: index_merge
possible_keys: ind_sales2_id,ind_sales2_year
       key: ind_sales2_id,ind_sales2_year
   key_len: 5,2
       ref: NULL
      rows: 2
     Extra: Using union(ind_sales2_id,ind_sales2_year); Using where
1 row in set (0.00 sec)

可以發現查詢正確的用到了索引,並且從執行計劃的描述中,發現 MySQL 在處理含有 OR 子句的查詢時,實際是對 OR 的各個欄位分別查詢後的結果進行了 UNION。

但是當在建有複合索引的列 company_id 和 moneys 上面做 OR 操作的時候,卻不能用到索引,具體如下:

mysql> explain select * from saels2 where company_id = 3 or moneys = 100\G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: ALL
possible_keys: ind_sales2_companyid_moneys
       key: NULL
   key_len: NULL
       ref: NULL
      rows: 1000
     Extra: Using where
1 row in set (0.00 sec)

7. 使用 SQL 提示

SQL 提示(SQL HINT)是優化資料庫的一個重要手段,簡單來說就是在 SQL 語句中加入一些人為的提示來達到優化操作的目的。

下面是一個使用 SQL 提示的例子:

SELECT SQL_BUFFER_RESULTS * FROM...

這個語句將強制 MySQL 生成一個臨時結果集。只要臨時結果集生成後,所有表上的鎖定均被釋放。這能在遇到表鎖定問題時或要花很長時間將結果傳給客戶端時有所幫助,因為可以儘快釋放鎖資源。

下面是一些在 MySQL 中常用的 SQL 提示。

1. USE INDEX
  在查詢語句中表名的後面,新增 **USE INDEX** 來提供希望 **MySQL** 去參考的索引列表,就可以讓 **MySQL** 不再考慮其他可用的索引。

  ```mysql
  mysql> explain select * from sales2 use index(ind_sales2_id) where id = 3\G;
  *************************** 1. row ***************************
           id: 1
    select_type: SIMPLE
        table: sales2
         type: ref
  possible_keys: ind_sales2_id
          key: ind_sales2_id
      key_len: 5
          ref: const
         rows: 1
        Extra: Using where
  1 row in set (0.00 sec).
  
  ```

  ​     
2. IGNORE INDEX
  如果使用者只是單純地想讓 **MySQL** 忽略一個或者多個索引,則可以使用 **IGNORE INDEX** 作為 **HINT**。同樣是上面的例子,這次來看一下查詢過程忽略索引 *ind_sales2_id* 的情況:

  ```mysql
  mysql> explain select * from sales2 ignore index(ind_sales2_id) where id=3\G;
  *************************** 1. row ***************************
           id: 1
    select_type: SIMPLE
        table: sales2
         type: ALL
  possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1000
        Extra: Using where
  1 row in set (0.00 sec).
  
  ```

  從執行計劃可以看出,系統忽略了指定的索引,而使用了全表掃描。
3. FORCE INDEX
  為強制 **MySQL** 使用一個特定的索引,可在查詢中使用 **FORCE INDEX** 作為 **HINT**。例如,當不強制使用索引的時候,因為 *id* 的值都是大於 *0* 的,因此 **MySQL** 會預設進行全表掃描,而不使用索引,如下所示:
mysql> explain select * from sales2 where id > 0 \G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: ALL
possible_keys: ind_sales2_id
       key: NULL
   key_len: NULL
       ref: NULL
      rows: 1000
     Extra: Using where
1 row in set (0.00 sec)

但是,當使用 FORCE INDEX 進行提示時,即便使用索引的效率不是最高,MySQL 還是選擇使用了索引,這是 MySQL 留給使用者的一個自行選擇執行計劃的權利。加入 FORCE INDEX 提示後再執行上面的 SQL:

mysql> explain select * from sales2 force index (ind_sales2_id) where id > 0\G;
*************************** 1. row ***************************
        id: 1
  select_type: SIMPLE
     table: sales2
      type: range
possible_keys: ind_sales2_id
       key: ind_sales2_id
   key_len: 5
       ref: NULL
      rows: 1000
     Extra: Using where
1 row in set (0.00 sec).

果然,執行計劃中使用了 FORCE INDEX 後的索引。



作者:安靜點就睡吧
連結:https://www.jianshu.com/p/1cc49f887ac7
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。