MySQL常規優化操作

查詢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]';

image-20211010224453613

在執行查詢計劃後會輸出以上的一條數據,其中:

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;

輸出內容如下:

image-20211011201453904

由圖片我們可知,上面執行的sql語句,耗時約1.045,如果還想查看每一步執行的耗時,可以使用下面語句:

SHOW PROFILE FOR QUERY 930;

image-20211011201535347

其中 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;

image-20211012001458882

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`;

image-20211013223449789

  • 如果查詢的字段有索引,排序時會直接使用索引,不需要進行文件排序,效率較高。
-- 使用索引排序
EXPLAIN SELECT `id`,`name` FROM `app_user` ORDER BY `name`;

image-20211013223516173

  • 多字段排序,要麼同時使用升序,要麼同時使用降序,多字段避免使用不同的排序。
-- 多字段同排序
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;

image-20211013223552733

-- 多字段不同排序,會使用文件排序
EXPLAIN SELECT `id`,`name`,`phone`,`age` FROM `app_user` ORDER BY `name` DESC,`phone` ASC;

image-20211013223618799

  • filesort排序優化,MySQL在進行文件排序filesort時,會根據變量max_length_for_sort_data來判斷使用什麼樣的排序算法,小於這個變量使用單路排序,大於這個變量使用雙路排序;我們可以在配置文件中適當調大這個值,讓filesort更容易使用單路排序,但需要注意的是這會導致消耗資源增加。
-- 查看使用排序算法的分界值,單位:字節
SHOW VARIABLES LIKE 'max_length_for_sort_data';
-- 查看排序緩存的大小,單位:字節
SHOW VARIABLES LIKE 'sort_buffer_size';

image-20211013225050908

GROUP BY 優化


  • MySQL在進行GROUP BY時,會先進行排序操作,我們可以手動禁止排序,讓TA直接進行分組操作。
-- 未添加緩存,未去除排序,自動使用了文件排序
EXPLAIN SELECT * FROM app_user GROUP BY `age`;

image-20211017194426144

-- 未添加緩存,禁止自動排序操作,耗時更短
EXPLAIN SELECT * FROM app_user GROUP BY `age` ORDER BY NULL;

image-20211017194653670

  • 使用索引字段進行分組和排序,也可避免使用文件排序。
-- 未使用索引字段進行分組
EXPLAIN SELECT `password`,COUNT(`password`) FROM app_user GROUP BY `password`;

image-20211017205520342

-- 使用了索引字段進行分組
EXPLAIN SELECT `name`,COUNT(`name`) FROM app_user GROUP BY `name`;

image-20211017205552628

嵌套查詢優化


儘量的使用多表連接查詢替代子查詢,避免子查詢產生的中間表。

-- 子查詢
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;

image-20211018171039786

-- OR,email單列索引和name複合索引,都有索引,索引生效
EXPLAIN SELECT * FROM app_user WHERE email='00' OR name='用戶';

image-20211018171215684

建議使用UNION替換OR,避免其中有列沒有索引,引起全部索引失效。

-- email有索引,age沒有索引
-- 1層查詢走了索引,2層查詢沒有走索引,避免了全部索引失效
EXPLAIN SELECT * FROM app_user WHERE email='[email protected]' UNION SELECT * FROM app_user WHERE age=10;

image-20211018171729303

分頁查詢優化


在進行分頁時,如果在前幾頁的數據,查詢速度還算可以;

但是越往後面,查詢的數據就會越慢,這是因爲,MySQL會根據條件查詢出符合的數據,最後才進行分頁操作,而前面查詢的記錄都被丟棄了,造成了大量的浪費。

假設查詢了第9萬頁的數據,MySQL會查詢出9萬頁的全部數據,然後丟棄前8.9萬的數據,返回第9萬頁的數據。

-- LIMIT 越往後,速度越慢
EXPLAIN SELECT * FROM app_user LIMIT 9000,10;

image-20211018175426584

針對這種情況我們可以,先在索引上完成分頁,然後再使用連接查詢篩選出數據,全在索引上進行分頁。

-- 把分頁操作移到索引上去操作,
-- 把以前的993537條記錄從數據文件上篩選,修改爲在索引上進行篩選
EXPLAIN SELECT * FROM app_user AS u
INNER JOIN (
	SELECT id FROM app_user LIMIT 9000,10
) t
ON t.id = u.id;

image-20211018175537507

如果在主鍵自增的列上,我們可以把分頁的頁修改爲條件,讓它直接在條件處就丟棄多餘的數據,但是這個情況下,數據順序不能變動,並且數據自增的序號還不能斷層,否則查詢的數據就不正確;如果滿足條件就使用,此方式查詢效率最高。

EXPLAIN SELECT * FROM app_user WHERE id>900000 LIMIT 10;

image-20211018180449991

內存優化


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:事務行鎖等待時間,快速反應的系統調小,大事務的系統調大,此處單位爲秒;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章