SQL優化(三)—— 索引、explain分析


一、什么是索引

  • 索引是一種排好序的快速查找的數據結構,它幫助數據庫高效的查詢數據
  • 在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式指向數據,這樣就可以在這些數據結構上實現高效的查找算法.這種數據結構,就是索引

  • 一般來說索引本身也很大,不可能全部存儲在內存中,因此往往以索引文件的形式存放在磁盤中

  • 我們平常所說的索引,如果沒有特別說明都是指BTree索引(平衡多路搜索樹). 其中聚集索引,次要索引,覆蓋索引復合索引,前綴索引,唯一索引默認都是使用的BTree索引,統稱索引.除了BTree索引之后,還有哈希索引

二、索引優缺點

  • 優點:
    • 提高數據查詢的效率,降低數據庫的IO成本
    • 通過索引對數據進行排序,降低數據排序的成本,降低CPU的消耗
  • 缺點:
    • 索引本身也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄,所以索引列也要占用空間
    • 雖然索引大大提高了查詢的速度,同時反向影響增刪改操作的效率,因為表中數據變化之后,會導致索引內容不准,所以也需要更新索引表信息,增加了數據庫的工作量
    • 隨着業務的不斷變化,之前建立的索引可能不能滿足查詢需求,需要消耗我們的時間去更新索引

三、索引的分類

  • 單值索引: 一個索引只包含單列,一個表可以有多個單列索引
  • 唯一索引:索引列的值必須唯一,但是允許有空值
  • 復合索引: 一個索引可以同時包含多列

四、索引結構

1、BTree索引

BTree圖示

  • 淺藍色: 磁盤塊
  • 深藍色: 數據項
  • 黃色: 數據的指針
  • 真實的數據僅在葉子節點中: 3, 5, 9, 10, 13, 15, 28, 29, 36, 60, 75, 79, 90,99

  • 查找過程: (假如要找29)
    1. 從樹根開始,即先把磁盤塊1中內容讀到內存中, 發生一次IO
    2. 確定29在(17,35)之間,鎖定磁盤塊1中的P2指針
    3. 根據P2指針,找到磁盤塊3, 讀取到內存中, 發生二次IO
    4. 29在(26,30)之間,鎖定磁盤塊3的P2指針
    5. 通過磁盤3的P2指針,將磁盤塊8的內容讀取到內存中, 發生第三次IO.
    6. 最終找到數據29,查詢結束,總共發生三次IO
  • 真實的情況:
    • 3層的BTree可以表示上百萬的數據,如果上百萬條數據,查找只需要三次IO,性能提高將是巨大的,如果沒有索引每次查找都要發生一次IO,那么總共就需要百萬次的IO,顯然成本是非常高的

2、Hash索引

3、full-Text索引

4、R-Tree索引

五、索引的使用

1、創建索引的情況

  1. 主鍵約束默認建立唯一索引
  2. 頻繁作為查詢條件的字段應該創建索引
  3. 多表查詢中與其它表進行關聯的字段,外鍵關系建立索引
  4. 單列索引/復合索引的選擇? 高並發下傾向於創建復合索引
  5. 查詢中經常用來排序的字段
  6. 查詢中經常用來統計或者分組字段

2、不創建索引的情況

  1. 頻繁更新的字段: 每次更新都會影響索引樹
  2. where條件查詢中用不到的字段
  3. 表記錄太少
  4. 經常增刪改的表: 更新了表,索引也得更新才行
  5. 注意: 如果一張表中,重復的記錄非常多,為它建立索引就沒有太大意義

3、索引創建語法

1 - 創建索引
2 create index 索引名稱 on 表名(列名...);
3 
4 - 刪除索引
5 drop index 索引名稱 on 表名;
6 
7 - 查看索引
8 show index from 表名;

六、性能分析

1、MySql Query Optimizer

mysql中有專門負責優化select語句的優化器模塊,主要功能:通過計算分析系統中收集到的統計信息,為客戶端請求的Query提供mysql認為最優的執行計划

