回到正題,關於此次的 MySQL 性能優化的知識點,我會分紅兩篇幅的文章來輸出,關於 SQL 語句的性能優化我會以單獨的篇幅來進行編寫,語句優化在實操中是屬於性能優化的最高級別的優化點,但願你們也能好好消化。mysql
這個 MySQL 專題是我從年前就一直在準備的,恰好過年在家也沒事就一直在思考着要怎麼去發表這部分的文章,讓你們可以看的時候思路比較清晰,記憶可以更加深入,最後我是經過先發布腦圖,而後再根據腦圖的方向進行專題知識點的發表,以後應該也會是這種形式,畢竟,這樣我寫文章思路清晰,你們看文章的時候思路也清晰嘛,複習知識點的時候也能夠根據腦圖來。程序員
由於開啓慢查詢日誌是有代價的(跟 binlog、optimizer-trace 同樣),因此在 MySQL 中,它默認是關閉的:
show variables like 'slow_query%';
複製代碼
除了這個開關,還有一個參數,控制執行超過多長時間的 SQL 才記錄到慢日誌,默認是 10 秒。
show variables like '%slow_query%';
複製代碼
能夠直接動態修改參數(重啓後失效)。
set @@global.slow_query_log=1; -- 1 開啓,0 關閉,重啓後失效
set @@global.long_query_time=3; -- mysql 默認的慢查詢時間是 10 秒,另開一個窗口後纔會查到最新值
show variables like '%long_query%';
show variables like '%slow_query%';
複製代碼
或者修改配置文件 my.cnf。
如下配置定義了慢查詢日誌的開關、慢查詢的時間、日誌文件的存放路徑。
slow_query_log = ON
long_query_time=2
slow_query_log_file =/var/lib/mysql/localhost-slow.log
複製代碼
模擬慢查詢:
select sleep(10);
複製代碼
查詢 user_innodb 表的 500 萬數據(檢查是否是沒有索引)。
SELECT * FROM `user_innodb` where phone = '136';
複製代碼
慢日誌分析
日誌內容
show global status like 'slow_queries'; -- 查看有多少慢查詢 show variables like '%slow_query%'; -- 獲取慢日誌目錄
cat /var/lib/mysql/ localhost-slow.log
複製代碼
DROP TABLE IF EXISTS course;
CREATE TABLE `course` (
`cid` int(3) DEFAULT NULL,
`cname` varchar(20) DEFAULT NULL,
`tid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher;
CREATE TABLE `teacher` (
`tid` int(3) DEFAULT NULL,
`tname` varchar(20) DEFAULT NULL,
`tcid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher_contact;
CREATE TABLE `teacher_contact` (
`tcid` int(3) DEFAULT NULL,
`phone` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `course` VALUES ('1', 'mysql', '1');
INSERT INTO `course` VALUES ('2', 'jvm', '1');
INSERT INTO `course` VALUES ('3', 'juc', '2');
INSERT INTO `course` VALUES ('4', 'spring', '3');
INSERT INTO `teacher` VALUES ('1', 'jerry', '1');
INSERT INTO `teacher` VALUES ('2', 'jack', '2');
INSERT INTO `teacher` VALUES ('3', 'mic', '3');
INSERT INTO `teacher_contact` VALUES ('1', '13688888888');
INSERT INTO `teacher_contact` VALUES ('2', '18166669999');
INSERT INTO `teacher_contact` VALUES ('3', '17722225555');
複製代碼
explain 的結果有不少的字段,咱們詳細地分析一下。
先確認一下環境:
select version();
show variables like '%engine%';
複製代碼
id
id 是查詢序列編號。
id 值不一樣
id 值不一樣的時候,先查詢 id 值大的(先大後小)。
--查詢 mysql 課程的老師手機號
EXPLAIN SELECT tc.phone FROM teacher_contact tc WHERE tcid = (
SELECT tcid FROM teacher t WHERE t.tid = ( SELECT c.tid FROM course c WHERE c.cname = 'mysql')
);
複製代碼
簡單查詢,不包含子查詢,不包含關聯查詢 union。 EXPLAIN SELECT * FROM teacher;
再看一個包含子查詢的案例: --查詢 mysql 課程的老師手機號
EXPLAIN SELECT tc.phone FROM teacher_contact tc WHERE tcid = (SELECT tcid FROM teacher t WHERE t.tid = ( SELECT c.tid FROM course c WHERE c.cname = 'mysql'));
PRIMARY
子查詢 SQL 語句中的主查詢,也就是最外面的那層查詢。
SUBQUERY
子查詢中全部的內層查詢都是 SUBQUERY 類型的。
DERIVED
衍生查詢,表示在獲得最終查詢結果以前會用到臨時表。例如: --查詢 ID 爲 1 或 2 的老師教授的課程 EXPLAIN SELECT cr.cname FROM (SELECT * FROM course WHERE tid = 1UNIONSELECT * FROM course WHERE tid = 2 ) cr;
DROP TABLE IF EXISTS single_data;
CREATE TABLE single_data(
id int(3) PRIMARY KEY,
content varchar(20)
);
insert into single_data values(1,'a');
EXPLAIN SELECT * FROM single_data a where id = 1;
複製代碼
system
system 是 const 的一種特例,只有一行知足條件。例如:只有一條數據的系統表。 EXPLAIN SELECT * FROM mysql.proxies_priv;
DELETE FROM teacher where tid in (4,5,6);
commit;
複製代碼
--備份
INSERT INTO `teacher` VALUES (4, 'james', 4);
INSERT INTO `teacher` VALUES (5, 'tom', 5);
INSERT INTO `teacher` VALUES (6, 'seven', 6);
commit;
複製代碼
爲 teacher_contact 表的 tcid(第一個字段)建立主鍵索引。 --ALTER TABLE teacher_contact DROP PRIMARY KEY; ALTER TABLE teacher_contact ADD PRIMARY KEY(tcid); 爲teacher 表的 tcid(第三個字段)建立普通索引。 --ALTER TABLE teacher DROP INDEX idx_tcid; ALTER TABLE teacher ADD INDEX idx_tcid (tcid); 執行如下 SQL 語句: select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;
此時的執行計劃(teacher_contact 表是 eq_ref):
小結:
以上三種 system,const,eq_ref,都是可遇而不可求的,基本上很難優化到這個狀態。
ref
查詢用到了非惟一性索引,或者關聯操做只使用了索引的最左前綴。 例如:使用 tcid 上的普通索引查詢: explain SELECT * FROM teacher where tcid = 3;
range
索引範圍掃描。 若是 where 後面是 between and 或 <或 > 或 >= 或 <=或 in 這些,type 類型就爲 range。 不走索引必定是全表掃描(ALL),因此先加上普通索引。 --ALTER TABLE teacher DROP INDEX idx_tid; ALTER TABLE teacher ADD INDEX idx_tid (tid); 執行範圍查詢(字段上有普通索引): EXPLAIN SELECT * FROM teacher t WHERE t.tid <3; --或 EXPLAIN SELECT * FROM teacher t WHERE tid BETWEEN 1 AND 2;
IN 查詢也是 range(字段有主鍵索引)
EXPLAIN SELECT * FROM teacher_contact t WHERE tcid in (1,2,3);
index
Full Index Scan,查詢所有索引中的數據(比不走索引要快)。 EXPLAIN SELECT tid FROM teacher;
all
Full Table Scan,若是沒有索引或者沒有用到索引,type 就是 ALL。表明全表掃描。
NULL
不用訪問表或者索引就能獲得結果,例如: EXPLAIN select 1 from dual where 1=1;