優化 sql 語句的通常步驟

1、經過 show status 命令瞭解各類 sql 的執行頻率

mysql 客戶端鏈接成功後,經過 show [session|global] status 命令能夠提供服務器狀態信息,也能夠在操做系統上使用 mysqladmin extend-status 命令獲取這些消息。
show status 命令中間能夠加入選項 session(默認) 或 global:mysql

  • session (當前鏈接)
  • global (自數據上次啓動至今)
# Com_xxx 表示每一個 xxx 語句執行的次數。
mysql> show status like 'Com_%';
咱們一般比較關心的是如下幾個統計參數:
  • Com_select : 執行 select 操做的次數,一次查詢只累加 1。
  • Com_insert : 執行 insert 操做的次數,對於批量插入的 insert 操做,只累加一次。
  • Com_update : 執行 update 操做的次數。
  • Com_delete : 執行 delete 操做的次數。

上面這些參數對於全部存儲引擎的表操做都會進行累計。下面這幾個參數只是針對 innodb 的,累加的算法也略有不一樣:算法

  • Innodb_rows_read : select 查詢返回的行數。
  • Innodb_rows_inserted : 執行 insert 操做插入的行數。
  • Innodb_rows_updated : 執行 update 操做更新的行數。
  • Innodb_rows_deleted : 執行 delete 操做刪除的行數。

經過以上幾個參數,能夠很容易地瞭解當前數據庫的應用是以插入更新爲主仍是以查詢操做爲主,以及各類類型的 sql 大體的執行比例是多少。對於更新操做的計數,是對執行次數的計數,不論提交仍是回滾都會進行累加。
對於事務型的應用,經過 Com_commit 和 Com_rollback 能夠了解事務提交和回滾的狀況,對於回滾操做很是頻繁的數據庫,可能意味着應用編寫存在問題。
此外,如下幾個參數便於用戶瞭解數據庫的基本狀況:sql

  • Connections : 試圖鏈接 mysql 服務器的次數。
  • Uptime : 服務器工做時間。
  • Slow_queries : 慢查詢次數。

2、定義執行效率較低的 sql 語句

1. 經過慢查詢日誌定位那些執行效率較低的 sql 語句,用 --log-slow-queries[=file_name] 選項啓動時,mysqld 寫一個包含全部執行時間超過 long_query_time 秒的 sql 語句的日誌文件。
2. 慢查詢日誌在查詢結束之後才記錄,因此在應用反映執行效率出現問題的時候慢查詢日誌並不能定位問題,可使用 show processlist 命令查看當前 mysql 在進行的線程,包括線程的狀態、是否鎖表等,能夠實時的查看 sql 的執行狀況,同時對一些鎖表操做進行優化。

3、經過 explain 分析低效 sql 的執行計劃

測試數據庫地址:https://downloads.mysql.com/d...數據庫

統計某個 email 爲租賃電影拷貝所支付的總金額,須要關聯客戶表 customer 和 付款表 payment , 而且對付款金額 amount 字段作求和(sum) 操做,相應的執行計劃以下:json

