沒內鬼,來點乾貨!SQL優化和診斷

SQL優化與診斷


Explain診斷

Explain各參數的含義以下:html

列名 說明
id 執行編號,標識select所屬的行。若是在語句中沒有子查詢或關聯查詢,只有惟一的select,每行都將顯示1.不然,內層的select語句通常會順序編號,對應於其在原始語句中的位置
select_type 顯示本行是簡單或複雜select,若是查詢有任何複雜的子查詢,則最外層標記爲PRIMARY(DERIVED、UNION、UNION RESUIT)
table 訪問引用哪一個表(引用某個查詢,如「derived3」)
type 數據訪問/讀取操做類型(All、index、range、ref、eq_ref、const/system、NULL)
possible_key 揭示哪一些索引可能有利於高效的查找
key 顯示mysql實際決定採用哪一個索引來優化查詢
key_len 顯示mysql在索引裏使用的字節數
ref 顯示了以前的表在key列記錄的索引中查找值所用的列或常量
rows 爲了找到所須要的行而須要讀取的行數,估算值
Extra 額外信息,如using index、filesort等

select_type 常見類型及其含義

  • SIMPLE:不包含子查詢或者 UNION 操做的查詢
  • PRIMARY:查詢中若是包含任何子查詢,那麼最外層的查詢則被標記爲 PRIMARY
  • SUBQUERY:子查詢中第一個 SELECT
  • DEPENDENT SUBQUERY:子查詢中的第一個 SELECT,取決於外部查詢
  • UNION:UNION 操做的第二個或者以後的查詢
  • DEPENDENT UNION:UNION 操做的第二個或者以後的查詢,取決於外部查詢
  • UNION RESULT:UNION 產生的結果集
  • DERIVED:出如今 FROM 字句中的子查詢

type常見類型及其含義

  • system:這是 const 類型的一個特例,只會出如今待查詢的表只有一行數據的狀況下
  • consts:常出如今主鍵或惟一索引與常量值進行比較的場景下,此時查詢性能是最優的
  • eq_ref:當鏈接使用的是完整的索引而且是 PRIMARY KEY 或 UNIQUE NOT NULL INDEX 時使用它
  • ref:當鏈接使用的是前綴索引或鏈接條件不是 PRIMARY KEY 或 UNIQUE INDEX 時則使用它
  • ref_or_null:相似於 ref 類型的查詢,可是附加了對 NULL 值列的查詢
  • index_merge:該聯接類型表示使用了索引進行合併優化
  • range:使用索引進行範圍掃描,常見於 between、> 、< 這樣的查詢條件
  • index:索引鏈接類型與 ALL 相同,只是掃描的是索引樹,一般出如今索引是該查詢的覆蓋索引的狀況
  • ALL:全表掃描,效率最差的查找方式

阿里編碼規範要求:至少要達到 range 級別,要求是 ref 級別,若是能夠是 consts 最好mysql

key列

實際在查詢中是否使用到索引的標誌字段面試

如何查看Mysql優化器優化以後的SQL

# 僅在服務器環境下或經過Navicat進入命令列界面
explain extended  SELECT * FROM `student` where `name` = 1 and `age` = 1;

# 再執行
show warnings;

# 結果以下:
/* select#1 */ select `mytest`.`student`.`age` AS `age`,`mytest`.`student`.`name` AS `name`,`mytest`.`student`.`year` AS `year` from `mytest`.`student` where ((`mytest`.`student`.`age` = 1) and (`mytest`.`student`.`name` = 1))
複製代碼

爲何要作這個事呢?咱們知道Mysql有一個最左匹配原則,那麼若是個人索引建的是age,name,那我以name,age這樣的順序去查詢可否使用到索引呢?其實是能夠的,就是由於Mysql查詢優化器能夠幫助咱們自動對SQL的執行順序等進行優化,以選取代價最低的方式進行查詢(注意是代價最低,不是時間最短)sql

SQL優化

超大分頁場景解決方案

如表中數據須要進行深度分頁,如何提升效率?在阿里出品的Java編程規範中寫道:數據庫

利用延遲關聯或者子查詢優化超多分頁場景編程

說明:MySQL 並非跳過 offset 行,而是取 offset+N 行,而後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就很是的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫性能優化

# 反例(耗時129.570s)
select * from task_result LIMIT 20000000, 10;

# 正例(耗時5.114s)
SELECT a.* FROM task_result a, (select id from task_result LIMIT 20000000, 10) b where a.id = b.id;

# 說明
task_result表爲生產環境的一個表,總數據量爲3400萬,id爲主鍵,偏移量達到2000萬
複製代碼

獲取一條數據時的Limit 1

若是數據表的狀況已知,某個業務須要獲取符合某個Where條件下的一條數據,注意使用Limit服務器

說明:在不少狀況下咱們已知數據僅存在一條,此時咱們應該告知數據庫只用查一條,不然將會轉化爲全表掃描mysql優化

# 反例(耗時2424.612s)
select * from task_result where unique_key = 'ebbf420b65d95573db7669f21fa3be3e_861414030800727_48';

# 正例(耗時1.036s)
select * from task_result where unique_key = 'ebbf420b65d95573db7669f21fa3be3e_861414030800727_48' LIMIT 1;

# 說明
task_result表爲生產環境的一個表,總數據量爲3400萬,where條件非索引字段,數據所在行爲第19486條記錄
複製代碼

批量插入

