1. 程式人生 > >SQL優化的一般步驟

SQL優化的一般步驟

在MySQL資料庫中,當面對一個有sql效能問題的資料庫時,我們可以使用show status、explain、show profile等命令進行系統分析,儘快定位問題SQL並解決問題。

一、通過show status命令瞭解各種SQL的執行情況

mysql> show status like 'Com_%';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| Com_admin_commands        |
0 | | Com_assign_to_keycache | 0 | | Com_alter_db | 0 | | Com_alter_db_upgrade | 0 | | Com_alter_event | 0 | | Com_alter_function | 0 | | Com_alter_procedure | 0 | | Com_alter_server | 0 | | Com_alter_table | 0 |
| Com_alter_tablespace | 0 | | Com_alter_user | 0 | | Com_analyze | 0 | ...

Com_xxx表示xxx語句執行的次數,通過觀察Com_insert、Com_select、Com_update、Com_delete等引數可以瞭解當前資料庫是以插入更新為主還是以查詢為主。以及各種型別的SQL大致的執行比例是多少。
對於事務性應用,通過Com_commit、Com_rollback可以瞭解事務提交和回滾的情況,對於事務回滾非常頻繁的資料庫,意味著應用編寫可能存在問題。

二、通過explain分析低效SQL的執行計劃

通過慢查詢日誌可以定位那些執行效率較低的SQL語句。之後可以通過explain或者desc命令獲取MySQL執行select語句的資訊。

mysql> explain select * from demo where id>5 and id<10 \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: demo
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 3
        Extra: Using where
1 row in set (0.00 sec)

explain命令顯示select語句的執行資訊,下面對每個欄位進行說明

1、select_type

表示select的型別,常見取值有:

  • select_type=SIMPLE:簡單表,即不使用表連線和子查詢
  • select_type=PRIMARY:主查詢,即外層查詢
  • select_type=UNION:UNION中的第二個或者後面的查詢
  • select_type=SUBQUERY:子查詢中的第一個select
2、table

輸出結果集的表

3、type

表示MySQL在表中找到所需行的方式,或者叫訪問型別。常見取值有:ALL、index、range、ref、eq_ref、const/system、NULL。從左到右,效能由最差到最好。

  • type=ALL:全表掃描,MySQL遍歷全表來找到匹配的行
  • type=index:索引全掃描。MySQL遍歷整個索引來查詢匹配的行
  • type=range:索引範圍掃描。常見於<、<=、>、>=、between等操作符
  • type=ref:使用非唯一索引掃描或唯一索引字首掃描,返回匹配某個單獨值的記錄行
  • type=eq_ref:類似ref,區別在於使用的索引是唯一索引,對於每個索引的鍵值,表中只有一條記錄匹配。
  • type=const/system:但表中最多有一個匹配行,查詢起來非常迅速。常見於根據主鍵primary key或唯一索引unique index進行的查詢。
  • type=NULL:MySQL不用訪問表或者索引,直接就能得到結果。比如 select 2 > 1;
  • 其他
4、possible_keys

表示查詢時可能使用的索引

5、key

表示實際使用的索引

6、key_len

使用到索引欄位的長度

7、rows

掃描行的數量

8、Extra

執行情況的說明和描述,包含不適合在其他列中顯示,但對執行計劃非常重要的額外資訊

使用explain extended命令加上show warnings,可以看到SQL真正被執行前優化做了哪些改寫:

mysql> explain extended select sum(salary) from demo a , role b where 1=1 and a.id=b.id and a.sex='男
' \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
         type: index
possible_keys: PRIMARY
          key: roleName_id
      key_len: 63
          ref: NULL
         rows: 6
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.b.id
         rows: 1
     filtered: 100.00
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

ERROR:
No query specified

mysql> show warnings \G;
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select sum(`test`.`a`.`salary`) AS `sum(salary)` from `test`.`demo` `a` join
`test`.`role` `b` where ((`test`.`a`.`id` = `test`.`b`.`id`) and (`test`.`a`.`sex` = '男'))
1 row in set (0.00 sec)

通過show warning命令的message欄位可以看到優化器去除了1=1的恆等條件,在遇到複雜SQL可以通過explain extended命令加show warning命令迅速獲得一個清晰易讀的語句

三、通過show profile分析SQL

通過profile,我們可以更清楚地瞭解SQL的執行過程,及各個過程所花時間。
檢視當前MySQL是否支援profile:

mysql> select @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES              |
+------------------+
1 row in set, 1 warning (0.00 sec)

預設profiling是關閉的,可以通過set語句在Session級別開啟profiling:

mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

在一個InnoDB表上查詢總行數:

mysql> select count(*) from tb_item;
+----------+
| count(*) |
+----------+
|     3096 |
+----------+
1 row in set (0.01 sec)

通過show profiles語句檢視當前查詢語句的Query ID:

mysql> show profiles;
+----------+------------+------------------------------+
| Query_ID | Duration   | Query                        |
+----------+------------+------------------------------+
|        1 | 0.00025375 | SELECT DATABASE()            |
|        2 | 0.00505650 | select count(*) from tb_item |
+----------+------------+------------------------------+
2 rows in set, 1 warning (0.00 sec)

通過show profile for query 2 看到執行過程中執行緒的執行狀態和消耗的時間:

mysql> show profile for query 2;
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000117 |
| checking permissions | 0.000012 |
| Opening tables       | 0.000028 |
| init                 | 0.000021 |
| System lock          | 0.000015 |
| optimizing           | 0.000009 |
| statistics           | 0.000023 |
| preparing            | 0.000019 |
| executing            | 0.000005 |
| Sending data         | 0.004631 |
| end                  | 0.000017 |
| query end            | 0.000013 |
| closing tables       | 0.000022 |
| freeing items        | 0.000099 |
| cleaning up          | 0.000028 |
+----------------------+----------+
15 rows in set, 1 warning (0.00 sec)

為了更加直觀看到排序結果,可以查詢information_schema.profiling表並按消耗時間做個desc排序:

mysql> select state,sum(duration) as Total_R ,
    ->  round(
    ->          100*sum(duration)/(select sum(duration) from information_schema.profiling
    ->                  where query_id = 2),2
    ->          )as Pct_R,
    ->  count(*)
    ->  from information_schema.profiling
    ->  where query_id=2
    ->  group by state
    ->  order by Total_R desc;
+----------------------+----------+-------+----------+
| state                | Total_R  | Pct_R | count(*) |
+----------------------+----------+-------+----------+
| Sending data         | 0.004631 | 91.54 |        1 |
| starting             | 0.000117 |  2.31 |        1 |
| freeing items        | 0.000099 |  1.96 |        1 |
| cleaning up          | 0.000028 |  0.55 |        1 |
| Opening tables       | 0.000028 |  0.55 |        1 |
| statistics           | 0.000023 |  0.45 |        1 |
| closing tables       | 0.000022 |  0.43 |        1 |
| init                 | 0.000021 |  0.42 |        1 |
| preparing            | 0.000019 |  0.38 |        1 |
| end                  | 0.000017 |  0.34 |        1 |
| System lock          | 0.000015 |  0.30 |        1 |
| query end            | 0.000013 |  0.26 |        1 |
| checking permissions | 0.000012 |  0.24 |        1 |
| optimizing           | 0.000009 |  0.18 |        1 |
| executing            | 0.000005 |  0.10 |        1 |
+----------------------+----------+-------+----------+
15 rows in set (0.00 sec)

可以清晰地看到時間主要消耗在Sending data這個狀態上。原因是MySQL執行緒要在該狀態做大量的磁碟讀取操作。