1. 程式人生 > >MYSQL學習筆記——sql語句優化工具

MYSQL學習筆記——sql語句優化工具

一、定位慢查詢                                                                                

      我們要對sql語句進行優化,第一步肯定是找到執行速度較慢的語句,那麼怎麼在一個專案裡面定位這些執行速度較慢的sql語句呢?下面就介紹一種定位慢查詢的方法。 

1.1、資料庫準備

     首先建立一個數據庫表:

1

2

3

4

5

6

7

8

9

10

CREATE TABLE emp

(empno  MEDIUMINT UNSIGNED  

NOT NULL  DEFAULT 0 COMMENT '編號',

ename VARCHAR(20) NOT NULL DEFAULT "" COMMENT '名字',

job VARCHAR(9) NOT NULL DEFAULT "" COMMENT '工作',

mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '上級編號',

hiredate DATE NOT NULL COMMENT '入職時間',

sal DECIMAL(7,2)  NOT NULL COMMENT '薪水',

comm DECIMAL

(7,2) NOT NULL COMMENT '紅利',

deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '部門編號'

)ENGINE=InnoDB DEFAULT CHARSET=utf8;

  然後我們構建一個儲存函式,這個儲存函式會返回一個長度為引數n的隨機字串:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

delimiter $$

create function rand_string(n INT)

returns varchar(255) #該函式會返回一個字串

begin

declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';

declare return_str varchar(255) default '';

declare int default 0;

while i < n do

set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));

set i = i + 1;

end while;

return return_str;

end $$

delimiter ;

    接下來我們再建立一個儲存函式,該儲存函式會返回一個隨機int值:

1

2

3

4

5

6

7

8

9

10

11

delimiter $$

create function rand_num( )

returns int(5)

begin

declare int default 0;

set i = floor(10+rand()*500);

return i;

end $$

delimiter ;

      然後我們利用剛剛建立的兩個儲存函式建立一個儲存過程,該儲存過程包含一個引數,該引數表示插入資料表emp的資料條數:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

delimiter $$

create procedure insert_emp(in max_num int(10))

begin

declare int default 0;

set autocommit = 0; 

repeat

set i = i + 1;

insert into emp values (i ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());

until i = max_num

end repeat;

commit;

end $$

delimiter ;

      最後,我們呼叫改改建立的儲存過程,對emp表插入1000w條資料:

1

call insert_emp(10000000);

1.2、檢視慢查詢

      我們可以用以下命令檢視慢查詢次數:

1

show status like 'slow_queries';

      現在在mysql中敲入該命令,可以看到value為1,這個慢查詢就是由剛剛批量插入1000w條資料產生。

      使用該命令只能檢視慢查詢次數,但是我們沒有辦法知道是哪些查詢產生了慢查詢,如果想要知道是哪些查詢導致的慢查詢,那麼我們必須修改mysql的配置檔案。開啟mysql的配置檔案(windows系統是my.ini,linux系統是my.cnf),在[mysqld]下面加上以下程式碼:

1

2

log-slow-queries=mysql_slow.log

long_query_time=1

  此時我們在mysql中執行以下命令,可以看到slow_query_log是ON狀態,log_file也是我們指定的檔案:

1

2

3

4

5

6

7

8

mysql> show variables like 'slow_query%'

+---------------------+------------------------------+

| Variable_name       | Value                        |

+---------------------+------------------------------+

| slow_query_log      | ON                           |

| slow_query_log_file | mysql_slow.log |

+---------------------+------------------------------+

rows in set (0.00 sec)

  執行以下命令我們可以看到我們設定的慢查詢時間也生效了,此時只要查詢時間大於1s,查詢語句都將存入日誌檔案。

1

2

3

4

5

6

7

mysql> show variables like 'long_query_time'

+-----------------+----------+

| Variable_name   | Value    |

+-----------------+----------+

| long_query_time | 1.000000 |

+-----------------+----------+