2、Mysql常見的瓶頸

  1. CPU飽和:CPU飽和的時候,一般發生在數據裝入內存或從磁盤上讀取數據的時候
  2. IO瓶頸: 磁盤IO瓶頸發生在裝入數據遠大於內存容量的時候
  3. 服務器硬件的性能瓶頸

七、執行計划

  使用explain關鍵字可以模擬優化器執行SQL查詢語句,從而知道MYSQL是如何處理SQL語句的.我們可以用執行計划來分析查詢語句或者表結構的性能瓶頸

1、作用

  1. 查看表的讀取順序
  2. 查看數據庫讀取操作的操作類型
  3. 查看哪些索引有可能被用到
  4. 查看哪些索引真正被用到
  5. 查看表之間的引用
  6. 查看表中有多少行記錄被優化器查詢

2、語法

1 explain + sql語句
2 
3 explain select * from tsmall;
4 +----+-------------+---------+------+---------------+------+---------+------+------+-------+
5 | id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra |
6 +----+-------------+---------+------+---------------+------+---------+------+------+-------+
7 |  1 | SIMPLE      | product | ALL  | NULL          | NULL | NULL    | NULL |   16 |       |
8 +----+-------------+---------+------+---------------+------+---------+------+------+-------+

2.1 環境准備

 1 create table t1(
 2   id int primary key,
 3   name varchar(20),
 4   col1 varchar(20),
 5   col2 varchar(20),
 6   col3 varchar(20)
 7 );
 8 create table t2(
 9   id int primary key,
10   name varchar(20),
11    col1 varchar(20),
12    col2 varchar(20),
13   col3 varchar(20)
14 );
15 create table t3(
16   id int primary key,
17   name varchar(20),
18     col1 varchar(20),
19    col2 varchar(20),
20   col3 varchar(20)
21 );
22 insert into t1 values(1,'zs1','col1','col2','col3');
23 insert into t2 values(1,'zs2','col2','col2','col3');
24 insert into t3 values(1,'zs3','col3','col2','col3');
25 
26 create index ind_t1_c1 on t1(col1);
27 create index ind_t2_c1 on t2(col1);
28 create index ind_t3_c1 on t3(col1);
29 
30 create index ind_t1_c12 on t1(col1,col2);
31 create index ind_t2_c12 on t2(col1,col2);
32 create index ind_t3_c12 on t3(col1,col2);

2.2 id

  • select 查詢的序列號,包含一組數字,表示查詢中執行Select子句或操作表的順序
  • 三種情況:
  1. id值相同,執行順序由上而下

    1 sql
    2  explain select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id= t3.id and t1.name = 'zs';

  2. id值不同,id值越大優先級越高,越先被執行

    1 sql
    2  explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.name='zs3'));

  3. id值有相同的也有不同的,如果id相同,從上往下執行,id值越大,優先級越高,越先執行

    1 sql
    2  explain select t2.* from (select t3.id from t3 where t3.name='zs3') s1,t2 where s1.id = t2.id;

2.3 select_type

 查詢類型,主要用於區別

  • SIMPLE : 簡單的select查詢,查詢中不包含子查詢或者UNION

  • PRIMARY: 查詢中若包含復雜的子查詢,最外層的查詢則標記為PRIMARY

  • SUBQUERY : 在SELECT或者WHERE列表中包含子查詢

  • DERIVED : 在from列表中包含子查詢被標記為DRIVED衍生,MYSQL會遞歸執行這些子查詢,把結果放到臨時表中

  • UNION: 若第二個SELECT出現在union之后,則被標記為UNION, 若union包含在from子句的子查詢中,外層select被標記為:derived

  • UNION RESULT: 從union表獲取結果的select

1 sql
2   explain select col1,col2 from t1 union select col1,col2 from t2;

2.4 table

  顯示這一行的數據是和哪張表相關

2.5 type

訪問類型: all, index,range,ref,eq_ref, const,system,null