mysql> explain select sum(amount) from customer a , payment b where a.customer_id= b.customer_id and a.email='JANE.BENNETT@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)
  • select_type: 表示 select 類型,常見的取值有:服務器

    • simple:簡單表,及不使用錶鏈接或者子查詢
    • primary:主查詢,即外層的查詢
    • union:union 中的第二個或後面的查詢語句
    • subquery: 子查詢中的第一個 select
  • table : 輸出結果集的表
  • type : 表示 mysql 在表中找到所需行的方式,或者叫訪問類型,常見類型性能由差到最好依次是:all、index、range、ref、eq_ref、const,system、null:
  1. type=ALL,全表掃描,mysql 遍歷全表來找到匹配的行:session

    mysql> explain select * from film where rating > 9 \G
    
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: film
      partitions: NULL
            type: ALL
    possible_keys: NULL
             key: NULL
         key_len: NULL
             ref: NULL
            rows: 1000
        filtered: 33.33
           Extra: Using where
    1 row in set, 1 warning (0.01 sec)
  2. type=index, 索引全掃描,mysql 遍歷整個索引來查詢匹配的行性能

    mysql> explain select title form film\G
    
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: film
      partitions: NULL
            type: index
    possible_keys: NULL
             key: idx_title
         key_len: 767
             ref: NULL
            rows: 1000
        filtered: 100.00
           Extra: Using index
    1 row in set, 1 warning (0.00 sec)
  3. type=range,索引範圍掃描,常見於<、<=、>、>=、between等操做:測試

    mysql> explain select * from payment where customer_id >= 300 and customer_id <= 350 \G  
    
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: payment
      partitions: NULL
            type: range
    possible_keys: idx_fk_customer_id
             key: idx_fk_customer_id
         key_len: 2
             ref: NULL
            rows: 1350
        filtered: 100.00
           Extra: Using index condition
    1 row in set, 1 warning (0.07 sec)
  4. type=ref, 使用非惟一索引掃描或惟一索引的前綴掃描,返回匹配某個單獨值的記錄行,例如:優化

    mysql> explain select * from payment where customer_id = 350 \G  
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: payment
      partitions: NULL
            type: ref
    possible_keys: idx_fk_customer_id
             key: idx_fk_customer_id
         key_len: 2
             ref: const
            rows: 23
        filtered: 100.00
           Extra: NULL
    1 row in set, 1 warning (0.01 sec)

    索引 idx_fk_customer_id 是非惟一索引,查詢條件爲等值查詢條件 customer_id = 350, 因此掃描索引的類型爲 ref。ref 還常常出如今 join 操做中:

    mysql> explain select b.*, a.* from payment a,customer b where a.customer_id = b.customer_id \G 
    
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: b
      partitions: NULL
            type: ALL
    possible_keys: PRIMARY
             key: NULL
         key_len: NULL
             ref: NULL
            rows: 599
        filtered: 100.00
           Extra: NULL
    *************************** 2. row ***************************
              id: 1
     select_type: SIMPLE
           table: a
      partitions: NULL
            type: ref
    possible_keys: idx_fk_customer_id
             key: idx_fk_customer_id
         key_len: 2
             ref: sakila.b.customer_id
            rows: 26
        filtered: 100.00
           Extra: NULL
    2 rows in set, 1 warning (0.00 sec)
  5. type=eq_ref,相似 ref,區別就在使用的索引時惟一索引,對於每一個索引的鍵值,表中只要一條記錄匹配;簡單的說,就是多表鏈接中使用 primary key 或者 unique index 做爲關聯條件。

    mysql> explain select * from film a , film_text b where a.film_id = b.film_id \G
    
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: b
      partitions: NULL
            type: ALL
    possible_keys: PRIMARY
             key: NULL
         key_len: NULL
             ref: NULL
            rows: 1000
        filtered: 100.00
           Extra: NULL
    *************************** 2. row ***************************
              id: 1
     select_type: SIMPLE
           table: a
      partitions: NULL
            type: eq_ref
    possible_keys: PRIMARY
             key: PRIMARY
         key_len: 2
             ref: sakila.b.film_id
            rows: 1
        filtered: 100.00
           Extra: Using where
    2 rows in set, 1 warning (0.03 sec)
  6. type=const/system,單表中最多有一個匹配行,查起來很是迅速,因此這個匹配行中的其餘列的值能夠被優化器在當前查詢中看成常量來處理,例如,根據主鍵 primary key 或者惟一索引 unique index 進行查詢。

    mysql> create table test_const (
       ->         test_id int,
       ->         test_context varchar(10),
       ->         primary key (`test_id`),
       ->     );
       
    insert into test_const values(1,'hello');
    
    explain select * from ( select * from test_const where test_id=1 ) a \G
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: test_const
      partitions: NULL
            type: const
    possible_keys: PRIMARY
             key: PRIMARY
         key_len: 4
             ref: const
            rows: 1
        filtered: 100.00
           Extra: NULL
     1 row in set, 1 warning (0.00 sec)
  7. type=null, mysql 不用訪問表或者索引,直接就可以獲得結果:

    mysql> explain select 1 from dual where 1 \G
    *************************** 1. row ***************************
              id: 1
     select_type: SIMPLE
           table: NULL
      partitions: NULL
            type: NULL
    possible_keys: NULL
             key: NULL
         key_len: NULL
             ref: NULL
            rows: NULL
        filtered: NULL
           Extra: No tables used
    1 row in set, 1 warning (0.00 sec)

  類型 type 還有其餘值,如 ref_or_null (與 ref 相似,區別在於條件中包含對 null 的查詢)、index_merge(索引合併優化)、unique_subquery (in 的後面是一個查詢主鍵字段的子查詢)、index_subquery(與 unique_subquery 相似,區別在於 in 的後面是查詢非惟一索引字段的子查詢)等。

  • possible_keys : 表示查詢時可能使用的索引。
  • key :表示實際使用索引
  • key-len : 使用到索引字段的長度。
  • rows : 掃描行的數量
  • extra:執行狀況的說明和描述,包含不適合在其餘列中顯示可是對執行計劃很是重要的額外信息。