1 row in set (0.00 sec)

  現在我們執行一個查詢時間超過1s的查詢語句:  

1

2

3

4

5

6

7

mysql> select from emp where empno=413345;

+--------+--------+----------+-----+------------+---------+--------+--------+

| empno  | ename  | job      | mgr | hiredate   | sal     | comm   | deptno |

+--------+--------+----------+-----+------------+---------+--------+--------+

| 413345 | vvOHUB | SALESMAN |   1 | 2014-10-26 | 2000.00 | 400.00 |     11 |

+--------+--------+----------+-----+------------+---------+--------+--------+

1 row in set (6.55 sec)

  然後檢視mysql安裝目錄下的data目錄,該目錄會產生一個慢查詢日誌檔案:mysql_slow.log,該檔案內容如下:

1

2

3

4

5

6

7

8

9

/usr/local/mysql/bin/mysqld, Version: 5.1.73-log (MySQL Community Server (GPL)). started with:

Tcp port: 3306  Unix socket: /tmp/mysql.sock

Time                 Id Command    Argument

Time: 141026 23:24:08

User@Host: root[root] @ localhost []

# Query_time: 6.547536  Lock_time: 0.002936 Rows_sent: 1  Rows_examined: 10000000

use temp;

SET timestamp=1414337048;

select from emp where empno=413345;

  在該日誌檔案中,我們可以知道慢查詢產生的時間,最終產生了幾行結果,測試了幾行結果,以及執行語句是什麼。在這裡我們可以看到,這條語句產生一個結果,但是檢測了1000w行記錄,是一個全表掃描。

二、Explain執行計劃                                                                            

     慢查詢日誌可以幫助我們把所有查詢時間過長的sql語句記錄下來,在優化這些語句之前,我們應該使用explain命令檢視mysql的執行計劃,尋找其中的可優化點。

      explain命令的使用十分簡單,只需要"explain + sql語句"即可,如下命令就是對我們剛剛的慢查詢語句使用explain之後的結果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

mysql> explain select from emp where empno=413345\G;

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: emp

type: ALL

possible_keys: NULL

keyNULL

key_len: NULL

ref: NULL

rows: 10000351

Extra: Using where

1 row in set (0.00 sec)

ERROR:

No query specified

  可以看到,explain命令的結果一共有以下幾列:id, select_type, table, type, possible_keys, key, key_len, ref, rows, Extra,這些列分別代表以下意思:

      1、id:SELECT識別符。這是SELECT的查詢序列號;

      2、select_type:查詢型別,主要有PRIMARY(子查詢中最外層查詢)、SUBQUERY(子查詢內層第一個SELECT)、UNION(UNION語句中第二個SELECT開始後面所有SELECT)、SIMPLE(除了子查詢或者union之外的其他查詢);

      3、table:所訪問的資料庫表明;

      4、type:對錶的訪問方式,包括以下型別all(全表掃描),index(全索引掃描),rang(索引範圍掃描),ref(join語句中被驅動表索引引用查詢),eq_ref(通過主鍵或唯一索引訪問,最多隻會有一條結果),const(讀常量,只需讀一次),system(系統表。表中只有一條資料),null(速度最快)。

      5、possible_keys:查詢可能使用到的索引;

      6、key:最後選用的索引;

      7、key_len:使用索引的最大長度;

      8、ref:列出某個表的某個欄位過濾;

      9、rows:估算出的結果行數;

      10、extra:查詢細節資訊,可能是以下值:distinct、using filesort(order by操作)、using index(所查資料只需要在index中即可獲取)、using temporary(使用臨時表)、using where(如果包含where,且不是僅通過索引即可獲取內容,就會包含此資訊)。

      這樣,通過"explain select * from emp where empno=413345\G"命令的輸出,我們就可以清楚的看到,這條查詢語句是一個全表掃描語句,查詢時沒有用到任何索引,所以它的查詢時間肯定會很慢。

