MySQL高級學習之索引失效及優化


MySQL學習筆記(第三天)

參考文章:

mysql中in和exits的區別

1.1單表使用索引避免常見的索引失效

環境准備

create table `tb_seller` (
	`sellerid` varchar (100),
	`name` varchar (100),
	`nickname` varchar (50),
	`password` varchar (60),
	`status` varchar (1),
	`address` varchar (100),
	`createtime` datetime,
    primary key(`sellerid`)
)engine=innodb default charset=utf8mb4; 

insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','華為科技有限公司','華為小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','傳智播客教育科技有限公司','傳智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑馬程序員','黑馬程序員','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','羅技科技有限公司','羅技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗艦店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗艦店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗艦店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗艦店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');


create index idx_seller_name_sta_addr on tb_seller(name,status,address);

a.全值匹配

​ 對索引中所有列都指定具體值。該情況下,索引生效,執行效率高。

explain select * from tb_seller where name='小米科技' and status='1' and address='北京市'\G;

b.最左前綴法則

​ 如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始,並且不跳過索引中的列。

c.范圍查詢右邊的列,不能使用索引

​ 根據前面的兩個字段name , status 查詢是走索引的, 但是最后一個條件address 沒有用到索引。

d.不要在索引列上進行運算操作,否則索引將失效

e. 字符串(varchar)不加單引號,造成索引失效

​ 由於,在查詢時,沒有對字符串加單引號,MySQL的查詢優化器,會自動的進行類型轉換,造成索引失效。

f.盡量使用覆蓋索引,避免select *

​ 盡量使用覆蓋索引(只訪問索引的查詢(索引列完全包含查詢列)),減少select * 。

​ 如果查詢列,超出索引列,也會降低性能。

TIP : 
	
    using index :使用覆蓋索引的時候就會出現

    using where:在查找使用索引的情況下,需要回表去查詢所需的數據

    using index condition:查找使用了索引,但是需要回表查詢數據

    using index ; using where:查找使用了索引,但是需要的數據都在索引列中能找到,所以不需要回表查詢數據

g. 用or分割開的條件, 如果or前的條件中的列有索引,而后面的列中沒有索引,那么涉及的索引都不會被用到。

​ 示例,name字段是索引列 , 而createtime不是索引列,中間是or進行連接是不走索引的 :

h. 以%開頭的Like模糊查詢,索引失效。

​ 如果僅僅是尾部模糊匹配,索引不會失效。如果是頭部模糊匹配,索引失效。

​ 解決方案 :

​ 通過覆蓋索引來解決

i. is NULL , is NOT NULL 有時索引失效。

j. in 走索引, not in 索引失效。

【優化總結口訣】

全值匹配我最愛,最左前綴要遵守;
帶頭大哥不能死,中間兄弟不能斷;
索引列上無計算,范圍之后全失效;
Like百分寫最右,覆蓋索引不寫星;
不等空值還有or,索引失效要少用;
VAR引號不可丟,SQL高級也不難!

2.優化insert語句

當進行數據的insert操作的時候,可以考慮采用以下幾種優化方案。

  • 如果需要同時對一張表插入很多行數據時,應該盡量使用多個值表的insert語句,這種方式將大大的縮減客戶端與數據庫之間的連接、關閉等消耗。使得效率比分開執行的單個insert語句快。

    示例, 原始方式為:

    insert into tb_test values(1,'Tom');
    insert into tb_test values(2,'Cat');
    insert into tb_test values(3,'Jerry');
    

    優化后的方案為 :

    insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
    
  • 在事務中進行數據插入。

    start transaction;
    insert into tb_test values(1,'Tom');
    insert into tb_test values(2,'Cat');
    insert into tb_test values(3,'Jerry');
    commit;
    
  • 數據有序插入

    insert into tb_test values(4,'Tim');
    insert into tb_test values(1,'Tom');
    insert into tb_test values(3,'Jerry');
    insert into tb_test values(5,'Rose');
    insert into tb_test values(2,'Cat');
    

    優化后

    insert into tb_test values(1,'Tom');
    insert into tb_test values(2,'Cat');
    insert into tb_test values(3,'Jerry');
    insert into tb_test values(4,'Tim');
    insert into tb_test values(5,'Rose');
    

3.優化order by語句

3.1環境准備