最好到最差依次是: system > const > eq_ref>ref >range > index > all , 最好能優化到range級別或則ref級別

  • system: 表中只有一行記錄(系統表), 這是const類型的特例, 基本上不會出現

  • const: 通過索引一次查詢就找到了,const用於比較primary key或者unique索引,因為只匹配一行數據,所以很快,如將主鍵置於where列表中,mysql就會將該查詢轉換為一個常量

1 sql
2   explain select * from (select * from t1 where id=1) s1;

 

  • eq_ref: 唯一性索引掃描, 對於每個索引鍵,表中只有一條記錄與之匹配, 常見於主鍵或者唯一索引掃描
1 sql
2   explain select * from t1,t2 where t1.id = t2.id;

  • ref : 非唯一性索引掃描,返回匹配某個單獨值的所有行,本質上也是一種索引訪問,它返回所有符合條件的行,然而它可能返回多個符合條件的行
1 sql
2   explain select * from t1 where col1='zs1';

  • range : 只檢索給定范圍的行, 使用一個索引來選擇行.key列顯示的是真正使用了哪個索引,一般就是在where條件中使用between,>,<,in 等范圍的條件,這種在索引范圍內的掃描比全表掃描要好,因為它只在某個范圍中掃描,不需要掃描全部的索引
1 sql
2   explain select * from t1 where id between 1 and 10;

  • index : 掃描整個索引表, index 和all的區別為index類型只遍歷索引樹. 這通常比all快,因為索引文件通常比數據文件小,雖然index和all都是讀全表,但是index是從索引中讀取,而all是從硬盤中讀取數據
1 sql
2   explain select id from t1;

  • all : full table scan全表掃描 ,將遍歷全表以找到匹配的行
1 sql
2   explain select * from t1;

  • 注意: 開發中,我們得保證查詢至少達到range級別,最好能達到ref以上

2.6 possible_keys

  SQL查詢中可能用到的索引,但查詢的過程中不一定真正使用

2.7 key

查詢過程中真正使用的索引,如果為null,則表示沒有使用索引

查詢中使用了覆蓋索引,則該索引僅出現在key列表中

1 explain select t2.* from t1,t2,t3 where t1.col1 = ' ' and t1.id = t2.id and t1.id= t3.id;

1 explain select col1 from t1;

2.8 key_len

  索引中使用的字節數,可通過該列計算查詢中使用的索引的長度,在不損失精確度的情況下,長度越短越好, key_len顯示的值為索引字段的最大可能長度,並非實際使用長度, 即key_len是根據表定義計算而得

1 explain select * from t1 where col1='c1';

1 explain select * from t1 where col1='col1' and col2 = 'col2';
2 
3 -- 注意: 為了演示這個結果,我們刪除了c1上面的索引
4 alter table t1 drop index ind_t1_c1;
5 -- 執行完成之后,再次創建索引
6 create index ind_t1_c1 on t1(col1);

2.9 ref

  顯示索引的哪一列被使用了,如果可能的話,是一個常數.哪些列或者常量被用於查找索引列上的值

1 explain select * from t1,t2 where t1.col1 = t2.col1 and t1.col2 = 'col2';

2.10 rows

根據表統計信息及索引選用的情況,估算找出所需記錄要讀取的行數 (有多少行記錄被優化器讀取)

2.11 extra

包含其它一些非常重要的額外信息

  • Using filesort : 說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,Mysql中無法利用索引完成的排序操作稱為文件排序
1 sql
2   explain select col1 from t1 where col1='col1' order by col3;

1 sql
2   -- 上面這條SQL語句出現了using filesort,但是我們去執行下面這條SQL語句的時候它,又不會出現using filesort
3   explain select col1 from t1 where col1='col1' order by col2;

1 sql
2   -- 如何優化第一條SQL語句 ? 
3   create index ind_t1_c13 on t1(col1,col3);
4   explain select col1 from t1 where col1='col1' order by col3;

  • Using temporary : 使用了臨時表保存中間結果,Mysql在對查詢結果排序時使用了臨時表,常見於order by 和分組查詢group by
