查詢SQL語句執行頻率 查詢 mysql 服務啟動時長 SHOW STATUS LIKE 'uptime'; 下列輸出表示服務啟動了276324秒 + + + | Variable_name | Value | + + + | Uptime | 276324 | + + + 查詢全局SQL執行的頻率 ...
查詢SQL語句執行頻率
查詢 mysql 服務啟動時長
SHOW STATUS LIKE 'uptime';
下列輸出表示服務啟動了276324秒
+---------------+--------+
| Variable_name | Value |
+---------------+--------+
| Uptime | 276324 |
+---------------+--------+
查詢全局SQL執行的頻率
-- 執行了多少次select
SHOW GLOBAL STATUS LIKE 'com_select';
-- 執行了多少次insert
SHOW GLOBAL STATUS LIKE 'com_insert';
-- 執行了多少次update
SHOW GLOBAL STATUS LIKE 'com_update';
-- 執行了多少次delete
SHOW GLOBAL STATUS LIKE 'com_delete';
查詢InnoDB引擎的資料庫SQL執行頻率
SHOW GLOBAL STATUS LIKE 'Innodb_rows_%';
輸出
+----------------------+---------+
| Variable_name | Value |
+----------------------+---------+
| Innodb_rows_deleted | 0 |
| Innodb_rows_inserted | 7682 |
| Innodb_rows_read | 4022470 |
| Innodb_rows_updated | 0 |
+----------------------+---------+
定位低效的查詢SQL
慢查詢記錄
查看實時執行進程
mysql> SHOW PROCESSLIST;
在執行時,一條sql正在執行中,就可以看到SQL的執行情況:
如果發現正在執行的Time
時間過長,我們就可以把這條低效的SQL拿來進行優化。
執行計劃EXPLAIN
概述
EXPLAIN是SQL語句執行的分析器,在執行一條SQL語句時,我們可以使用EXPLAIN
命令查看SQL語句的執行計劃,從而知道SQL語句時如何執行的。
在使用EXPLAIN時,我們只需要在執行的SQL語句前面加上EXPLAIN就可以列印執行計劃:
EXPLAIN SELECT * FROM app_user WHERE email = '[email protected]';
在執行查詢計劃後會輸出以上的一條數據,其中:
1)id 序列號
表結構的執行序列號,序號一樣就從上而下執行,序號不同值,值越大優先順序越高,先執行;
一般簡單查詢只會有一條記錄,連接查詢、子查詢會根據查詢的層次出現多條記錄,這時序號就會就會不同。
2)select_type 查詢類型
如果是連接查詢,嵌套查詢的,每層查詢的類型可能不一樣;
select_type值 | 說明 |
---|---|
SIMPLE | 簡單查詢(不使用UNION 或子查詢) |
PRIMARY | 查詢中包含子查詢的,最外層標記為PRIMARY |
UNION | 第二個SELECT出現在UNION之後,則被標記為UNION |
UNION RESULT | 在UNION結果中查詢 |
SUBQUERY | 在SELECT或者WHERE中包含子查詢的查詢 |
DERIVED | 在FROM列表中包含子查詢 |
3)table 引用表
輸出行所引用的表的名稱,可能還會出現以下值:
- <unionM,N>:引用union結果,M,N表示進行union的兩條記錄的ID;
- <derivedN>:引用了ID為N的派生結果,例如,派生表可能來自FROM中的子查詢;
- <subqueryN>:引用了ID為N的子查詢結果。
4)partitions 匹配分區
如果表設置了分區,會顯示數據從哪些分區中查詢,多個分區使用逗號隔開。
5)type 聯結類型
顯示查詢使用了何種類型,按照從最佳到最壞類型排序;
-
NULL:不訪問任何表,比如直接列印
NOW()
函數,就不查詢任何表; -
system:一次就查詢到了,查詢系統表才會出現;
-
const:通過索引一次就查詢到了,通常使用了主鍵索引、唯一索引的記錄查詢時會出現;
-
eq_ref:常見連接查詢,查詢的結果只出現一條記錄;
-
ref:根據非唯一索引進行條件查詢,匹配到查詢條件的所有行;
-
range:根據檢索的條件,搜索到指定範圍的數據,常見為WHRER出現範圍查詢;
-
index:遍歷了索引樹,但是沒有去遍曆數據,速度比遍曆數據快一點;
-
all:遍歷了全表的數據,查詢最慢;
一般保證查詢至少達到range級別,最好能達到ref。
6)possible_keys 可使用索引
表示數據可以從哪些索引中檢索數據。
7)key 選擇索引
MySQL在possible_keys中最終選擇了那一key進行檢索數據。
8)key_len 鍵長度
MySQL決定使用的key的長度。
9)ref 比較列
顯示將哪些列或常量與鍵列中命名的索引進行比較,以從表中選擇行。
10)rows 查詢行數
根據表統計信息以及索引選用情況,大致估算出找到所需的記錄所需要掃描的記錄數。
11)filtered 過濾百分比
按表條件過濾的表行的估計百分比。最大值為100,這意味著沒有發生行過濾,從100開始減小的值表示過濾量增加。
12)Extra 擴展信息
- Using filesort:如果MySQL無法使用索引完成排序而使用了文件排序,需要優化;
- Using temporary:使用了臨時表來來保存了結果,在排序或者分組時沒有使用索引,需要優化;
- Using index condition:從索引中檢索到數據的坐標,需要回表查詢到具體數據。
- Using index:從索引中檢索到數據,一般保持這個,不需要做優化;
- Using where:使用了where條件查詢到的,但是沒有使用索引,建議給查詢條件添加索引;
PROFILES 分析
顯示當前會話過程中執行的語句資源使用信息,一般在調試中使用,比較耗費資源,生產環境不要使用。
我們先使用have_profiling
變數查看mysql是否支持PROFILES,如果輸出yes
,表示支持:
SELECT @@have_profiling;
查看當前回話是否開啟了profiling
,0表示未開啟:
SELECT @@profiling;
當前回話 開啟/關閉profiling
-- 開啟profiling
SET profiling=1;
-- 關閉profiling
SET profiling=0;
我們先開啟profiling,然後執行我們需要分析的SQL語句:
-- 執行SQL
SELECT * FROM `app_user` WHERE `name`='用戶14' OR `phone`='18620769501';
-- 分析
SHOW PROFILES;
輸出內容如下:
由圖片我們可知,上面執行的sql語句,耗時約1.045,如果還想查看每一步執行的耗時,可以使用下麵語句:
SHOW PROFILE FOR QUERY 930;
其中 930
是 SHOW PROFILES 記錄的Query_ID
。
trace分析
查看優化器如何選擇執行計劃的,和PROFILES一樣,trace分析非常消耗資源,不建議在生產環境開啟。
我們先開啟trace分析器,把輸出的內容改成json字元串:
-- 開啟trace分析器
SET optimizer_trace="enabled=on",end_markers_in_json=on;
-- 設置記錄的trace的最大值
SET optimizer_trace_max_mem_size=1000000;
在調試完成後記得關閉trace分析器:
SET optimizer_trace="enabled=off";
我們現在執行一條需要分析的SQL語句,讓trace分析器來進行分析:
SELECT * FROM app_user WHERE id = 2;
分析結果記錄到了 information_schema.optimizer_trace
系統表中,我們查詢一下即可得到分析結果:
SELECT * FROM information_schema.optimizer_trace;
TRACE 中大概分為3塊:
- join_preparation:準備階段,主要包括查詢語句轉換
- join_optimization:優化階段,處理where條件、依賴檢查、索引評估、掃描行數統計,評估執行計劃
- join_execution:執行階段,優化後執行
優化 INSERT 語句
- 在MySQL中,插入多條數據,應該儘量避免使用多個INSERT語句;可以使用多值插入的方式,這種方式大大減少客戶端和資料庫服務連接的次數,比單獨執行多個INSERT語句效率高。
-- 低效的插入
INSERT INTO `login_user`(`loginName`,`loginPwd`) VALUES('1014','12134');
INSERT INTO `login_user`(`loginName`,`loginPwd`) VALUES('1015','12134');
INSERT INTO `login_user`(`loginName`,`loginPwd`) VALUES('1016','12134');
-- 高效的插入
INSERT INTO `login_user`(`loginName`,`loginPwd`)
VALUES('1014','12134'),('1015','12134'),('1016','12134');
- 在事務中進行插入,避免每次插入都進行提交,如果數據量比較大,可進行分段提交。
-- 低效的插入
INSERT INTO `login_user`(`loginName`,`loginPwd`) VALUES('1017','12134'),('1018','12134');
INSERT INTO `user_info`(`userId`,`mobile`) VALUES(1,'13800001017'),(1,'13800001018');
-- 使用手動事務提交,高效的插入
BEGIN;
INSERT INTO `login_user`(`loginName`,`loginPwd`) VALUES('1019','12134'),('1020','12134');
INSERT INTO `user_info`(`userId`,`mobile`) VALUES(1,'13800001019'),(1,'13800001020');
COMMIT;
ORDER BY 優化
- 在排序時儘量避免回表查詢。在根據索引進行排序時,查詢的欄位如果沒有添加索引,會導致排序使用文件排序,索引失效;
-- 索引失效,進行文件排序
EXPLAIN SELECT * FROM `app_user` ORDER BY `name`;
- 如果查詢的欄位有索引,排序時會直接使用索引,不需要進行文件排序,效率較高。
-- 使用索引排序
EXPLAIN SELECT `id`,`name` FROM `app_user` ORDER BY `name`;
- 多欄位排序,要麼同時使用升序,要麼同時使用降序,多欄位避免使用不同的排序。
-- 多欄位同排序
EXPLAIN SELECT `id`,`name`,`phone`,`age` FROM `app_user` ORDER BY `name`,`phone`;
EXPLAIN SELECT `id`,`name`,`phone`,`age` FROM `app_user` ORDER BY `name` DESC,`phone` DESC;
-- 多欄位不同排序,會使用文件排序
EXPLAIN SELECT `id`,`name`,`phone`,`age` FROM `app_user` ORDER BY `name` DESC,`phone` ASC;
- filesort排序優化,MySQL在進行文件排序filesort時,會根據變數
max_length_for_sort_data
來判斷使用什麼樣的排序演算法,小於這個變數使用單路排序,大於這個變數使用雙路排序;我們可以在配置文件中適當調大這個值,讓filesort更容易使用單路排序,但需要註意的是這會導致消耗資源增加。
-- 查看使用排序演算法的分界值,單位:位元組
SHOW VARIABLES LIKE 'max_length_for_sort_data';
-- 查看排序緩存的大小,單位:位元組
SHOW VARIABLES LIKE 'sort_buffer_size';
GROUP BY 優化
- MySQL在進行
GROUP BY
時,會先進行排序操作,我們可以手動禁止排序,讓TA直接進行分組操作。
-- 未添加緩存,未去除排序,自動使用了文件排序
EXPLAIN SELECT * FROM app_user GROUP BY `age`;
-- 未添加緩存,禁止自動排序操作,耗時更短
EXPLAIN SELECT * FROM app_user GROUP BY `age` ORDER BY NULL;
- 使用索引欄位進行分組和排序,也可避免使用文件排序。
-- 未使用索引欄位進行分組
EXPLAIN SELECT `password`,COUNT(`password`) FROM app_user GROUP BY `password`;
-- 使用了索引欄位進行分組
EXPLAIN SELECT `name`,COUNT(`name`) FROM app_user GROUP BY `name`;
嵌套查詢優化
儘量的使用多表連接查詢替代子查詢,避免子查詢產生的中間表。
-- 子查詢
EXPLAIN SELECT * FROM user_info WHERE userId IN(
SELECT id FROM user WHERE dept_id=1
);
-- 內連接查詢替換子查詢
EXPLAIN SELECT i.* FROM user_info AS i
INNER JOIN user AS u ON u.id = i.userId
WHERE u.dept_id=1;
優化OR條件
對於包含了OR的查詢語句,如果要利用索引,則OR之間的每個條件列都必須使用索引(複合索引也必須滿足最左首碼原則);如果沒有索引,則應該考慮添加索引。
-- OR,其中age沒有索引,全部索引失效
EXPLAIN SELECT * FROM app_user WHERE email='00' OR age=10;
-- OR,email單列索引和name複合索引,都有索引,索引生效
EXPLAIN SELECT * FROM app_user WHERE email='00' OR name='用戶';
建議使用UNION替換OR,避免其中有列沒有索引,引起全部索引失效。
-- email有索引,age沒有索引
-- 1層查詢走了索引,2層查詢沒有走索引,避免了全部索引失效
EXPLAIN SELECT * FROM app_user WHERE email='[email protected]' UNION SELECT * FROM app_user WHERE age=10;
分頁查詢優化
在進行分頁時,如果在前幾頁的數據,查詢速度還算可以;
但是越往後面,查詢的數據就會越慢,這是因為,MySQL會根據條件查詢出符合的數據,最後才進行分頁操作,而前面查詢的記錄都被丟棄了,造成了大量的浪費。
假設查詢了第9萬頁的數據,MySQL會查詢出9萬頁的全部數據,然後丟棄前8.9萬的數據,返回第9萬頁的數據。
-- LIMIT 越往後,速度越慢
EXPLAIN SELECT * FROM app_user LIMIT 9000,10;
針對這種情況我們可以,先在索引上完成分頁,然後再使用連接查詢篩選出數據,全在索引上進行分頁。
-- 把分頁操作移到索引上去操作,
-- 把以前的993537條記錄從數據文件上篩選,修改為在索引上進行篩選
EXPLAIN SELECT * FROM app_user AS u
INNER JOIN (
SELECT id FROM app_user LIMIT 9000,10
) t
ON t.id = u.id;
如果在主鍵自增的列上,我們可以把分頁的頁修改為條件,讓它直接在條件處就丟棄多餘的數據,但是這個情況下,數據順序不能變動,並且數據自增的序號還不能斷層,否則查詢的數據就不正確;如果滿足條件就使用,此方式查詢效率最高。
EXPLAIN SELECT * FROM app_user WHERE id>900000 LIMIT 10;
記憶體優化
MyISAM 引擎記憶體使用 key_buffer 緩存索引塊,數據塊則直接讀取磁碟文件,我們可以調整以下參數,讓MyISAM 能緩存更多的索引。
- key_buffer_size:緩存索引區大小,一般設置為MySQL記憶體的四分之一;
- read_buffer_size:每個連接全表掃描緩存;
- read_rnd_buffer_size:每個連接多欄位排序緩存;
InnoDB用記憶體區做緩存池,緩存了索引和數據塊,占用記憶體較大。
- innodb_buffer_pool_size:緩存池的大小,儘量調大;
- innodb_log_buffer_size:用於日誌緩存,調大避免在事務期間去進行io操作;
併發參數
- max_connections:允許鏈接MySQL服務的最大連接數,超過最大限制的將被積壓到請求棧中;
- back_log:積壓請求棧大小(連接數),超過這個值,連接會直接返回錯誤;
- table_open_cache:緩存表的數量,一般為 max_connections 乘以關聯查詢表數;
- thread_cache_size:服務端緩存線程池大小,便於客戶端快速連接;
- innodb_lock_wait_timeout:事務行鎖等待時間,快速反應的系統調小,大事務的系統調大,此處單位為秒;