当前位置:网站首页>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 種資源中的內部花費。
边栏推荐
- MySQL31-MySQL事务日志
- 百度百科数据爬取及内容分类识别
- [C language] deeply analyze the underlying principle of data storage
- text 文本数据增强方法 data argumentation
- Time in TCP state_ The role of wait?
- MySQL实战优化高手10 生产经验:如何为数据库的监控系统部署可视化报表系统?
- Sichuan cloud education and double teacher model
- Write your own CPU Chapter 10 - learning notes
- Const decorated member function problem
- 第一篇博客
猜你喜欢
随机推荐
第一篇博客
Use JUnit unit test & transaction usage
A new understanding of RMAN retention policy recovery window
Security design verification of API interface: ticket, signature, timestamp
C miscellaneous dynamic linked list operation
pytorch的Dataset的使用
MySQL combat optimization expert 05 production experience: how to plan the database machine configuration in the real production environment?
South China Technology stack cnn+bilstm+attention
Implement context manager through with
MNIST implementation using pytoch in jupyter notebook
Several errors encountered when installing opencv
14 medical registration system_ [Alibaba cloud OSS, user authentication and patient]
cmooc互联网+教育
flask运维脚本(长时间运行)
Implement sending post request with form data parameter
MySQL ERROR 1040: Too many connections
UEditor国际化配置,支持中英文切换
MySQL combat optimization expert 06 production experience: how does the production environment database of Internet companies conduct performance testing?
实现微信公众号H5消息推送的超级详细步骤
16 medical registration system_ [order by appointment]