1 sql
2   explain select col1 from t1 where col1>'col1' group by col2;

1 sql
2   explain select col1 from t1 where col1 >'col1' group by col1,col2;

  • Using index :

  • 查詢操作中使用了覆蓋索引(查詢的列和索引列一致),避免訪問了表的數據行,效率好

  • 如果同時出現了using where, 表明索引被用來執行索引鍵值的查找
  • 如果沒有同時出現using where, 表明索引用來讀取數據而非執行查找動作
  • 覆蓋索引: 查詢的列和索引列一致, 換句話說查詢的列要被所鍵的索引覆蓋,就是select中數據列只需從索引中就能讀取,不必讀取原來的數據行,MySql可以利用索引返回select列表中的字段,而不必根據索引再次讀取數據文件
1 sql
2   explain select col2 from t1 where col1='col1';

 

1 sql
2   explain select col2 from t1;

  • using where : 表明使用了where條件過濾

  • using join buffer : 表明使用了連接緩存, join次數太多了可能會出現

  • impossible where : where子句中的值總是false,不能用來獲取任何數據

1 sql
2   explain select * from t1 where col1='zs' and col1='ls';

  • select tables optimized away :

  • 在沒有group by 子句的情況下, 基於索引優化min/max操作或者對於MyISAM存儲引擎優化count(*)操作,不必等到執行階段再進行計算,查詢執行計划生成階段即完成優化

  • distinct : 優化distinct操作,在找到第一個匹配的數據后即停止查找同樣的值的動作

3、小練習

1 explain select a1.name,(select id from t3) a2
2 from
3     (select id,name from t1 where name='zs1') a1
4 union
5     select name,id from t2;

執行順序如下:
1 id=4, select_type為union, union后的select語句先被執行
2 
3 id=3, 因為(select id,name from t1 where name='zs1')被當作一張表處理,所以為select_type 為derived
4 
5 id=2, select_type為SUBQUERY,為select后面的子查詢
6 
7 id=1, 表示union中的第一個select, select_type為primary表示該查詢為外層查詢,table被標記為<derived3>,表示結果來自衍生表
8 
9 id=null,代表從union的臨時表中讀取行記錄, <union1,4>表示將id=1和id=4的結果進行union操作

4、索引優化

4.1 索引分析

單表
 1 create table if not exists article(
 2   id int primary key auto_increment,
 3   author_id int not null,
 4   category_id int not null,
 5   views int not null,
 6   comments int not null,
 7   title varchar(255) not null,
 8   content text not null
 9 );
10 
11 insert into article values(null,1,1,1,1,'1','1');
12 insert into article values(null,2,2,2,2,'2','2');
13 insert into article values(null,1,1,3,3,'3','3');
14 
15 select * from article;
16 
17 -- 查詢category_id為1且comments 大於 1 的所有記錄,顯示id,author_id,views
18 
19 explain select  id,author_id,views  from  article
20 where category_id = 1
21 and comments > 1;

1 -- 查詢category_id為1且comments 大於 1 的所有記錄,顯示id,author_id,views
2 -- 並且找出views查看次數最多的記錄
3 explain select  id,author_id,views from  article
4 where category_id = 1
5 and comments > 1
6 order by views desc limit 1;

 1 -- 總結上面出現的情況:type=all,產生了全表掃描, 並且出現了Using filesort,使用了外部的索引排序,所以優化是必須的
 2 
 3 -- 創建索引:
 4 create index ind_article_ccv on article(category_id,comments,views);
 5 
 6 --執行如下指令:
 7 explain select  id,author_id,views from  article
 8 where category_id = 1
 9 and comments > 1
10 order by views desc limit 1;

 1 創建索引之后type=range, 但是Using filesort 依然存在.
 2 
 3 索引創建了,為什么在排序的時候沒有生效?
 4 這是因為先排序category_id, 如果遇到相同的category_id,則再排序comments, 如果遇到相同的comments則再排序views,
 5 當comments字段在聯合索引處於中間位置時,
 6 因為comments>1條件是一個范圍值,所以type=range
 7 mysql無法再利用索引對后面的views部分進行檢索,即range類型查詢字段后面的索引全都無效
 8 
 9 綜上所述: 索引創建有問題