三、Profiling 的使用                                                                      

      mysql除了提供explain命令用於檢視命令執行計劃外,還提供了profiling工具用於檢視語句查詢過程中的資源消耗情況。首先我們要使用以下命令開啟Profiling功能:

1

set profiling = 1;

  接下來我們執行一條查詢命令:

1

2

3

4

5

6

7

mysql> select from emp where empno=413345;

+--------+--------+----------+-----+------------+---------+--------+--------+

| empno  | ename  | job      | mgr | hiredate   | sal     | comm   | deptno |

+--------+--------+----------+-----+------------+---------+--------+--------+

| 413345 | vvOHUB | SALESMAN |   1 | 2014-10-26 | 2000.00 | 400.00 |     11 |

+--------+--------+----------+-----+------------+---------+--------+--------+

1 row in set (6.44 sec)

  在開啟了Query Profiler功能之後,MySQL就會自動記錄所有執行的Query的profile資訊了。 然後我們通過以下命令獲取系統中儲存的所有 Query 的 profile 概要資訊:

1

2

3

4

5

6

7

8

9

10

mysql> show profiles;

+----------+------------+--------------------------------------+

| Query_ID | Duration   | Query                                |

+----------+------------+--------------------------------------+

|        1 | 0.00053000 | show tables                          |

|        2 | 0.07412700 | select from dept                   |

|        3 | 0.06743300 | select from salgrade               |

|        4 | 6.44056000 | select from emp where empno=413345 |

+----------+------------+--------------------------------------+

rows in set (0.00 sec)

  然後我們可以通過以下命令檢視具體的某一次查詢的profile資訊:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

mysql> show profile cpu, block io for query 4;

+--------------------+----------+----------+------------+--------------+---------------+

| Status             | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |

+--------------------+----------+----------+------------+--------------+---------------+

| starting           | 0.000107 | 0.000072 |   0.000025 |            0 |             0 |

| Opening tables     | 0.000021 | 0.000018 |   0.000003 |            0 |             0 |

| System lock        | 0.000006 | 0.000004 |   0.000001 |            0 |             0 |

Table lock         | 0.000009 | 0.000008 |   0.000001 |            0 |             0 |

| init               | 0.000034 | 0.000033 |   0.000002 |            0 |             0 |

| optimizing         | 0.000012 | 0.000011 |   0.000001 |            0 |             0 |

statistics         | 0.000014 | 0.000012 |   0.000001 |            0 |             0 |

| preparing          | 0.000013 | 0.000012 |   0.000002 |            0 |             0 |

| executing          | 0.000005 | 0.000005 |   0.000016 |            0 |             0 |

| Sending data       | 6.440260 | 7.818553 |   0.178155 |            0 |             0 |

end                | 0.000008 | 0.000006 |   0.000011 |            0 |             0 |

| query end          | 0.000002 | 0.000002 |   0.000003 |            0 |             0 |

| freeing items      | 0.000030 | 0.000013 |   0.000017 |            0 |             0 |

| logging slow query | 0.000001 | 0.000000 |   0.000001 |            0 |             0 |

| logging slow query | 0.000035 | 0.000020 |   0.000015 |            0 |             0 |

| cleaning up        | 0.000003 | 0.000003 |   0.000000 |            0 |             0 |

+--------------------+----------+----------+------------+--------------+---------------+

16 rows in set (0.00 sec)

  該profile顯示了每一步操作的耗時以及cpu和Block IO的消耗,這樣我們就可以更有針對性的優化查詢語句了。可以看到,由於這是一次全表掃描,這裡耗時最大是在sending data上。除了這種情況,以下幾種情況也可能耗費大量時間:converting HEAP to MyISAM(查詢結果太大時,把結果放在磁碟)、create tmp table(建立臨時表,如group時儲存中間結果)、Copying to tmp table on disk(把記憶體臨時表複製到磁碟)、locked(被其他查詢鎖住) 、logging slow query(記錄慢查詢)。