当前位置:网站首页>MySQL27-索引優化與查詢優化
MySQL27-索引優化與查詢優化
2022-07-06 10:28:00 【保護我方阿遙】
一. 數據准備
學員錶 插 50萬 條, 班級錶 插 1萬 條。
1.1. 建錶
CREATE TABLE `class` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`className` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`monitor` INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`classId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
1.2. 設置參數
命令開啟:允許創建函數設置:
set global log_bin_trust_function_creators=1; # 不加global只是當前窗口有效。
1.3. 創建函數
保證每條數據都不同。
#隨機產生字符串
DELIMITER //
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
#假如要删除
#drop function rand_string;
隨機產生班級編號
#用於隨機產生多少到多少的編號
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END //
DELIMITER ;
#假如要删除
#drop function rand_num;
1.4. 創建存儲過程
創建往class錶中插入數據的存儲過程
#執行存儲過程,往class錶添加隨機數據
DELIMITER //
CREATE PROCEDURE `insert_class`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO class ( classname,address,monitor ) VALUES
(rand_string(8),rand_string(10),rand_num(1,100000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
#假如要删除
#drop PROCEDURE insert_class;
1.5. 調用存儲過程
class
#執行存儲過程,往class錶添加1萬條數據
CALL insert_class(10000);
stu
#執行存儲過程,往stu錶添加50萬條數據
CALL insert_stu(100000,500000);
1.6. 删除某錶上的索引
創建存儲過程
DELIMITER //
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM
information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND
seq_in_index=1 AND index_name <>'PRIMARY' ;
#每個遊標必須使用不同的declare continue handler for not found set done=1來控制遊標的結束
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
#若沒有數據返回,程序繼續,並將變量done設為2
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index " , _index , " on " , tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END //
DELIMITER ;
執行存儲過程
CALL proc_drop_index("dbname","tablename");
二. 索引失效案例
2.1. 全值匹配
2.2. 最佳左前綴法則
拓展:Alibaba《Java開發手册》
索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。
2.3. 主鍵插入順序
如果此時再插入一條主鍵值為 9 的記錄,那它插入的比特置就如下圖:
可這個數據頁已經滿了,再插進來咋辦呢?我們需要把當前 頁面分裂 成兩個頁面,把本頁中的一些記錄移動到新創建的這個頁中。頁面分裂和記錄移比特意味著什麼?意味著: 性能損耗 !所以如果我們想盡量避免這樣無謂的性能損耗,最好讓插入的記錄的 主鍵值依次遞增 ,這樣就不會發生這樣的性能損耗了。所以我們建議:讓主鍵具有 AUTO_INCREMENT ,讓存儲引擎自己為錶生成主鍵,而不是我們手動插入 ,比如: person_info 錶:
CREATE TABLE person_info(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
birthday DATE NOT NULL,
phone_number CHAR(11) NOT NULL,
country varchar(100) NOT NULL,
PRIMARY KEY (id),
KEY idx_name_birthday_phone_number (name(10), birthday, phone_number)
);
我們自定義的主鍵列 id 擁有 AUTO_INCREMENT 屬性,在插入記錄時存儲引擎會自動為我們填入自增的主鍵值。這樣的主鍵占用空間小,順序寫入,减少頁分裂。
2.4. 計算、函數、類型轉換(自動或手動)導致索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
創建索引
CREATE INDEX idx_name ON student(NAME);
第一種:索引優化生效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
第二種:索引優化失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
type為“ALL”,錶示沒有使用到索引。
再舉例:
- student錶的字段stuno上設置有索引。
CREATE INDEX idx_sno ON student(stuno);
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;
- 索引優化生效
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000;
再舉例:
3. student錶的字段name上設置有索引
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT id, stuno, name FROM student WHERE SUBSTRING(name, 1,3)='abc';
EXPLAIN SELECT id, stuno, NAME FROM student WHERE NAME LIKE 'abc%';
2.5. 類型轉換導致索引失效
下列哪個sql語句可以用到索引。(假設name字段上設置有索引)
name=123發生類型轉換,索引失效。
未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
# 使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';
2.6. 範圍條件右邊的列索引失效
ALTER TABLE student DROP INDEX idx_name;
ALTER TABLE student DROP INDEX idx_age;
ALTER TABLE student DROP INDEX idx_age_classid;
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
create index idx_age_name_classid on student(age,name,classid);
- 將範圍查詢條件放置語句最後:
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name =
'abc' AND student.classId>20 ;
2.7. 不等於(!= 或者<>)索引失效
2.8. is null可以使用索引,is not null無法使用索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL;
2.9. like以通配符%開頭索引失效
拓展:Alibaba《Java開發手册》
【强制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解决。
2.10. OR 前後存在非索引的列,索引失效
# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;
#使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR name = 'Abel';
2.11. 數據庫和錶的字符集統一使用utf8mb4
統一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,統一字符集可以避免由於字符集轉換產生的亂碼。不同的 字符集 進行比較前需要進行 轉換 會造成索引失效。
三. 關聯查詢優化
3.1. 准備數據
#分類
CREATE TABLE IF NOT EXISTS `type` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
#圖書
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
#向分類錶中添加20條記錄
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO `type`(card) VALUES(FLOOR(1 + (RAND() * 20)));
#向圖書錶中添加20條記錄
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
3.2. 采用左外連接
下面開始 EXPLAIN 分析
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
結論:type 有All
添加索引優化
ALTER TABLE book ADD INDEX Y ( card); #【被驅動錶】,可以避免全錶掃描
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
可以看到第二行的 type 變為了 ref,rows 也變成了優化比較明顯。這是由左連接特性决定的。LEFT JOIN條件用於確定如何從右錶搜索行,左邊一定都有,所以 右邊是我們的關鍵點,一定需要建立索引。
ALTER TABLE `type` ADD INDEX X (card); #【驅動錶】,無法避免全錶掃描
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
接著:
DROP INDEX Y ON book;
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
3.3. 采用內連接
drop index X on type;
drop index Y on book;(如果已經删除了可以不用再執行該操作)
換成 inner join(MySQL自動選擇驅動錶)
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
添加索引優化
ALTER TABLE book ADD INDEX Y ( card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
ALTER TABLE type ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
接著:
DROP INDEX X ON `type`;
EXPLAIN SELECT SQL_NO_CACHE * FROM TYPE INNER JOIN book ON type.card=book.card;
接著:
ALTER TABLE `type` ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card=book.card;
3.4. join語句原理
Index Nested-Loop Join
CREATE TABLE `t2` (
`id` INT(11) NOT NULL,
`a` INT(11) DEFAULT NULL,
`b` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `a` (`a`)
) ENGINE=INNODB;
DELIMITER //
CREATE PROCEDURE idata()
BEGIN
DECLARE i INT;
SET i=1;
WHILE(i<=1000)DO
INSERT INTO t2 VALUES(i, i, i);
SET i=i+1;
END WHILE;
END //
DELIMITER ;
CALL idata();
#創建t1錶並複制t1錶中前100條數據
CREATE TABLE t1
AS
SELECT * FROM t2
WHERE id <= 100;
#測試錶數據
SELECT COUNT(*) FROM t1;
SELECT COUNT(*) FROM t2;
#查看索引
SHOW INDEX FROM t2;
SHOW INDEX FROM t1;
我們來看一下這個語句:
EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON (t1.a=t2.a);
如果直接使用join語句,MySQL優化器可能會選擇錶t1或t2作為驅動錶,這樣會影響我們分析SQL語句的執行過程。所以,為了便於分析執行過程中的性能問題,我改用 straight_join 讓MySQL使用固定的連接方式執行查詢,這樣優化器只會按照我們指定的方式去join。在這個語句裏,t1 是驅動錶,t2是被驅動錶。
可以看到,在這條語句裏,被驅動錶t2的字段a上有索引,join過程用上了這個索引,因此這個語句的執行流程是這樣的:
- 從錶t1中讀入一行數據 R;
- 從數據行R中,取出a字段到錶t2裏去查找;
- 取出錶t2中滿足條件的行,跟R組成一行,作為結果集的一部分;
- 重複執行步驟1到3,直到錶t1的末尾循環結束。
這個過程是先遍曆錶t1,然後根據從錶t1中取出的每行數據中的a值,去錶t2中查找滿足條件的記錄。在形式上,這個過程就跟我們寫程序時的嵌套查詢類似,並且可以用上被驅動錶的索引,所以我們稱之為“Index Nested-Loop Join”,簡稱NLJ。
它對應的流程圖如下所示:
在這個流程裏: - 對驅動錶t1做了全錶掃描,這個過程需要掃描100行;
- 而對於每一行R,根據a字段去錶t2查找,走的是樹搜索過程。由於我們構造的數據都是一一對應的,因此每次的搜索過程都只掃描一行,也是總共掃描100行;
- 所以,整個執行流程,總掃描行數是200。
3.5. 小結
- 保證被驅動錶的JOIN字段已經創建了索引。
- 需要JOIN 的字段,數據類型保持絕對一致。
- LEFT JOIN 時,選擇小錶作為驅動錶, 大錶作為被驅動錶 。减少外層循環的次數。
- INNER JOIN 時,MySQL會自動將 小結果集的錶選為驅動錶 。選擇相信MySQL優化策略。
- 能够直接多錶關聯的盡量直接關聯,不用子查詢。(减少查詢的趟數)。
- 不建議使用子查詢,建議將子查詢SQL拆開結合程序多次查詢,或使用 JOIN 來代替子查詢。
- 衍生錶建不了索引
四. 子查詢優化
MySQL從4.1版本開始支持子查詢,使用子查詢可以進行SELECT語句的嵌套查詢,即一個SELECT查詢的結果作為另一個SELECT語句的條件。 子查詢可以一次性完成很多邏輯上需要多個步驟才能完成的SQL操作 。
子查詢是 MySQL 的一項重要的功能,可以幫助我們通過一個 SQL 語句實現比較複雜的查詢。但是,子查詢的執行效率不高。原因:
① 執行子查詢時,MySQL需要為內層查詢語句的查詢結果 建立一個臨時錶 ,然後外層查詢語句從臨時錶中查詢記錄。查詢完畢後,再 撤銷這些臨時錶 。這樣會消耗過多的CPU和IO資源,產生大量的慢查詢。
② 子查詢的結果集存儲的臨時錶,不論是內存臨時錶還是磁盤臨時錶都 不會存在索引 ,所以查詢性能會受到一定的影響。
③ 對於返回結果集比較大的子查詢,其對查詢性能的影響也就越大。
在MySQL中,可以使用連接(JOIN)查詢來替代子查詢。連接查詢 不需要建立臨時錶 ,其 速度比子查詢要快 ,如果查詢中使用索引的話,性能就會更好。
結論:盡量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代
五. 排序優化
5.1. 排序優化
問題:在 WHERE 條件字段上加索引,但是為什麼在 ORDER BY 字段上還要加索引呢?
優化建議:
- SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中 避免全錶掃描 ,在 ORDER BY 子句 避免使用 FileSort 排序 。當然,某些情况下全錶掃描,或者 FileSort 排序不一定比索引慢。但總的來說,我們還是要避免,以提高查詢效率。
- 盡量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 後面是相同的列就使用單索引列;如果不同就使用聯合索引。
- 無法使用 Index 時,需要對 FileSort 方式進行調優。
INDEX a_b_c(a,b,c)
order by 能使用索引最左前綴
- ORDER BY a
- ORDER BY a,b
- ORDER BY a,b,c
- ORDER BY a DESC,b DESC,c DESC
如果WHERE使用索引的最左前綴定義為常量,則order by 能使用索引
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b > const ORDER BY b,c
不能使用索引進行排序
- ORDER BY a ASC,b DESC,c DESC /* 排序不一致 */
- WHERE g = const ORDER BY b,c /*丟失a索引*/
- WHERE a = const ORDER BY c /*丟失b索引*/
- WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*對於排序來說,多個相等條件也是範圍查詢*/
5.2. 案例實戰
ORDER BY子句,盡量使用Index方式排序,避免使用FileSort方式排序。
執行案例前先清除student上的索引,只留主鍵:
DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid_stuno ON student;
DROP INDEX idx_age_classid_name ON student;
#或者
call proc_drop_index('INDEXTEST','student');
場景:查詢年齡為30歲的,且學生編號小於101000的學生,按用戶名稱排序
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
NAME ;
結論:type 是 ALL,即最壞的情况。Extra 裏還出現了 Using filesort,也是最壞的情况。優化是必須的。
優化思路:
方案一: 為了去掉filesort我們可以把索引建成
#創建新索引
CREATE INDEX idx_age_name ON student(age,NAME);
方案二: 盡量讓where的過濾條件和排序使用上索引
建一個三個字段的組合索引:
DROP INDEX idx_age_name ON student;
CREATE INDEX idx_age_stuno_name ON student (age,stuno,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
NAME ;
結果竟然有 filesort的 sql 運行速度, 超過了已經優化掉 filesort的 sql ,而且快了很多,幾乎一瞬間就出現了結果。
結論:
1. 兩個索引同時存在,mysql自動選擇最優的方案。(對於這個例子,mysql選擇
idx_age_stuno_name)。但是, 隨著數據量的變化,選擇的索引也會隨之變化的 。
2. 當【範圍條件】和【group by 或者 order by】的字段出現二選一時,優先觀察條件字段的過
濾數量,如果過濾的數據足够多,而需要排序的數據並不多時,優先把索引放在範圍字段
上。反之,亦然。
5.3. filesort算法:雙路排序和單路排序
5.3.1. 雙路排序 (慢)
- MySQL 4.1之前是使用雙路排序 ,字面意思就是兩次掃描磁盤,最終得到數據, 讀取行指針和order by列 ,對他們進行排序,然後掃描已經排序好的列錶,按照列錶中的值重新從列錶中讀取對應的數據輸出。
- 從磁盤取排序字段,在buffer進行排序,再從 磁盤取其他字段 。
取一批數據,要對磁盤進行兩次掃描,眾所周知,IO是很耗時的,所以在mysql4.1之後,出現了第二種改進的算法,就是單路排序。
5.3.2. 單路排序 (快)
從磁盤讀取查詢需要的 所有列 ,按照order by列在buffer對它們進行排序,然後掃描排序後的列錶進行輸出, 它的效率更快一些,避免了第二次讀取數據。並且把隨機IO變成了順序IO,但是它會使用更多的空間, 因為它把每一行都保存在內存中了。
5.3.3. 優化策略
- 嘗試提高 sort_buffer_size。
- 嘗試提高 max_length_for_sort_data。
- Order by 時select * 是一個大忌。最好只Query需要的字段。
六. GROUP BY優化
- group by 使用索引的原則幾乎跟order by一致 ,group by 即使沒有過濾條件用到索引,也可以直接使用索引。
- group by 先排序再分組,遵照索引建的最佳左前綴法則。
- 當無法使用索引列,增大 max_length_for_sort_data 和 sort_buffer_size 參數的設置。
- where效率高於having,能寫在where限定的條件就不要寫在having中了。
- 减少使用order by,和業務溝通能不排序就不排序,或將排序放到程序端去做。Order by、groupby、distinct這些語句較為耗費CPU,數據庫的CPU資源是極其寶貴的。
- 包含了order by、group by、distinct這些查詢的語句,where條件過濾出來的結果集請保持在1000行以內,否則SQL會很慢。
七. 優化分頁查詢
優化思路一:
在索引上完成排序分頁操作,最後根據主鍵關聯回原錶查詢所需要的其他列內容。
EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10)
a
WHERE t.id = a.id;
優化思路二:
該方案適用於主鍵自增的錶,可以把Limit 查詢轉換成某個比特置的查詢 。
EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;
八. 優先考慮覆蓋索引
8.1. 什麼是覆蓋索引?
理解方式一:索引是高效找到行的一個方法,但是一般數據庫也能使用索引找到一個列的數據,因此它不必讀取整個行。畢竟索引葉子節點存儲了它們索引的數據;當能通過讀取索引就可以得到想要的數據,那就不需要讀取行了。一個索引包含了滿足查詢結果的數據就叫做覆蓋索引。
理解方式二:非聚簇複合索引的一種形式,它包括在查詢裏的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆蓋查詢條件中所涉及的字段)。
簡單說就是, 索引列+主鍵 包含 SELECT 到 FROM之間查詢的列 。
8.2. 覆蓋索引的利弊
好處:
- 避免Innodb錶進行索引的二次查詢(回錶)。
- 可以把隨機IO變成順序IO加快查詢效率。
弊端:
索引字段的維護 總是有代價的。因此,在建立冗餘索引來支持覆蓋索引時就需要權衡考慮了。這是業務DBA,或者稱為業務數據架構師的工作。
九. 如何給字符串添加索引
有一張教師錶,錶定義如下:
create table teacher(
ID bigint unsigned primary key,
email varchar(64),
...
)engine=innodb;
講師要使用郵箱登錄,所以業務代碼中一定會出現類似於這樣的語句:
select col1, col2 from teacher where email='xxx';
如果email這個字段上沒有索引,那麼這個語句就只能做 全錶掃描 。
9.1. 前綴索引
MySQL是支持前綴索引的。默認地,如果你創建索引的語句不指定前綴長度,那麼索引就會包含整個字符串。
alter table teacher add index index1(email)
#或
alter table teacher add index index2(email(6));
這兩種不同的定義在數據結構和存儲上有什麼區別呢?下圖就是這兩個索引的示意圖。
以及:
如果使用的是index1(即email整個字符串的索引結構),執行順序是這樣的:
- 從index1索引樹找到滿足索引值是’ [email protected] ’的這條記錄,取得ID2的值;
- 到主鍵上查到主鍵值是ID2的行,判斷email的值是正確的,將這行記錄加入結果集;
- 取index1索引樹上剛剛查到的比特置的下一條記錄,發現已經不滿足email=’ [email protected] ’的條件了,循環結束。
這個過程中,只需要回主鍵索引取一次數據,所以系統認為只掃描了一行。
如果使用的是index2(即email(6)索引結構),執行順序是這樣的: - 從index2索引樹找到滿足索引值是’zhangs’的記錄,找到的第一個是ID1;
- 到主鍵上查到主鍵值是ID1的行,判斷出email的值不是’ [email protected] ’,這行記錄丟弃;
- 取index2上剛剛查到的比特置的下一條記錄,發現仍然是’zhangs’,取出ID2,再到ID索引上取整行然後判斷,這次值對了,將這行記錄加入結果集;
- 重複上一步,直到在idxe2上取到的值不是’zhangs’時,循環結束。
也就是說使用前綴索引,定義好長度,就可以做到既節省空間,又不用額外增加太多的查詢成本。前面已經講過區分度,區分度越高越好。因為區分度越高,意味著重複的鍵值越少。
9.2. 前綴索引對覆蓋索引的影響
結論:
使用前綴索引就用不上覆蓋索引對查詢性能的優化了,這也是你在選擇是否使用前綴索引時需要考慮的一個因素。
十. 索引下推
Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一種在存儲引擎層使用索引過濾數據的一種優化方式。ICP可以减少存儲引擎訪問基錶的次數以及MySQL服務器訪問存儲引擎的次數。
10.1. 使用前後的掃描過程
在不使用ICP索引掃描的過程:
storage層:只將滿足index key條件的索引記錄對應的整行記錄取出,返回給server層。
server 層:對返回的數據,使用後面的where條件過濾,直至返回最後一行。
使用ICP掃描的過程:
- storage層:
首先將index key條件滿足的索引記錄區間確定,然後在索引上使用index filter進行過濾。將滿足的indexfilter條件的索引記錄才去回錶取出整行記錄返回server層。不滿足index filter條件的索引記錄丟弃,不回錶、也不會返回server層。 - server 層:
對返回的數據,使用table filter條件做最後的過濾。
使用前後的成本差別
- 使用前,存儲層多返回了需要被index filter過濾掉的整行記錄
- 使用ICP後,直接就去掉了不滿足index filter條件的記錄,省去了他們回錶和傳遞到server層的成本。
- ICP的 加速效果 取决於在存儲引擎內通過 ICP篩選 掉的數據的比例。
10.2. ICP的使用條件
ICP的使用條件:
① 只能用於二級索引(secondary index)
②explain顯示的執行計劃中type值(join 類型)為 range 、 ref 、 eq_ref 或者 ref_or_null 。
③ 並非全部where條件都可以用ICP篩選,如果where條件的字段不在索引列中,還是要讀取整錶的記錄
到server端做where過濾。
④ ICP可以用於MyISAM和InnnoDB存儲引擎
⑤ MySQL 5.6版本的不支持分區錶的ICP功能,5.7版本的開始支持。
⑥ 當SQL使用覆蓋索引時,不支持ICP優化方法。
10.3. ICP使用案例
案例1:
SELECT * FROM tuser
WHERE NAME LIKE '張%'
AND age = 10
AND ismale = 1;
案例2:
十一. 普通索引 vs 唯一索引
從性能的角度考慮,選擇唯一索引還是普通索引呢?選擇的依據是什麼呢?
假設,我們有一個主鍵列為ID的錶,錶中有字段k,並且在k上有索引,假設字段 k 上的值都不重複。
這個錶的建錶語句是:
reate table test(
id int primary key,
k int not null,
name varchar(16),
index (k)
)engine=InnoDB;
11.1. 查詢過程
假設,執行查詢的語句是 select id from test where k=5。
- 對於普通索引來說,查找到滿足條件的第一個記錄(5,500)後,需要查找下一個記錄,直到碰到第一個不滿足k=5條件的記錄。
- 對於唯一索引來說,由於索引定義了唯一性,查找到第一個滿足條件的記錄後,就會停止繼續檢索。
那麼,這個不同帶來的性能差距會有多少呢?答案是, 微乎其微 。
11.2. 更新過程
為了說明普通索引和唯一索引對更新語句性能的影響這個問題,介紹一下change buffer。
當需要更新一個數據頁時,如果數據頁在內存中就直接更新,而如果這個數據頁還沒有在內存中的話,在不影響數據一致性的前提下, InooDB會將這些更新操作緩存在change buffer中 ,這樣就不需要從磁盤中讀入這個數據頁了。在下次查詢需要訪問這個數據頁的時候,將數據頁讀入內存,然後執行change buffer中與這個頁有關的操作。通過這種方式就能保證這個數據邏輯的正確性。
將change buffer中的操作應用到原數據頁,得到最新結果的過程稱為 merge 。除了 訪問這個數據頁 會觸發merge外,系統有 後臺線程會定期 merge。在 數據庫正常關閉(shutdown) 的過程中,也會執行merge操作。
如果能够將更新操作先記錄在change buffer, 减少讀磁盤 ,語句的執行速度會得到明顯的提昇。而且,數據讀入內存是需要占用 buffer pool 的,所以這種方式還能够 避免占用內存 ,提高內存利用率。
唯一索引的更新就不能使用change buffer ,實際上也只有普通索引可以使用。
如果要在這張錶中插入一個新記錄(4,400)的話,InnoDB的處理流程是怎樣的?
11.3. change buffer的使用場景
- 普通索引和唯一索引應該怎麼選擇?其實,這兩類索引在查詢能力上是沒差別的,主要考慮的是對更新性能 的影響。所以,建議盡量選擇普通索引 。
- 在實際使用中會發現, 普通索引 和 change buffer 的配合使用,對於 數據量大 的錶的更新優化還是很明顯的。
- 如果所有的更新後面,都馬上 伴隨著對這個記錄的查詢 ,那麼你應該 關閉change buffer 。而在其他情况下,change buffer都能提昇更新性能。
- 由於唯一索引用不上change buffer的優化機制,因此如果 業務可以接受 ,從性能角度出發建議優先考慮非唯一索引。但是如果"業務可能無法確保"的情况下,怎麼處理呢?
- 首先, 業務正確性優先 。我們的前提是“業務代碼已經保證不會寫入重複數據”的情况下,討論性能問題。如果業務不能保證,或者業務就是要求數據庫來做約束,那麼沒得選,必須創建唯一索引。這種情况下,本節的意義在於,如果碰上了大量插入數據慢、內存命中率低的時候,給你多提供一個排查思路。
- 然後,在一些“ 歸檔庫 ”的場景,你是可以考慮使用唯一索引的。比如,線上數據只需要保留半年,然後曆史數據保存在歸檔庫。這時候,歸檔數據已經是確保沒有唯一鍵沖突了。要提高歸檔效率,可以考慮把錶裏面的唯一索引改成普通索引。
十二. 其它查詢優化策略
12.1. 關於SELECT(*)
在錶查詢中,建議明確字段,不要使用 * 作為查詢的字段列錶,推薦使用SELECT <字段列錶> 查詢。原因:
① MySQL 在解析的過程中,會通過 查詢數據字典 將"*"按序轉換成所有列名,這會大大的耗費資源和時間。
② 無法使用 覆蓋索引。
12.2. LIMIT 1 對優化的影響
針對的是會掃描全錶的 SQL 語句,如果你可以確定結果集只有一條,那麼加上 LIMIT 1 的時候,當找到一條結果的時候就不會繼續掃描了,這樣會加快查詢速度。
如果數據錶已經對字段建立了唯一索引,那麼可以通過索引進行查詢,不會全錶掃描的話,就不需要加上 LIMIT 1 了。
12.3. 多使用COMMIT
只要有可能,在程序中盡量多使用 COMMIT,這樣程序的性能得到提高,需求也會因為 COMMIT 所釋放的資源而减少。
COMMIT 所釋放的資源:
- 回滾段上用於恢複數據的信息。
- 被程序語句獲得的鎖。
- redo / undo log buffer 中的空間。
- 管理上述 3 種資源中的內部花費。
边栏推荐
- Write your own CPU Chapter 10 - learning notes
- 基于Pytorch肺部感染识别案例(采用ResNet网络结构)
- MySQL combat optimization expert 12 what does the memory data structure buffer pool look like?
- 实现以form-data参数发送post请求
- cmooc互联网+教育
- Pytorch LSTM实现流程(可视化版本)
- Complete web login process through filter
- 如何搭建接口自动化测试框架?
- MySQL combat optimization expert 04 uses the execution process of update statements in the InnoDB storage engine to talk about what binlog is?
- 13 医疗挂号系统_【 微信登录】
猜你喜欢
The appearance is popular. Two JSON visualization tools are recommended for use with swagger. It's really fragrant
The programming ranking list came out in February. Is the result as you expected?
寶塔的安裝和flask項目部署
MySQL33-多版本并发控制
C miscellaneous dynamic linked list operation
宝塔的安装和flask项目部署
Installation de la pagode et déploiement du projet flask
Implement sending post request with form data parameter
Bytetrack: multi object tracking by associating every detection box paper reading notes ()
该不会还有人不懂用C语言写扫雷游戏吧
随机推荐
MySQL36-数据库备份与恢复
使用OVF Tool工具从Esxi 6.7中导出虚拟机
MySQL33-多版本并发控制
Several errors encountered when installing opencv
Flash operation and maintenance script (running for a long time)
Constants and pointers
A necessary soft skill for Software Test Engineers: structured thinking
Target detection -- yolov2 paper intensive reading
flask运维脚本(长时间运行)
再有人问你数据库缓存一致性的问题,直接把这篇文章发给他
Bytetrack: multi object tracking by associating every detection box paper reading notes ()
保姆级手把手教你用C语言写三子棋
Isn't there anyone who doesn't know how to write mine sweeping games in C language
[Julia] exit notes - Serial
Vscode common instructions
Southwest University: Hu hang - Analysis on learning behavior and learning effect
UEditor国际化配置,支持中英文切换
Super detailed steps for pushing wechat official account H5 messages
MySQL real battle optimization expert 08 production experience: how to observe the machine performance 360 degrees without dead angle in the process of database pressure test?
Complete web login process through filter