10 
11 -- 刪除上面創建的索引:
12 drop index ind_article_ccv on article;
13 -- 重新創建索引: 
14 create index ind_art_cb on article(category_id,views);
15 -- 重新執行如下代碼
16 
17 explain select  id,author_id,views from  article
18 where category_id = 1
19 and comments > 1
20 order by views desc limit 1;

兩張表
SQL准備
 1 create table if not exists class(
 2     id int(10) primary key auto_increment,
 3       card int not null
 4 );
 5 
 6 create table  if not exists book(
 7     bookid int primary key auto_increment,
 8       card int not null
 9 );
10 
11 insert into class(card) values(floor(1+rand()*20));
12 insert into class(card) values(floor(1+rand()*20));
13 insert into class(card) values(floor(1+rand()*20));
14 insert into class(card) values(floor(1+rand()*20));
15 insert into class(card) values(floor(1+rand()*20));
16 insert into class(card) values(floor(1+rand()*20));
17 insert into class(card) values(floor(1+rand()*20));
18 insert into class(card) values(floor(1+rand()*20));
19 insert into class(card) values(floor(1+rand()*20));
20 insert into class(card) values(floor(1+rand()*20));
21 
22 insert into book(card) values(floor(1+rand()*20));
23 insert into book(card) values(floor(1+rand()*20));
24 insert into book(card) values(floor(1+rand()*20));
25 insert into book(card) values(floor(1+rand()*20));
26 insert into book(card) values(floor(1+rand()*20));
27 insert into book(card) values(floor(1+rand()*20));
28 insert into book(card) values(floor(1+rand()*20));
29 insert into book(card) values(floor(1+rand()*20));
30 insert into book(card) values(floor(1+rand()*20));
31 insert into book(card) values(floor(1+rand()*20));
32 -- 下面開始分析
33 explain select * from class left join book on class.card = book.card;
34 
35 +----+-------------+-------+------+---------------+------+---------+------+------+-------+
36 | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
37 +----+-------------+-------+------+---------------+------+---------+------+------+-------+
38 |  1 | SIMPLE      | class | ALL  | NULL          | NULL | NULL    | NULL |   10 |       |
39 |  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL |   10 |       |
40 +----+-------------+-------+------+---------------+------+---------+------+------+-------+
41 
42 -- 結論: type 都有all
43 create index X on book(card);
44 
45 -- 分析
46 explain select * from class left join book on class.card = book.card;
47 -- 我們可以看到第二行的type變為了ref,rows也變小了,優化效果明顯

1 -- 這是由左連接特型決定的.left join條件用於確定如何從右表搜索行,左邊一定都有,所有右邊一定要建立索引
2 
3 -- 我們再來看一個右外連接
4 explain select * from class right join book on class.card = book.card;

三張表
SQL准備
 1 create table if not exists phone(
 2     phoneid int primary key auto_increment,
 3       card int
 4 );
 5 
 6 insert into phone(card) values(floor(1+rand()*20));
 7 insert into phone(card) values(floor(1+rand()*20));
 8 insert into phone(card) values(floor(1+rand()*20));
 9 insert into phone(card) values(floor(1+rand()*20));
10 insert into phone(card) values(floor(1+rand()*20));
11 insert into phone(card) values(floor(1+rand()*20));
12 insert into phone(card) values(floor(1+rand()*20));
13 insert into phone(card) values(floor(1+rand()*20));
14 insert into phone(card) values(floor(1+rand()*20));
15 insert into phone(card) values(floor(1+rand()*20));
16 
17 -- 在沒有添加索引的情況下執行如下語句
18 explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
19 
20 -- 創建索引:
21 create index Y on book(card);
22 create index Z on phone(card);

