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


優化sql:思路:

使用explan-》先查詢type類型看看是all還是ref,然后判斷

possible_keys (顯示可能應用在這張表中的索引, 一個或多個。查詢涉及到的字段是若存在索引, 則該索引將被列出, 但不一定被查詢實際使用) ;

如果這個值沒有達到預期的效果(比如說本來預料到可能使用某個索引但是這里沒顯示),就去查看sql語句哪里出問題了,

如果這里顯示了預料的可能使用的索引,之后再去查看key :這一列,看看是否用到了索引

如果沒有索引,再去查看sql語句where條件哪里出問題,

最后再去查看row這一列查詢了幾行

如果幾億的並發量突然沖過來怎么辦:

思路一:扔流量,當前面一個億的用戶點擊搶紅包的時候,不走數據庫,直接在頁面返回失敗提醒(相當於這一億用戶直接扔掉,擋在頁面上,他們一點擊搶紅包,頁面直接返回失敗,不把數據走進服務器處理),后面的用戶在進行異步處理,redis,負載均衡等操作

我們要對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的隨機字符串:

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 i 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值:

delimiter $$
 
create function rand_num( )
returns int(5)
begin
 declare i int default 0;
 set i = floor(10+rand()*500);
return i;
  end $$
 
delimiter ;

然后我們利用剛剛創建的兩個存儲函數創建一個存儲過程,該存儲過程包含一個參數,該參數表示插入數據表emp的數據條數

delimiter $$
 
create procedure insert_emp(in max_num int(10))
begin
declare i 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條數據:


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也是我們指定的文件:

mysql> show variables like 'slow_query%'; 
+---------------------+------------------------------+
| Variable_name       | Value                        |
+---------------------+------------------------------+
| slow_query_log      | ON                           |
| slow_query_log_file | mysql_slow.log |
+---------------------+------------------------------+
2 rows in set (0.00 sec)
  運行以下命令我們可以看到我們設定的慢查詢時間也生效了,此時只要查詢時間大於1s,查詢語句都將存入日志文件。

mysql> show variables like 'long_query_time'; 
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
1 row in set (0.00 sec)
  現在我們運行一個查詢時間超過1s的查詢語句:  

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,該文件內容如下:


/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之后的結果:

mysql> explain select * from emp where empno=413345\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: ALL
possible_keys: NULL
          key: NULL
      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(速度最快)。

訪問類型, 顯示查詢使用了何種類型, 從最好到最差依次是 :

system>const>eq_ref>ref>range>index>ALL

一般來說, 要保證查詢至少達到range級別, 最好能達到ref

system : 表只有一行記錄(等於系統表), 這是const類型的特例, 平時不會出現

const : 表示通過索引1次就找到了, const用於比較primary key或者unique索引。因為只匹配一行數據, 索引很快, 如將主鍵置於where列表中, MySQL就能將該查詢轉換為一個常量
eq_ref : 唯一性索引掃描, 對於每個索引鍵, 表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描

ref : 非唯一性索引掃描, 返回匹配某個單獨值的所有行。本質上也是一種索引訪問, 它返回所有匹配某個單獨值的行, 可能會找到多個符合條件的行, 所以這個應該屬於查找和掃描的混合體

range : 只檢索給定范圍的行, 使用一個索引來選擇行。key列顯示使用了哪個索引, 一般就是在where語句中出現了between, < ,> ,in等的查詢。這種范圍索引掃描比全表掃描要好, 因為它只需要開始於索引的某一點, 而結束於另一點, 不用掃描全部索引。

ndex : Full Index Scan, index與ALL的區別為index類型只遍歷索引樹。這通常比ALL快, 因為索引文件通常比數據文件小。(也就是說雖然ALL和Index都是讀全表, 但index是從索引中讀取的, 而all是從硬盤中讀取的)

      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;

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

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 概要信息:

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 |
+----------+------------+--------------------------------------+
4 rows in set (0.00 sec)
  然后我們可以通過以下命令查看具體的某一次查詢的profile信息:

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(記錄慢查詢)。

  


免責聲明!

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



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