show warnings 命令

執行explain 後再執行 show warnings,能夠看到sql 真正被執行以前優化器作了哪些 sql 改寫:

MySQL [sakila]> explain select sum(amount) from customer a , payment b where 1=1 and a.customer_id = b.customer_id and email = 'JANE.BENNETT@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)

MySQL [sakila]> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                     |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select sum(`sakila`.`b`.`amount`) AS `sum(amount)` from `sakila`.`customer` `a` join `sakila`.`payment` `b` where ((`sakila`.`b`.`customer_id` = `sakila`.`a`.`customer_id`) and (`sakila`.`a`.`email` = 'JANE.BENNETT@sakilacustomer.org')) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

從 warning 的 message 字段中可以看到優化器自動去除了 1=1 恆成立的條件,也就是說優化器在改寫 sql 時會自動去掉恆成立的條件。

explain 命令也有對分區的支持.
MySQL [sakila]> CREATE TABLE `customer_part` (
    ->   `customer_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    ->   `store_id` tinyint(3) unsigned NOT NULL,
    ->   `first_name` varchar(45) NOT NULL,
    ->   `last_name` varchar(45) NOT NULL,
    ->   `email` varchar(50) DEFAULT NULL,
    ->   `address_id` smallint(5) unsigned NOT NULL,
    ->   `active` tinyint(1) NOT NULL DEFAULT '1',
    ->   `create_date` datetime NOT NULL,
    ->   `last_update` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    ->   PRIMARY KEY (`customer_id`)
    ->  
    -> ) partition by hash (customer_id) partitions 8;
Query OK, 0 rows affected (0.06 sec)

MySQL [sakila]> insert into customer_part select * from customer;
Query OK, 599 rows affected (0.06 sec)
Records: 599  Duplicates: 0  Warnings: 0

MySQL [sakila]> explain select * from customer_part where customer_id=130\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: customer_part
   partitions: p2
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warnings (0.00 sec)

能夠看到 sql 訪問的分區是 p2。

4、經過 performance_schema 分析 sql 性能

舊版本的 mysql 可使用 profiles 分析 sql 性能,我用的是5.7.18的版本,已經不容許使用 profiles 了,推薦用
performance_schema 分析sql。

5、經過 trace 分析優化器如何選擇執行計劃。

mysql5.6 提供了對 sql 的跟蹤 trace,能夠進一步瞭解爲何優化器選擇 A 執行計劃而不是 B 執行計劃,幫助咱們更好的理解優化器的行爲。

使用方式:首先打開 trace ,設置格式爲 json,設置 trace 最大可以使用的內存大小,避免解析過程當中由於默認內存太小而不可以完整顯示。

MySQL [sakila]> set optimizer_trace="enabled=on",end_markers_in_json=on;
Query OK, 0 rows affected (0.00 sec)

MySQL [sakila]> set optimizer_trace_max_mem_size=1000000;
Query OK, 0 rows affected (0.00 sec)

接下來執行想作 trace 的 sql 語句,例如像瞭解租賃表 rental 中庫存編號 inventory_id 爲 4466 的電影拷貝在出租日期 rental_date 爲 2005-05-25 4:00:00 ~ 5:00:00 之間出租的記錄:

mysql> select rental_id from rental where 1=1 and 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.06 sec)

MySQL [sakila]> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
                            QUERY: select * from infomation_schema.optimizer_trace
                            TRACE: {
  "steps": [
  ] /* steps */
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

6、 肯定問題並採起相應的優化措施

通過以上步驟,基本就能夠確認問題出現的緣由。此時能夠根據狀況採起相應的措施,進行優化以提升執行的效率。

相關文章
相關標籤/搜索