1 -- 后2行的type都是ref且總rows數量大大降低了,效果不錯,因此索引最好是設置在需要經常查詢的字段中
2 
3 -- 結論:
4 盡可能減少join語句中的循環總次數: 永遠用小結果集驅動大的結果集;
5 優先優化內層循環
6 保證join語句中被驅動表上的join條件字段已經創建過索引
7 當無法保證被驅動表的join條件字段是否有索引且內存資源充足的前提下,不要太吝惜JoinBuffer的設置

4.2 索引失效

建表語句

 1 create table emp(
 2     empno int primary key auto_increment,
 3     ename varchar(24) not null default '' comment '姓名',
 4       age int not null default 0 comment '年齡',
 5       job varchar(20) not null default '' comment '職位',
 6       hiredate timestamp not null default current_timestamp comment '入職時間'
 7 );
 8 
 9 insert into emp(ename,age,job,hiredate) values('zs',22,'manager',now());
10 insert into emp(ename,age,job,hiredate) values('ls',23,'dev',now());
11 insert into emp(ename,age,job,hiredate) values('1000',24,'manager',now());
12 insert into emp(ename,age,job,hiredate) values('zss',22,'manager',now());
13 insert into emp(ename,age,job,hiredate) values('xzss',22,'manager',now());
14 
15 select * from emp;
16 
17 create index ind_emp_naj on emp(ename,age,job);
  1. 全值匹配我最愛: 查詢的條件列剛好和索引創建的列數量和順序相同
 1 ```sql
 2 explain select * from emp where ename=‘zs’;
 3 explain select * from emp where ename=‘zs’ and age=22;
 4 explain select * from emp where ename=‘zs’ and age=22 and job=‘manager’;
 5 
 6   -- 有問題的sql
 7 explain select * from emp where age=22 and job=‘manager’;
 8 explain select * from emp where job=‘manager’;
 9 – 沒有問題的sql
10 explain select * from emp where ename=‘zs’;
11 ```

  2. 最佳左前綴法則: 如果索引了多列,要遵循最左前綴法則. 查詢從索引的最左列開始並且不跳過索引中的列

