前面講解了不少mysql的基礎知識,這一章講解mysql的語句優化。mysql
1、定位慢查詢 linux
咱們要對sql語句進行優化,第一步確定是找到執行速度較慢的語句,那麼怎麼在一個項目裏面定位這些執行速度較慢的sql語句呢?下面就介紹一種定位慢查詢的方法。 sql
1.一、數據庫準備數據庫
首先建立一個數據庫表:windows
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的隨機字符串:socket
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.二、查看慢查詢優化
咱們能夠用如下命令查看慢查詢次數:
show status like 'slow_queries';
如今在mysql中敲入該命令,能夠看到value爲1,這個慢查詢就是由剛剛批量插入1000w條數據產生。
使用該命令只能查看慢查詢次數,可是咱們沒有辦法知道是哪些查詢產生了慢查詢,若是想要知道是哪些查詢致使的慢查詢,那麼咱們必須修改mysql的配置文件。打開mysql的配置文件(windows系統是my.ini,linux系統是my.cnf),在[mysqld]下面加上如下代碼:
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行記錄,是一個全表掃描。
2、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,這些列分別表明如下意思:
一、id:SELECT識別符。這是SELECT的查詢序列號;
二、select_type:查詢類型,主要有PRIMARY(子查詢中最外層查詢)、SUBQUERY(子查詢內層第一個SELECT)、UNION(UNION語句中第二個SELECT開始後面全部SELECT)、SIMPLE(除了子查詢或者union以外的其餘查詢);
三、table:所訪問的數據庫代表;
四、type:對錶的訪問方式,包括如下類型all(全表掃描),index(全索引掃描),rang(索引範圍掃描),ref(join語句中被驅動表索引引用查詢),eq_ref(經過主鍵或惟一索引訪問,最多隻會有一條結果),const(讀常量,只需讀一次),system(系統表。表中只有一條數據),null(速度最快)。
五、possible_keys:查詢可能使用到的索引;
六、key:最後選用的索引;
七、key_len:使用索引的最大長度;
八、ref:列出某個表的某個字段過濾;
九、rows:估算出的結果行數;
十、extra:查詢細節信息,多是如下值:distinct、using filesort(order by操做)、using index(所查數據只須要在index中便可獲取)、using temporary(使用臨時表)、using where(若是包含where,且不是僅經過索引便可獲取內容,就會包含此信息)。
這樣,經過"explain select * from emp where empno=413345\G"命令的輸出,咱們就能夠清楚的看到,這條查詢語句是一個全表掃描語句,查詢時沒有用到任何索引,因此它的查詢時間確定會很慢。
3、Profiling 的使用
mysql除了提供explain命令用於查看命令執行計劃外,還提供了profiling工具用於查看語句查詢過程當中的資源消耗狀況。首先咱們要使用如下命令開啓Profiling功能:
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(記錄慢查詢)。