大綱
1.Explain概述
2.Explain詳解
3.索引最佳化資料準備
4.索引最佳化原則詳解
5.慢查詢設定與測試
6.慢查詢SQL最佳化思路
1.Explain概述
使用Explain關鍵字可以模擬查詢最佳化器來執行SQL查詢語句,從而知道MySQL是如何處理SQL語句的,從而分析出查詢語句和表結構的效能瓶頸。
MySQL查詢過程:
透過Explain可以獲得以下資訊:
一.表的讀取順序
二.資料讀取操作的操作型別
三.哪些索引可以被使用
四.哪些索引真正被使用
五.表的直接引用
六.每張表的有多少行被最佳化器查詢了
Explain使用方式:Explain + SQL語句,透過執行Explain可以獲得SQL語句執行的相關資訊。
EXPLAIN SELECT * FROM L1;
2.Explain詳解
(1)資料準備
(2)ID欄位說明
(3)select_type和table欄位說明
(4)type欄位說明
(5)possible_keys與key說明
(6)key_len欄位說明
(7)ref欄位說明
(8)rows欄位說明
(9)filtered欄位說明
(10)extra欄位說明
(1)資料準備
-- 建立資料庫
CREATE DATABASE test CHARACTER SET 'utf8';
-- 建立表
CREATE TABLE L1(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L2(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L3(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L4(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
-- 每張表插入3條資料
INSERT INTO L1(title) VALUES('test001'),('test002'),('test003');
INSERT INTO L2(title) VALUES('test004'),('test005'),('test006');
INSERT INTO L3(title) VALUES('test007'),('test008'),('test009');
INSERT INTO L4(title) VALUES('test010'),('test011'),('test012');
(2)ID欄位說明
ID欄位代表SELECT查詢的序列號,它是一組數字,表示的是查詢或操作表的順序。
一.ID相同,執行順序就是由上至下
EXPLAIN SELECT * FROM L1,L2,L3 WHERE L1.id=L2.id AND L2.id = L3.id;
二.ID不同,如果有子查詢,ID號會遞增
ID值越大優先順序越高,越先被執行。
EXPLAIN SELECT * FROM L2 WHERE id = (
SELECT id FROM L1 WHERE id = (SELECT L3.id FROM L3 WHERE L3.title = 'test007')
);
(3)select_type和table欄位說明
select_type表示查詢型別,主要用於區別普通查詢還是子查詢等,table表示被操作的表。
一.SIMPLE:簡單的SELECT查詢,查詢中不包含子查詢或者UNION
EXPLAIN SELECT * FROM L1 where id = 1;
二.PRIMARY:在有子查詢的情況下,最外層被標記為PRIMARY
三.SUBQUERY:在SELECT或WHERE列表中包含了子查詢
EXPLAIN SELECT * FROM L2 WHERE id = (
SELECT id FROM L1 WHERE id = (SELECT L3.id FROM L3 WHERE L3.title = 'test08')
);
四.UNION:UNION連線的兩個SELECT查詢
在使用UNION時,左邊的表的select_type是DERIVED,右邊的表的select_type是UNION。
五.DERIVED:在FROM列表中包含的子查詢被標記為DERIVED派生表
MySQL會遞迴執行這些被標記為DERIVED的子查詢,然後把結果放到臨時表中。
六.UNION RESULT:UNION的結果
EXPLAIN SELECT * FROM (SELECT * FROM L3 UNION SELECT * FROM L4) a;
(4)type欄位說明
type欄位表示的是連線型別,描述了找到所需資料而使用的掃描方式。
下面給出各種連線型別,按照從最好型別到最差型別進行排序:system -> const -> eq_ref -> ref -> fulltext -> ref_or_null -> index_merge -> unique_subquery -> index_subquery -> range -> index -> ALL
簡化後,可以只關注以下幾種 :system -> const -> eq_ref -> ref -> range -> index -> ALL
一般來說,需要保證查詢至少達到range級別,最好能達到ref級別,否則就要就行SQL的最佳化調整。
下面介紹type欄位不同值表示的含義:
一.system
表示表中僅有一行資料,這是const連線型別的一個特例,很少出現。
二.const
表示命中主鍵索引(primary key)或唯一索引(unique),透過主鍵索引或唯一索引一次就找到了資料。因為只匹配一條記錄,所以被連線的部分是一個常量。如果將主鍵放在where條件中,MySQL就能將該查詢轉換為一個常量。這種型別非常快,例如以下查詢:
EXPLAIN SELECT * FROM L1 WHERE id = 3;
-- 為L1表的title欄位新增唯一索引
ALTER TABLE L1 ADD UNIQUE(title);
EXPLAIN SELECT * FROM L1 WHERE title = 'test001';
三.eq_ref
表示的是使用了唯一索引。比如連表查詢中,對於前一個表中的每一行,後表只有一行被掃描。除了system和const型別之外,這是最好的連線型別。只有在連表時使用的索引都是主鍵或唯一索引時,才會出現這種型別,例如以下查詢:
EXPLAIN SELECT L1.id,L1.title FROM L1 LEFT JOIN L2 ON L1.id = L2.id;
四.ref
表示使用了普通索引,即非唯一性索引。比如連表時對於前表的每一行,後表可能有多於一行的資料被掃描,例如以下查詢:
-- 為L1表的title欄位新增普通索引
ALTER TABLE L1 ADD INDEX idx_title (title);
EXPLAIN SELECT * FROM L1 INNER JOIN L2 ON L1.title = L2.title;
-- 如果L1表的title欄位沒有唯一索引,只有普通索引,如下查詢也是ref
EXPLAIN SELECT * FROM L1 WHERE title = 'test001';
五.range
表示的是進行了索引上的範圍查詢,檢索了給定範圍的行,比如between、in函式、>都是典型的範圍查詢,例如以下查詢:
EXPLAIN SELECT * FROM L1 WHERE L1.id BETWEEN 1 AND 10;
注意:當in函式中的資料很大時,可能會導致效率下降,最終不走索引。
六.index
當可以使用索引覆蓋,但需要掃描全部索引記錄時,則type為index。
當需要執行全表掃描,且需要對主鍵進行排序時,則type也為index。
所以如果type的值等於index,那麼就需要進行最佳化了。因為出現index表示沒有透過索引進行過濾,需要掃描索引的全部資料。index會遍歷掃描索引樹,比ALL快一些。如果索引檔案過大,index的速度還是會很慢的。
總結:
當遍歷二級索引不需要回表或者主鍵排序全表掃描時,type就為index。
注意:
使用索引進行排序分組時,可能會出現這種type值為index的情況。比如進行統計操作時,會出現type值為index的情況。
EXPLAIN SELECT * FROM L2 GROUP BY id ORDER BY id;
-- 該count查詢需要透過掃描索引上的全部資料來計數
EXPLAIN SELECT count(*) FROM L2;
七.ALL
表示沒有使用到任何索引,連表查詢時對於前表的每一行,後表都要被全表掃描。
EXPLAIN SELECT * FROM L3 inner join L4 on L3.title = L4.title;
總結各類type型別的特點:
system:不進行磁碟IO,查詢系統表,僅僅返回一條資料。
const:查詢主鍵索引,最多返回1條或0條資料,屬於精確查詢。
eq_ref:查詢唯一性索引,返回資料最多一條,屬於精確查詢。
ref:查詢非唯一性索引,返回匹配的多條資料,屬於精確查詢。
range:查詢索引中給定範圍的行,屬於範圍查詢(>、<、in、between)。
index:使用了索引但掃描全部了,比all快,因索引檔案比資料檔案小。
index:比如遍歷二級索引不需要回表或者主鍵排序全表掃描。
all:不使用任何索引,直接進行全表掃描。
(5)possible_keys與key說明
一.possible_keys
表示可能用於查詢的表上的索引。查詢涉及到的欄位上若存在索引,則該索引將被列出,但不一定被查詢實際使用。
二.key
表示實際使用的索引。若為null,則表示沒有使用到索引或索引失效。查詢中若使用了覆蓋索引,則該索引僅出現在key列表中。
情形一:理論和實際都用到了索引
EXPLAIN SELECT * FROM L1 WHERE id = 1;
情形二:理論上沒有使用索引,但實際上使用了
EXPLAIN SELECT L3.id FROM L3;
情形三:理論和實際上都沒有使用索引
EXPLAIN SELECT * FROM L3 WHERE title = 'test007';
(6)key_len欄位說明
表示索引中使用的位元組數,透過該列可以計算查詢中使用索引的長度。key_len欄位能夠幫我們檢查是否充分利用了索引,ken_len越長越好,說明索引使用的越充分。
一.建立表
CREATE TABLE L5(
a INT PRIMARY KEY,
b INT NOT NULL,
c INT DEFAULT NULL,
d CHAR(10) NOT NULL
);
二.使用EXPLAIN進行測試
-- 下面的查詢只用到了主鍵a的索引
EXPLAIN SELECT * FROM L5 WHERE a > 1 AND b = 1;
觀察key_len的值,用到了主鍵索引,是int型別的,所以key_len是4位元組。
三.為b欄位新增索引,進行測試
ALTER TABLE L5 ADD INDEX idx_b(b);
-- 執行SQL,這次將b欄位也作為條件
EXPLAIN SELECT * FROM L5 WHERE a > 1 AND b = 1;
ken_len還是4。
四.為c、d欄位新增聯合索引,然後進行測試
ALTER TABLE L5 ADD INDEX idx_c_b(c,d);
EXPLAIN SELECT * FROM L5 WHERE c = 1 AND d = 'A';
c欄位是int型別4個位元組,d欄位char(10)代表的是10個字元30個位元組。因為資料庫的字符集是utf8,一個字元3個位元組。d欄位是char(10)代表的是10個字元相當30個位元組。多出的一個位元組用來表示是聯合索引。
下面這個例子雖然使用了聯合索引,但沒充分利用索引,還有最佳化空間。因為可以根據ken_len的長度推測出該聯合索引只使用一部分。
EXPLAIN SELECT * FROM L5 WHERE c = 1;
(7)ref欄位說明
表示的是顯示索引的哪一列被使用了,如果可能的話,最好是一個常數。表示的是哪些列或常量被用於查詢索引列上的值。
如下的"L1.id=1"中,由於1是常量,所以ref = const,此時的ref = const表示著查詢過程中使用到了常量。
EXPLAIN SELECT * FROM L1 WHERE L1.id = 1;
(8)rows欄位說明
表示MySQL為了找到所需的記錄,一共訪問了多少行(預估的)。L3中的title沒有新增索引,所以L3中有3條記錄,就需要訪問3條記錄。
EXPLAIN SELECT * FROM L3,L4 WHERE L3.id = L4.id AND L3.title LIKE 'test007';
需要注意的是rows只是一個估算值,並不準確。所以rows行數過大的問題並不值得過多考慮,主要分析的還是索引是否使用正確了。
(9)filtered欄位說明
它指返回結果的行佔需要讀到的行(rows列的值)的百分比。
(10)extra欄位說明
Extra是EXPLAIN輸出中另外一個很重要的列,該列顯示MySQL在查詢過程中的一些詳細資訊。
一.準備資料
CREATE TABLE users (
uid INT PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(20),
age INT(11)
);
INSERT INTO users VALUES(NULL, 'lisa', 10);
INSERT INTO users VALUES(NULL, 'lisa', 10);
INSERT INTO users VALUES(NULL, 'rose', 11);
INSERT INTO users VALUES(NULL, 'jack', 12);
INSERT INTO users VALUES(NULL, 'sam', 13);
二.Using filesort(需要進行檔案排序)
執行結果Extra為Using filesort,說明得到所需結果集,需要對所有記錄進行檔案排序。表示執行的SQL語句效能極差,需要進行最佳化。
下面就是在一個沒有建立索引的列上進行order by,此時會觸發filesort。最佳化方案是,在order by的列上新增索引,避免每次查詢都全量排序。
EXPLAIN SELECT * FROM users ORDER BY age;
三.Using temporary
表示使用了臨時表來儲存結果集,常見於排序和分組查詢。
EXPLAIN SELECT COUNT(*),uname FROM users GROUP BY uname;
四.Using where
表示使用了全表掃描或者在查詢時使用索引的情況下,還有查詢條件不在索引欄位中需要回表。
注意一:返回所有記錄的SQL,不使用where條件過濾資料,大機率不符合預期,這類SQL往往需要進行最佳化。
注意二:使用了where條件的SQL,並不代表不需要最佳化,往往需要配合explain結果中的type(連線型別)來綜合判斷。例如下面查詢的age未設定索引,所以返回的type為ALL,仍有最佳化空間,可建立索引最佳化查詢。
EXPLAIN SELECT * FROM users WHERE age = 10;
五.Using index
表示直接訪問索引就能獲取所需資料(覆蓋索引),不需要回表。
-- 為uname建立索引
ALTER TABLE users ADD INDEX idx_uname(uname);
EXPLAIN SELECT uid,uname FROM users WHERE uname='lisa';
六.Using join buffer
表示使用了連線快取,還會顯示join連線查詢時使用的演算法。
EXPLAIN SELECT * FROM users u1 LEFT JOIN
(SELECT * FROM users WHERE age = 1) u2 ON u1.age = u2.age;
Using join buffer(Block Nested Loop)說明,需要進行巢狀迴圈計算。這裡每個表都有五條記錄,內外表查詢的type都為ALL。兩個表透過欄位age進行關聯,且age欄位未建立索引。
七.Using index condition
表示的是使用了索引,但是隻使用了索引的一部分。一般發生在使用聯合索引時,需要回表查詢。
EXPLAIN SELECT * FROM L5 WHERE c > 10 AND d = '';
八.Extra主要指標的含義總結
using index:查詢時使用了覆蓋索引的時候就會出現,不需要回表。
using where:查詢時使用索引的情況下需要回表或全表掃描。
using index condition:查詢時使用了索引但只用一部分索引需要回表。
Using filesort:在一個沒有建立索引的列上order by,發生檔案排序。
Using temporary:使用了臨時表儲存結果集,常見於排序和分組查詢。
當遍歷二級索引不需要回表或者主鍵排序全表掃描時,type就為index。
查詢非唯一性索引,返回匹配的多條資料,type就為ref。
查詢唯一性索引,返回匹配的資料最多一條,type就為eq_ref。
查詢索引中給定範圍的行,type就為range。
3.索引最佳化資料準備
(1)建立資料庫、表,插入資料
create database idx_optimize character set 'utf8';
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(20) NOT NULL COMMENT '姓名',
user_age INT NOT NULL DEFAULT 0 COMMENT '年齡',
user_level VARCHAR(20) NOT NULL COMMENT '使用者等級',
reg_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '註冊時間'
);
INSERT INTO users(user_name,user_age,user_level,reg_time)
VALUES('tom',17,'A',NOW()),('jack',18,'B',NOW()),('lucy',18,'C',NOW());
(2)建立聯合索引
ALTER TABLE users ADD INDEX idx_nal (user_name,user_age,user_level) USING BTREE;
4.索引最佳化原則詳解
(1)最左側列匹配和最左字首匹配法則
(2)不要在索引列上做任何計算
(3)範圍之後全失效
(4)避免使用is null、is not null、!= 、or
(5)like以%開頭會使索引失效
(6)索引最佳化原則總結
(1)最左側列匹配和最左字首匹配法則
如果建立的是聯合索引,就要遵循該法則。where後面的條件需從索引的最左側列開始,且不能跳過索引中的列。如果where只匹配一個列,那麼該列在索引最左側,且只匹配字首欄位。
一.最左側列匹配和最左字首匹配的場景
場景1:按照索引欄位順序使用,三個欄位都使用了索引,沒有問題。
EXPLAIN SELECT * FROM users
WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
場景2:直接跳過user_name使用索引欄位,索引無效,未使用到索引。
EXPLAIN SELECT * FROM users WHERE user_age = 17 AND user_level = 'A';
場景3: 不按照建立聯合索引的順序,使用索引。
EXPLAIN SELECT * FROM users WHERE user_age = 17 AND user_name = 'tom' AND user_level = 'A';
where後面查詢條件順序是user_age、user_level、user_name,這與建立的索引順序user_name、user_age、user_level不一致。為什麼還是使用了索引,原因是MySQL底層最佳化器對其進行了最佳化。
場景4:只要包含最左側欄位,索引就可以生效
但從key_len可知只是用到索引的一部分。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom';
二.最左側列匹配和最左字首匹配的原理
InnoDB建立聯合索引的規則是:
首先會對聯合索引最左邊的欄位進行排序,例子中是user_name。在第一個欄位的基礎之上再對第二個欄位進行排序,例子中是user_age。所以最佳左字首原則其實是和B+樹的結構有關係,最左欄位肯定是有序的,第二個欄位則是無序的。
聯合索引的排序方式是:
先按第一個欄位進行排序,如果第一個欄位相等再根據第二個欄位排序。所以如果直接使用第二個欄位user_age通常是使用不到索引的。
(2)不要在索引列上做任何計算
不要在索引列上做任何操作,否則會導致索引失效,從而轉向全表掃描。比如計算、使用函式、自動或手動進行型別轉換(字串不加雙引號)。
一.插入資料
INSERT INTO users(user_name,user_age,user_level,reg_time) VALUES('11223344',22,'D',NOW());
場景1:使用系統函式left()函式,對user_name進行操作
EXPLAIN SELECT * FROM users WHERE LEFT(user_name, 6) = '112233';
場景2:字串不加單引號(隱式型別轉換)
對於varchar型別的欄位,如果查詢時不加單引號就會進行隱式轉換,導致索引失效轉向全表掃描。
EXPLAIN SELECT * FROM users WHERE user_name = 11223344;
(3)範圍之後全失效
where條件中如果有範圍條件,並且範圍條件之後還有其他過濾條件,那麼範圍條件之後的列就都將會索引失效。
場景1:條件單獨使用user_name時,type=ref、key_len=62。
-- 條件只有一個 user_name
EXPLAIN SELECT * FROM users WHERE user_name = 'tom';
場景2:條件增加一個user_age(使用常量等值),type= ref、key_len = 66。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17;
場景3:使用全值匹配,type = ref、key_len = 128,索引都利用上了。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
場景4:使用範圍條件時,avg > 17、type = range、key_len = 66。與場景3比較,可發現user_level索引沒用上。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age > 17 AND user_level = 'A';
(4)避免使用is null、is not null、!= 、or
一.使用is null會使索引失效
EXPLAIN SELECT * FROM users WHERE user_name IS NULL;
Impossible Where:表示where條件不成立,不能返回任何行。
二.使用is not null會使索引失效
EXPLAIN SELECT * FROM users WHERE user_name IS NOT NULL;
三.使用!=和or會使索引失效
EXPLAIN SELECT * FROM users WHERE user_name != 'tom';
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' or user_name = 'jack';
(5)like以%開頭會使索引失效
一.like查詢中%出現在左邊則索引失效,%出現在右邊索引未失效
場景1:兩邊都有%或者%在左邊,索引都會失效
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom%';
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom';
場景2:%在右邊,索引生效
EXPLAIN SELECT * FROM users WHERE user_name LIKE 'tom%';
二.解決%出現在左邊索引失效的方法——使用覆蓋索引
EXPLAIN SELECT user_name FROM users WHERE user_name LIKE '%jack%';
EXPLAIN SELECT user_name,user_age,user_level FROM users WHERE user_name LIKE '%jack%';
對比場景1可以知道:透過使用覆蓋索引type = index,並且extra = Using index,從原來的全表掃描變成了全索引掃描,也就是索引的全表掃描。
三.like失效的原理
原理一:%號在右
由於B+樹的索引順序,是按照首字母的大小進行排序,而%號在右時的匹配又會匹配首字母,所以能在B+樹上進行有序的查詢。也就是查詢出首字母符合要求的資料,所以%號在右可以用到索引。
原理二:%號在左是匹配字串尾部的資料
由於尾部的字母是沒有順序的,所以不能按索引順序查詢,用不到索引。
原理三:兩個%%號
這個是查詢任意位置的字母滿足條件即可。只有首字母是進行索引排序的,其他位置的字母都是相對無序的,所以查詢任意位置的字母是用不上索引的。
(6)索引最佳化原則總結
一.最左側列匹配和最左字首匹配
二.索引列上不計算不轉換
三.範圍之後全失效
四.最好使用覆蓋索引
五.!=、is null、is not null、or會索引失效
六.like百分號加右邊,加左邊導致索引失效的解決方法是使用覆蓋索引
5.慢查詢設定與測試
(1)慢查詢介紹
(2)慢查詢引數
(3)慢查詢配置方式
(4)慢查詢測試
(5)慢日誌內容
(1)慢查詢介紹
MySQL的慢查詢全名是慢查詢日誌,是MySQL提供的一種日誌記錄。慢查詢日誌會記錄在MySQL中響應時間超過閾值的語句。MySQL資料庫預設不啟動慢查詢日誌,需要手動來設定這個引數。
如果不是調優需要的話,一般不建議啟動該引數。因為開啟慢查詢日誌會或多或少帶來一定的效能影響,慢查詢日誌支援將日誌記錄寫入檔案和資料庫表。
(2)慢查詢引數
執行下面的語句
mysql> show variables like '%slow_query_log%';
+---------------------+------------------------------+
| Variable_name | Value |
+---------------------+------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/test-slow.log |
+---------------------+------------------------------+
mysql> show variables like '%long_query%';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
MySQL慢查詢的相關引數解釋:
一.slow_query_log:是否開啟慢查詢日誌。
二.slow-query-log-file:慢查詢日誌儲存路徑。
三.long_query_time:慢查詢閾值,查詢時間多於設定閾值則記錄日誌。
(3)慢查詢配置方式
一.預設情況下slow_query_log的值為OFF,表示慢查詢日誌是禁用的
mysql> show variables like '%slow_query_log%';
+---------------------+------------------------------+
| Variable_name | Value |
+---------------------+------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/test-slow.log |
+---------------------+------------------------------+
二.可以透過設定slow_query_log的值來開啟
mysql> set global slow_query_log=1;
三.set global slow_query_log=1開啟慢查詢日誌當前生效重啟失效
如果要永久生效,就必須修改配置檔案my.cnf,其它系統變數也是如此。
-- 編輯配置
vim /etc/my.cnf
-- 新增如下內容
slow_query_log =1
slow_query_log_file=/var/lib/mysql/ruyuan-slow.log
-- 重啟MySQL
service mysqld restart
mysql> show variables like '%slow_query%';
+---------------------+--------------------------------+
| Variable_name | Value |
+---------------------+--------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/ruyuan-slow.log |
+---------------------+--------------------------------+
四. 開啟了慢查詢日誌後,什麼樣的SQL才會記錄到慢查詢日誌裡
這個由引數long_query_time控制,預設long_query_time的值為10秒。
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
mysql> set global long_query_time=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
五.修改變數long_query_time,但查詢值還是10
執行命令set global long_query_time=1後,需要重新連線或者開啟新開會話才能看到修改值。
mysql> show variables like 'long_query_time';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
六.log_output引數是指定日誌的儲存方式
log_output=FILE表示將日誌存入檔案,預設值是FILE。log_output=TABLE表示將日誌存入資料庫,這樣日誌資訊就會被寫入到mysql.slow_log表中。
MySQL資料庫可以同時支援兩種日誌儲存方式,配置的時候以逗號隔開即可,如:log_output='FILE,TABLE'。
日誌記錄到系統的專用日誌表中,要比記錄到檔案耗費更多的系統資源。因此如果啟用慢查詢日誌+獲得更高系統效能,則建議優先記錄到檔案。
mysql> SHOW VARIABLES LIKE '%log_output%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output | FILE |
+---------------+-------+
七.開啟系統變數讓未使用索引的查詢也被記錄到慢查詢日誌中
這個系統變數就是log-queries-not-using-indexes,所以在進行調優時,可以開啟這個選項。
mysql> show variables like 'log_queries_not_using_indexes';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | OFF |
+-------------------------------+-------+
mysql> set global log_queries_not_using_indexes=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'log_queries_not_using_indexes';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | ON |
+-------------------------------+-------+
(4)慢查詢測試
一.執行test_index.sql指令碼,監控慢查詢日誌內容
[root@localhost mysql]# tail -f /var/lib/mysql/test-slow.log
/usr/sbin/mysqld, Version: 5.7.30-log (MySQL Community Server (GPL)). started with:
Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
二. 執行下面的SQL,執行超時(超過1秒)我們去檢視慢查詢日誌
SELECT * FROM test_index WHERE
hobby = '20009951' OR hobby = '10009931' OR hobby = '30009931'
OR dname = 'name4000' OR dname = 'name6600' ;
(5)慢日誌內容
我們得到慢查詢日誌後,最重要的一步就是去分析這個日誌。先來看慢日誌裡到底記錄了哪些內容,如下是慢日誌裡其中一條記錄,可以看到有時間戳、使用者、查詢時長及具體的SQL等資訊。
Time Id Command Argument
# Time: 2022-02-23 T03:55:15. 336037Z
# User@Host: root[root] @ localhost [] Id: 6
# Query_time: 2.375219 Lock_time: 0.000137 Rows_sent: 3 Rows_examined: 5000000
use db4;
SET timestamp=1645588515;
SELECT * FROM test_index WHERE hobby = '20009961' OR hobby = '10009941' OR hobby = '30009961' OR dname = 'name4001' OR dname = 'name6601';
Time:執行時間;
Users:使用者資訊;
Query_time:查詢時長;
Lock_time:等待鎖時長;
Rows_sent:結果行統計數量;
Rows_examined:掃描的行數;
6.慢查詢SQL最佳化思路
(1)SQL效能下降的原因
(2)慢查詢最佳化思路
(1)SQL效能下降的原因
導致SQL執行效能下降的原因可體現在以下兩方面:
一.等待時間長
鎖表導致查詢一直處於等待狀態。
二.執行時間長
查詢語句沒最佳化、索引失效、關聯查詢太多join、機器及引數沒調優。
(2)慢查詢最佳化思路
一.優先選擇最佳化高併發執行的SQL
因為高併發的SQL出現問題帶來後果更嚴重,比如下面兩種情況:SQL1每小時執行10000次,每次20個IO,最佳化後每次18個IO,每小時節省2萬次IO;SQL2每小時10次,每次20000個IO,每次最佳化減少2000個IO,每小時節省2萬次IO。此時SQL2更難最佳化,SQL1更好最佳化。但是第一種屬於高併發SQL,更急需最佳化,因為成本更低。
二.定位最佳化物件的效能瓶頸
在去最佳化SQL時,選擇最佳化分方向有三個:
方向1:IO,資料訪問消耗了太多時間,檢視是否正確使用索引。
方向2:CPU,資料運算花費了太多時間,資料的運算分組、排序是不是有問題。
方向3:網路頻寬,加大網路頻寬。
三.明確最佳化目標
根據資料庫當前狀態、當前SQL的具體功能,來確定最好情況下消耗的資源和最差情況下消耗的資源。因為最佳化的結果只有一個,即給使用者一個好的體驗。
四.從explain執行計劃入手
只有explain能告訴我們當前SQL的執行狀態。
五.永遠用小的結果集驅動大的結果集
小的資料集驅動大的資料集,減少內層表讀取次數。
//類似於巢狀迴圈
for (int i = 0; i < 5; i++) {
for (int i = 0; i < 1000; i++) {
}
}
六.儘可能在索引中完成排序
排序操作用得比較多,所以order by後面的欄位儘量使用上索引。因為索引本來就是排好序的,所以速度很快。沒有索引的話,就需要從表中拿資料,在記憶體中進行排序。如果記憶體空間不夠還會發生臨時檔案落盤操作。
七.只獲取自己需要的列
不要使用select *,因為select * 很可能不使用索引,而且資料量過大。
八.只使用最有效的過濾條件
where後面的條件並非越多越好,應該用最短的路徑訪問到資料。
九.儘可能避免複雜的join和子查詢
每條SQL的JOIN操作建議不要超過三張表。將複雜的SQL,拆分成多個小的SQL,單個表執行,然後對獲取的結果在程式中進行封裝。因為如果join佔用的資源比較多,會導致其他程序等待時間變長。
十.合理設計並利用索引
也就是合理判斷是否需要建立索引,以及合理選擇合適索引。
(3)如何判定是否需要建立索引
一.頻繁作為查詢條件的欄位應該建立索引
二.唯一性太差的欄位不適合單獨建立索引,即使它頻繁作為查詢條件
唯一性太差的欄位主要是指哪些呢?如狀態欄位、型別欄位等。這些欄位中的資料可能總共就是那麼幾個幾十個數值重複使用。當一條Query所返回的資料超過了全表的15%時,就不應該再使用索引掃描來完成這個Query了。
三.更新非常頻繁的欄位不適合建立索引
因為索引中的欄位被更新時,不僅要更新表的資料,還要更新索引資料。
四.不會出現在WHERE子句中的欄位不該建立索引
(4)如何選擇合適索引
一.單鍵索引,儘量選擇針對當前Query過濾性更好的索引。
二.聯合索引,當前查詢中過濾性最好的欄位在索引欄位順序中排列靠前。