1 ```sql
2 explain select * from emp where ename=‘zs’ and job=‘manager’;
3 
4 explain select * from emp where job=‘manager’;
5 
6 explain select * from emp where ename=‘zs’;
7 ```

  3.不在索引上做任何操作(計算,函數,(自動/手動)類型轉換,這樣會導致索引失效而轉向全表掃描

1 sql
2    explain select * from emp where left(ename,2)='zs';

  4.存儲引擎不能使用索引中范圍條件右邊的列

1 ```sql
2 – 使用到了索引
3 explain select * from emp where ename=‘zs’;
4 explain select * from emp where ename=‘zs’ and age=22;
5 explain select * from emp where ename=‘zs’ and age=22 and job=‘manager’;
6 
7 explain select * from emp where ename=‘zs’ and age>22 and job=‘manager’;
8 ```

  5.盡量使用覆蓋索引(要查詢的列和索引中的列一致),避免使用select *

1 sql
2    explain select * from emp where ename='zs' and age=22 and job='manager';
3    explain select ename,age,job from emp where ename='zs' and age=22 and job='manager';
4    explain select ename,age,job from emp where ename='zs' and age>22 and job='manager';
5    explain select ename,age,job from emp where ename='zs' and age=22;

  6.mysql在使用不等於條件判斷的時候,索引會失效引發全表掃描

1 sql
2    explain select * from emp where ename='zs';
3    explain select * from emp where ename != 'zs';

  7.is null, is not null 索引會失效,無法使用索引

1 sql
2    explain select * from emp where ename is null;
3    explain select * from emp where ename is not null;​

  7.like以通配符開頭(‘%xxx…’)索引會失效,導致全表掃描

 1 ```sql
 2 explain select * from emp where ename like%zs%’;
 3 explain select * from emp where ename like%zs’;
 4 explain select * from emp where ename like ‘zs%’;
 5 
 6 問題: 解決like ‘%xxx%’ 時索引失效的問題
 7 explain select ename from emp where ename like%zs%’;
 8 
 9 explain select ename,age from emp where ename like%zs%’;
10 explain select ename,age,job from emp where ename like%zs%’;
11 explain select ename,age,job,hiredate from emp where ename like%zs%’;
12 ```

  8.字符串不加單引號索引失效

1 sql
2    explain select * from emp where ename=1000;
3    explain select * from emp where ename='1000';

  9.少用or,它會導致索引失效

1 explain select * from emp where ename='zs' or ename='ls';
  10.小總結

4.3 面試題分析

 1 create table test3(
 2     id int primary key auto_increment,
 3       c1 char(10),
 4       c2 char(10),
 5       c3 char(10),
 6       c4 char(10),
 7       c5 char(10)
 8 );
 9 
10 insert into test3 values(null,'a1','a2','a3','a4','a5');
11 insert into test3 values(null,'b1','b2','b3','b4','b5');
12 insert into test3 values(null,'c1','c2','c3','c4','c5');
13 insert into test3 values(null,'d1','d2','d3','d4','d5');
14 insert into test3 values(null,'e1','e2','e3','e4','e5');
15 
16 select * from test3;
17 
18 -- 創建索引
19 create index ind_test3_c1234 on test3(c1,c2,c3,c4);
20 show index from test3;
21 
22 explain select * from test3 where c1='a1';
23 explain select * from test3 where c1='a1' and c2='a2';
24 explain select * from test3 where c1='a1' and c2='a2' and c3='a3';
25 explain select * from test3 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
26 explain select * from test3 where c1='a1' and c2='a2' and c3='a3' and c4='a4' and c5='a5';
27 
28 -- 請執行如下問題SQL,分析會出現的問題
29 -- (1)
30 explain select * from test3 where c1='a1' and c2='a2' and c4='a4' and c3='a3' ;
31 -- (2)
32 explain select * from test3 where c4='a1' and c3='a2' and c2='a4' and c1='a3' ;
33 -- (3)
34 explain select * from test3 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
35 -- (4)
36 explain select * from test3 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
37 -- (5)
38 explain select * from test3 where c1='a1' and c2='a2' and c4='a4' order by c3; 
39 -- (6)
40 explain select * from test3 where c1='a1' and c2='a2' order by c3; 
41 -- (7)
42 explain select * from test3 where c1='a1' and c2='a2' order by c4; 
43 -- (8)
44 explain select * from test3 where c1='a1' and c5='a5' order by c2,c3; 
45 只用了c1這個字段索引,但是c2,c3用於排序,無filesort
46 explain select * from test3 where c1='a1' and c5='a5' order by c3,c2; 
47 只用了c1這個字段索引,但是由於c3,c2順序顛倒了,所以無法使用索引排序,出現filesort
48 
49 -- (9)
50 explain select * from test3 where c1='a1' and c2='a2' order by c2,c3; 
51 -- (10)
52 explain select * from test3 where c1='a1' and c2='a2' and c5='a5' order by c2,c3; 
53 用到了c1,c2兩個字段索引,但是c2,c3用於排序,無filesort
54 explain select * from test3 where c1='a1' and c2='a2' and c5='a5' order by c3,c2; 
55 排序字段已經是一個常量,所以不會出現filesort
56 
57 -- (11)
58 explain select * from test3 where c1='a1' and c4='c4' group by c2,c3;
59 用到了c1字段索引
60 explain select * from test3 where c1='a1' and c4='c4' group by c3,c2;
61 索引字段順序不正確,出現了Using temporary; Using filesort
  建議
    1. 對於單列索引,盡量選擇針對當前查詢過濾性更好的索引
    2. 在選擇復合索引的時候,當前查詢中過濾性最好的字段在索引字段順序中,位置越靠前越好
    3. 在創建復合索引的時候,盡量選擇能夠包含查詢中where子句中更多的字段
    4. 盡可能通過分析統計信息和調整查詢的寫法來達到選擇合適索引的目的

4.4 in和exists

優化規則: 小表驅動大表,即小的數據集驅動大的數據集

 1 -- 當A的數據集大於B的數據集時, in 優於 exists
 2 select * from A where id in (select id from B);
 3 等價於:
 4     for select id from B
 5         for select * from A where A.id = B.id;
 6 
 7 -- 當A的數據集小於B的數據集時, exists優於in
 8 select * from A where exists(select 1 from B where B.id = A.id);
 9 等價於:
10     for select * from A
11         for select * from B where B.id = A.id

4.5 Order by 排序優化

  1. order by 子句,盡量使用index方式排序,避免使用Filesort方式排序
1 sql
2    select * from emp order by age
3    select * from emp order by age,birth
4    select * from emp order by birth,age
5    select * from emp order by age asc,birth desc;

盡可能再索引列上完成排序操作,遵照索引建的最佳左前綴

  1. 如果不在索引列上,filesort算法:雙路排序,單路排序

  2. 雙路排序: mysql4.1之前使用的雙路排序,字面意思就是掃描兩次磁盤,從而得到最終的數據,讀取行指針和order by 列,對它們進行排序,然后掃描已經排序好的列表,按照列表中的值重新從列表中讀取對應的數據輸出. 從磁盤取排序字段,在buffer進行排序,再從磁盤取其它字段

  3. 單路排序:從磁盤讀取查詢需要的所有列,按照order by列在buffer對它們進行排序,然后掃描排序后的列表進行輸出,它的效率更快一些,避免了第二次讀取數據.並且把隨機IO變成了順序IO,但是它會使用更多的空間,因為它把每一行都保存到內存中
  4. 結論:
    1. 單路排序,優於雙路排序
    2. 在sort_buffer中,單路要比雙路占用更多的空間,因為單路是把所有的字段都取出來,有可能導致取出的數據總大小超出了sort_buffer的容量,導致每次只能取sort_buffer容量大小的數據,進行排序(創建tmp,多路合並),排完再取sort_buffer容量大小,再排…從而導致了多次IO. (本來想節省一次IO操作,反而導致了大量的IO操作,得不償失)
  5. 優化策略

  6. 增大sort_buffer_size參數設置

  7. 增大max_length_for_sort_data參數設置
  8. why
    1. order by時,select * 時一個大忌,只查詢需要的字段,這點非常重要.
      1. 當查詢的字段大小總和小於max_length_for_sort_data而且排序字段不是TEXT|BLOB類型時,會用單路排序,否則會用多路排序
      2. 兩種算法的數據都有可能超過sort_buffer的容量,超出之后,會創建tmp文件進行合並排序,導致多次IO,但是用單路排序算法的風險會更大一些,所以要提高sort_buffer_size
    2. 嘗試調高sort_buffer_size: 不管使用哪種算法,提高這個參數都會提高效率,當然,要根據系統的能力去調高,因為這個參數是針對每個進程的
    3. 嘗試調高max_length_for_sort_data: 調高這個參數,會增加使用單路算法的概率,但是如果設置太高,數據總容量超出sort_buffer_size的概率就增大,明顯症狀是高的磁盤IO活動和低的處理器使用率
  9. 總結

 1 ```sql
 2 mysql有兩種排序方式: 文件排序和掃描有序索引排序
 3 mysql能為排序和查詢使用相同的索引
 4 
 5 index abc(a,b,c)
 6 order by 能使用最左前綴
 7 order by a
 8 order by b
 9 order by a,b,c
10 order by a desc,b desc,c desc
11 
12 如果where使用索引的最前綴定義為常量,則order by能使用索引
13 where a=const order by b,c
14 where a=const and b = const order by c
15 where a=const order by b,c
16 where a=const and b > const order by b,c
17 
18 不能使用索引進行排序
19 order by a asc,b desc, c desc /*排序順序不一致*/
20 where g=const order by b,c /*丟失a索引*/
21 where a=const order by c /*丟失b索引*/
22 where a=const order by a,d /*d不是索引*/
23 where a in(…) order by b,c /*對於排序來說,in 相當於是范圍查詢*/
24 ```


免責聲明!

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



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