# 反例
INSERT into person(name,age) values('A',24)
INSERT into person(name,age) values('B',24)
INSERT into person(name,age) values('C',24)

# 正例
INSERT into person(name,age) values('A',24),('B',24),('C',24);

# 說明
比較常規,就很少作說明了
複製代碼

like語句的優化

like語句通常業務要求都是 '%關鍵字%'這種形式,可是依然要思考可否考慮使用右模糊的方式去替代產品的要求,其中阿里的編碼規範提到:函數

頁面搜索嚴禁左模糊或者全模糊,若是須要請走搜索引擎來解決

# 反例(耗時78.843s)
EXPLAIN select * from task_result where taskid LIKE '%tt600e6b601677b5cbfe516a013b8e46%' LIMIT 1;

# 正例(耗時0.986s)
select * from task_result where taskid LIKE 'tt600e6b601677b5cbfe516a013b8e46%' LIMIT 1

##########################################################################
# 對正例的Explain
1	SIMPLE	task_result		range	adapt_id	adapt_id	98		99	100.00	Using index condition

# 對反例的Explain
1	SIMPLE	task_result		ALL					                    33628554	11.11	Using where

# 說明
task_result表爲生產環境的一個表,總數據量爲3400萬,taskid是一個普通索引列,可見%%這種匹配方式徹底沒法使用索引,從而進行全表掃描致使效率極低,而正例經過索引查找數據只須要掃描99條數據便可
複製代碼

避免SQL中對where字段進行函數轉換或表達式計算

# 反例
select * from task_result where id + 1 = 15551;

# 正例
select * from task_result where id = 15550;

##########################################################################
# 對正例的Explain
1	SIMPLE	task_result		const	PRIMARY	PRIMARY	8	const	1	100.00	

# 對反例的Explain
1	SIMPLE	task_result		ALL					                33631512  100.00	Using where

# 說明
其實在知道了有SQL優化器以後,我我的感受這種普通的表達式轉換應該能夠提早進行處理再進行查詢,這樣一來就能夠用到索引了,可是問題又來了,若是mysql優化器能夠提早計算出結果,那麼寫sql語句的人也必定能夠提早計算出結果,因此矛盾點在這個地方,致使5.7版本之前的此種狀況都沒法使用索引吧,將來可能會對其進行優化
複製代碼

使用 ISNULL()來判斷是否爲 NULL 值

說明:NULL 與任何值的直接比較都爲 NULL

# 1) NULL<>NULL 的返回結果是 NULL,而不是 false。 
# 2) NULL=NULL 的返回結果是 NULL,而不是 true。 
# 3) NULL<>1 的返回結果是 NULL,而不是 true。
複製代碼

多表查詢

我所在的公司基本禁止了多表查詢,那若是必須使用到的話,咱們能夠一塊兒參考一下阿里的編碼規範

Eg:超過三個表禁止 join。須要 join 的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段須要有索引

明明有索引爲何還走全表掃描

以前回答一些面試問題的時候,對某一個點的理解出現了誤差,即我認爲只要查詢的列有索引則必定會使用索引去Push數據

然而實際上不只僅是這樣,真正應該是:針對查詢的數據行佔總數據量過多時會轉化成全表查詢

那麼這個過多指代的是多少呢?

個人測試結果是50%,但我的認爲MySQL優化器不會徹底糾結於行數區分是否全表,而是有不少其餘因素綜合考慮發現全表掃描的效率更高等等,因此充分認識到該問題便可

count(*) 仍是 count(id)

阿里的Java編碼規範中有如下內容:

【強制】不要使用 count(列名) 或 count(常量) 來替代 count(*)

count(*) 是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。

說明:count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行

字段類型不一樣致使索引失效

阿里的Java編碼規範中有如下內容:

【推薦】防止因字段類型不一樣形成的隱式轉換,致使索引失效

實際上數據庫在查詢的時候會做一層隱式的轉換,好比 varchar 類型字段經過 數字去查詢

# 正例
EXPLAIN SELECT * FROM `user_coll` where pid = '1';
type:ref
ref:const	
rows:1	
Extra:Using index condition

# 反例
EXPLAIN SELECT * FROM `user_coll` where pid = 1;
type:index
ref:NULL	
rows:3(總記錄數)
Extra:Using where; Using index

# 說明
pid字段有相應索引,且格式爲varchar 
複製代碼

關於

感謝如下博文及其做者:

乾貨!SQL性能優化,書寫高質量SQL語句

乾貨!SQL性能優化,書寫高質量SQL語句(二)

MySQL官方文檔

Tips

自建數據表進行測試

CREATE TABLE `student` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(255) NOT NULL,
  `class` varchar(255) DEFAULT NULL,
  `page` bigint(20) DEFAULT NULL,
  `status` tinyint(3) unsigned NOT NULL COMMENT '狀態:0 正常,1 凍結,2 刪除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4
複製代碼

插入數據

DELIMITER ;;
    CREATE PROCEDURE insertData()
    BEGIN
        declare i int;
        set i = 1 ;
        WHILE (i < 1000000) DO
            INSERT INTO student(`name`,class,`page`,`status`)
                VALUES(CONCAT('class_', i),
                    CONCAT('class_', i),
                i, (SELECT FLOOR(RAND() * 2)));
            set i = i + 1;
        END WHILE;
        commit;
		END;;
CALL insertData();
複製代碼
相關文章
相關標籤/搜索