CREATE TABLE `emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `age` int(3) NOT NULL,
  `salary` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

insert into `emp` (`id`, `name`, `age`, `salary`) values('1','Tom','25','2300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('2','Jerry','30','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('3','Luci','25','2800');
insert into `emp` (`id`, `name`, `age`, `salary`) values('4','Jay','36','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('5','Tom2','21','2200');
insert into `emp` (`id`, `name`, `age`, `salary`) values('6','Jerry2','31','3300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('7','Luci2','26','2700');
insert into `emp` (`id`, `name`, `age`, `salary`) values('8','Jay2','33','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('9','Tom3','23','2400');
insert into `emp` (`id`, `name`, `age`, `salary`) values('10','Jerry3','32','3100');
insert into `emp` (`id`, `name`, `age`, `salary`) values('11','Luci3','26','2900');
insert into `emp` (`id`, `name`, `age`, `salary`) values('12','Jay3','37','4500');

create index idx_emp_age_salary on emp(age,salary);

3.2排序方式

1). 第一種是通過對返回數據進行排序,也就是通常說的 filesort 排序,所有不是通過索引直接返回排序結果的排序都叫 FileSort 排序。

2). 第二種通過有序索引順序掃描直接返回有序數據,這種情況即為 using index,不需要額外排序,操作效率高。

多字段排序

了解了MySQL的排序方式,優化目標就清晰了:盡量減少額外的排序,通過索引直接返回有序數據。where 條件和Order by 使用相同的索引,並且Order By 的順序和索引順序相同, 並且Order by 的字段都是升序,或者都是降序。否則肯定需要額外的操作,這樣就會出現FileSort。

3.3FileSort的優化

通過創建合適的索引,能夠減少 Filesort 的出現,但是在某些情況下,條件限制不能讓Filesort消失,那就需要加快 Filesort的排序操作。對於Filesort , MySQL 有兩種排序算法:

1) 兩次掃描算法 :MySQL4.1 之前,使用該方式排序。首先根據條件取出排序字段和行指針信息,然后在排序區 sort buffer 中排序,如果sort buffer不夠,則在臨時表 temporary table 中存儲排序結果。完成排序之后,再根據行指針回表讀取記錄,該操作可能會導致大量隨機I/O操作。

2)一次掃描算法:一次性取出滿足條件的所有字段,然后在排序區 sort buffer 中排序后直接輸出結果集。排序時內存開銷較大,但是排序效率比兩次掃描算法要高。

MySQL 通過比較系統變量 max_length_for_sort_data 的大小和Query語句取出的字段總大小, 來判定是否那種排序算法,如果max_length_for_sort_data 更大,那么使用第二種優化之后的算法;否則使用第一種。

可以適當提高 sort_buffer_size 和 max_length_for_sort_data 系統變量,來增大排序區的大小,提高排序的效率。

4.優化group by語句

由於GROUP BY 實際上也同樣會進行排序操作,而且與ORDER BY 相比,GROUP BY 主要只是多了排序之后的分組操作。當然,如果

在分組的時候還使用了其他的一些聚合函數,那么還需要一些聚合函數的計算。所以,在GROUP BY 的實現過程中,與 ORDER BY 一

樣也可以利用到索引。如果查詢包含 group by 但是用戶想要避免排序結果的消耗, 則可以執行order by null 禁止排序。如下 :

drop index idx_emp_age_salary on emp;

explain select age,count(*) from emp group by age;

優化后

explain select age,count(*) from emp group by age order by null;

從上面的例子可以看出,第一個SQL語句需要進行"filesort",而第二個SQL由於order by null 不需要進行 "filesort", 而上文提過Filesort往往非常耗費時間。

創建索引 :

create index idx_emp_age_salary on emp(age,salary);

5.優化嵌套查詢

Mysql4.1版本之后,開始支持SQL的子查詢。這個技術可以使用SELECT語句來創建一個單列的查詢結果,然后把這個結果作為過濾條件用在另一個查詢中。使用子查詢可以一次性的完成很多邏輯上需要多個步驟才能完成的SQL操作,同時也可以避免事務或者表鎖死,並且寫起來也很容易。但是,有些情況下,子查詢是可以被更高效的連接(JOIN)替代。

示例 ,查找有角色的所有的用戶信息 :

 explain select * from t_user where id in (select user_id from user_role );

執行結果為 :

優化后 :

explain select * from t_user u , user_role ur where u.id = ur.user_id;

連接(Join)查詢之所以更有效率一些 ,是因為MySQL不需要在內存中創建臨時表來完成這個邏輯上需要兩個步驟的查詢工作。

6.優化or條件

對於包含OR的查詢子句,如果要利用索引,則OR之間的每個條件列都必須用到索引 , 而且不能使用到復合索引; 如果沒有索引,則應該考慮增加索引。

獲取 emp 表中的所有的索引 :

示例 :

explain select * from emp where id = 1 or age = 30;

建議使用 union 替換 or :

我們來比較下重要指標,發現主要差別是 type 和 ref 這兩項

type 顯示的是訪問類型,是較為重要的一個指標,結果值從好到壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null  > index_merge > unique_subquery > index_subquery > range > index > ALL

UNION 語句的 type 值為 ref,OR 語句的 type 值為 range,可以看到這是一個很明顯的差距

UNION 語句的 ref 值為 const,OR 語句的 type 值為 null,const 表示是常量值引用,非常快

這兩項的差距就說明了 UNION 要優於 OR 。

7.優化分頁查詢

一般分頁查詢時,通過創建覆蓋索引能夠比較好地提高性能。一個常見又非常頭疼的問題就是 limit 2000000,10 ,此時需要MySQL排序前2000010 記錄,僅僅返回2000000 - 2000010 的記錄,其他記錄丟棄,查詢排序的代價非常大 。

優化思路一

在索引上完成排序分頁操作,最后根據主鍵關聯回原表查詢所需要的其他列內容。

優化思路二

該方案適用於主鍵自增的表,可以把Limit 查詢轉換成某個位置的查詢 。

8.使用SQL提示

SQL提示,是優化數據庫的一個重要手段,簡單來說,就是在SQL語句中加入一些人為的提示來達到優化操作的目的。

8.1 USE INDEX

在查詢語句中表名的后面,添加 use index 來提供希望MySQL去參考的索引列表,就可以讓MySQL不再考慮其他可用的索引。

create index idx_seller_name on tb_seller(name);

8.2 IGNORE INDEX

如果用戶只是單純的想讓MySQL忽略一個或者多個索引,則可以使用 ignore index 作為 hint 。

 explain select * from tb_seller ignore index(idx_seller_name) where name = '小米科技';

8.3 FORCE INDEX

為強制MySQL使用一個特定的索引,可在查詢中使用 force index 作為hint 。

create index idx_seller_address on tb_seller(address);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM