SQL優化:索引優化

SQL索引

   SQL索引在數據庫優化中佔有一個很是大的比例, 一個好的索引的設計,可讓你的效率提升幾十甚至幾百倍,在這裏將帶你一步步揭開他的神祕面紗。mysql

  1.1 什麼是索引?web

  SQL索引有兩種,彙集索引和非彙集索引,索引主要目的是提升了SQL Server系統的性能,加快數據的查詢速度與減小系統的響應時間 sql

下面舉兩個簡單的例子:數據庫

        圖書館的例子:一個圖書館那麼多書,怎麼管理呢?創建一個字母開頭的目錄,例如:a開頭的書,在第一排,b開頭的在第二排,這樣在找什麼書就好說了,這個就是一個彙集索引,但是不少人借書找某某做者的,不知道書名怎麼辦?圖書管理員在寫一個目錄,某某做者的書分別在第幾排,第幾排,這就是一個非彙集索引api

        字典的例子:字典前面的目錄,能夠按照拼音和部首去查詢,咱們想查詢一個字,只須要根據拼音或者部首去查詢,就能夠快速的定位到這個漢字了,這個就是索引的好處,拼音查詢法就是彙集索引,部首查詢就是一個非彙集索引.性能優化

    看了上面的例子,下面的一句話你們就很容易理解了:網絡

  1. 彙集索引存儲記錄是物理上連續存在,而非彙集索引是邏輯上的連續,物理存儲並不連續。
  2. 就像字段,彙集索引是連續的,a後面確定是b,非彙集索引就不連續了,就像圖書館的某個做者的書,有可能在第1個貨架上和第10個貨架上。
  3. 還有一個小知識點就是:彙集索引一個表只能有一個,而非彙集索引一個表能夠存在多個。

 

   1.2 索引的存儲機制app

    首先,無索引的表,查詢時,是按照順序存續的方法掃描每一個記錄來查找符合條件的記錄,這樣效率十分低下,舉個例子,若是咱們將字典的漢字隨即打亂,沒有前面的按照拼音或者部首查詢,那麼咱們想找一個字,按照順序的方式去一頁頁的找,這樣效率有多底,你們能夠想象。數據庫設計

       彙集索引和非彙集索引的根本區別表記錄的排列順序和與索引的排列順序是否一致,其實理解起來很是簡單,仍是舉字典的例子:若是按照拼音查詢,那麼都是從a-z的,是具備連續性的,a後面就是b,b後面就是c, 彙集索引就是這樣的,他是和表的物理排列順序是同樣的,例若有id爲彙集索引,那麼1後面確定是2,2後面確定是3,因此說這樣的搜索順序的就是彙集索引。ide

        非彙集索引就和按照部首查詢是同樣是,可能按照偏房查詢的時候,根據偏旁‘弓’字旁,索引出兩個漢字,張和弘,可是這兩個其實一個在100頁,一個在1000頁,(這裏只是舉個例子),他們的索引順序和數據庫表的排列順序是不同的,這個樣的就是非彙集索引

      原理明白了,那他們是怎麼存儲的呢?在這裏簡單的說一下,彙集索引就是在數據庫被開闢一個物理空間存放他的排列的值,例如1-100,因此當插入數據時,他會從新排列整個整個物理空間,而非彙集索引其實能夠看做是一個含有彙集索引的表,他只僅包含原表中非彙集索引的列和指向實際物理表的指針。他只記錄一個指針,其實就有點和堆棧差很少的感受了

 

  1.3 什麼狀況下設置索引 

動做描述

使用匯集索引 

 使用非彙集索引

 外鍵列

 應

 應

 主鍵列

 應

 應

 列常常被分組排序(order by)

 應

 應

 返回某範圍內的數據

 應

 不該

 小數目的不一樣值

 應

 不該

 大數目的不一樣值

 不該

 應

 頻繁更新的列

不該 

 應

 頻繁修改索引列

 不該

 應

 一個或極少不一樣值

 不該

 不該

 

  • 創建索引的原則:

1) 定義主鍵的數據列必定要創建索引。

2) 定義有外鍵的數據列必定要創建索引。

3) 對於常常查詢的數據列最好創建索引。

4) 對於須要在指定範圍內的快速或頻繁查詢的數據列;

5) 常常用在WHERE子句中的數據列。

6) 常常出如今關鍵字order by、group by、distinct後面的字段,創建索引。若是創建的是複合索引,索引的字段順序要和這些關鍵字後面的字段順序一致,不然索引不會被使用。

7) 對於那些查詢中不多涉及的列,重複值比較多的列不要創建索引。

8) 對於定義爲textimagebit的數據類型的列不要創建索引。

9) 對於常常存取的列避免創建索引 

9) 限制表上的索引數目。對一個存在大量更新操做的表,所建索引的數目通常不要超過3個,最多不要超過5個索引雖然說提升了訪問速度,但太多索引會影響數據的更新操做

10) 對複合索引,按照字段在查詢條件中出現的頻度創建索引。在複合索引中,記錄首先按照第一個字段排序。對於在第一個字段上取值相同的記錄,系統再按照第二個字段的取值排序,以此類推。所以只有複合索引的第一個字段出如今查詢條件中,該索引纔可能被使用,所以將應用頻度高的字段,放置在複合索引的前面,會使系統最大可能地使用此索引,發揮索引的做用。

 

  • 索引的不足之處

上面都在說使用索引的好處,但過多的使用索引將會形成濫用。所以索引也會有它的缺點:

  1. 雖然索引大大提升了查詢速度,同時卻會下降更新表的速度,如對錶進行INSERT、UPDATE和DELETE。由於更新表時,MySQL不只要保存數據,還要保存一下索引文件。
  2. 創建索引會佔用磁盤空間的索引文件。通常狀況這個問題不太嚴重,但若是你在一個大表上建立了多種組合索引,索引文件的會膨脹很快。索引只是提升效率的一個因素,若是你的MySQL有大數據量的表,就須要花時間研究創建最優秀的索引,或優化查詢語句。


使用索引時,有如下一些技巧和注意事項:

  • 索引不會包含有NULL值的列

只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時不要讓字段的默認值爲NULL。

  • 使用短索引

對串列進行索引,若是可能應該指定一個前綴長度。例如,若是有一個CHAR(255)的列,若是在前10個或20個字符內,多數值是唯一的,那麼就不要對整個列進行索引。短索引不只能夠提升查詢速度並且能夠節省磁盤空間和I/O操做。

  • 索引列排序

MySQL查詢只使用一個索引,所以若是where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。所以數據庫默認排序能夠符合要求的狀況下不要使用排序操做儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引

  • like語句操做

通常狀況下不鼓勵使用like操做,若是非使用不可,如何使用也是一個問題。like 「%aaa%」 不會使用索引而like 「aaa%」可使用索引

  • 不要在列上進行運算

將在每一個行上進行運算,這將致使索引失效而進行全表掃描,所以咱們能夠改爲

1
select * from users where adddate<‘2007-01-01’;  


  • 不使用NOT IN和<>操做


1.4 如何建立索引

  1.41 建立索引的語法:

1.42 刪除索引語法:

 
1
DROP INDEX table_name.index_name[,table_name.index_name]
2
3
說明:table_name: 索引所在的表名稱。
4
5
index_name : 要刪除的索引名稱。

1.43 顯示索引信息:

使用系統存儲過程:sp_helpindex 查看指定表的索引信息。

執行代碼以下:

 1.44查詢索引(都可) 

 
1
show index from table_name;
2
show keys from table_name;
3
desc table_Name;

  1.44組合索引

不少時候,咱們在mysql中建立了索引,可是某些查詢仍是很慢,根本就沒有使用到索引!通常來講,多是某些字段沒有建立索引,或者是組合索引中字段的順序與查詢語句中字段的順序不符。

看下面的例子:
假設有一張訂單表(orders),包含order_id和product_id二個字段。
一共有31條數據。符合下面語句的數據有5條。執行下面的sql語句:

這條語句要mysql去根據order_id進行搜索,而後返回匹配記錄中的product_id。因此組合索引應該按照如下的順序建立:

 
1
create index orderid_productid on orders(order_id, product_id)
2
mysql> explain select product_id from orders where order_id in (123, 312, 223, 132, 224) \G
3
*************************** 1. row ***************************
4
           id: 1
5
  select_type: SIMPLE
6
        table: orders
7
         type: range
8
possible_keys: orderid_productid
9
          key: orderid_productid
10
      key_len: 5
11
          ref: NULL
12
         rows: 5
13
        Extra: Using where; Using index
14
1 row in set (0.00 sec)
能夠看到,這個組合索引被用到了,掃描的範圍也很小,只有5行。若是把組合索引的順序換成product_id, order_id的話,mysql就會去索引中搜索 *123 *312 *223 *132 *224,必然會有些慢了。
此次索引搜索的性能顯然不能和上次相比了。rows:31,個人表中一共就31條數據。索引被使用部分的長度:key_len:10,比上一次的key_len:5多了一倍。不知道是這樣在索引裏面查找速度快,仍是直接去全表掃描更快呢?
 
1
mysql> alter table orders add modify_a char(255) default 'aaa';
2
Query OK, 31 rows affected (0.01 sec)
3
Records: 31  Duplicates: 0  Warnings: 0
4
 
5
mysql>
6
mysql>
7
mysql> explain select modify_a from orders where order_id in (123, 312, 223, 132, 224) \G         
8
*************************** 1. row ***************************
9
           id: 1
10
  select_type: SIMPLE
11
        table: orders
12
         type: ALL
13
possible_keys: NULL
14
          key: NULL
15
      key_len: NULL
16
          ref: NULL
17
         rows: 31
18
        Extra: Using where
19
1 row in set (0.00 sec)
這樣就不會用到索引了。 剛纔是由於select的product_id與where中的order_id都在索引裏面的。


爲何要建立組合索引呢?這麼簡單的狀況直接建立一個order_id的索引不就好了嗎?果只有一個order_id索引,沒什麼問題,會用到這個索引,而後mysql要去磁盤上的表裏面取到product_id。若是有組合索引的話,mysql能夠徹底從索引中取到product_id,速度天然會快。再多說幾句組合索引的最左優先原則:
組合索引的第一個字段必須出如今查詢組句中,這個索引纔會被用到。果有一個組合索引(col_a,col_b,col_c),下面的狀況都會用到這個索引:

對於最後一條語句,mysql會自動優化成第三條的樣子~~。下面的狀況就不會用到索引:
 
1
col_b = "aaaaaa";
2
col_b = "aaaa" and col_c = "cccccc";

經過實例理解單列索引、多列索引以及最左前綴原則。實例:如今咱們想查出知足如下條件的用戶id:


由於咱們不想掃描整表,故考慮用索引。


1.單列索引:

 
1
ALTER TABLE people ADD INDEX lname (lname);


將lname列建索引,這樣就把範圍限制在lname='Liu'的結果集1上,以後掃描結果集1,產生知足fname='Zhiqun'的結果集2,再掃描結果集2,找到 age=26的結果集3,即最終結果。

由 於創建了lname列的索引,與執行表的徹底掃描相比,效率提升了不少,但咱們要求掃描的記錄數量仍舊遠遠超過了實際所需 要的。雖然咱們能夠刪除lname列上的索引,再建立fname或者age 列的索引,可是,不論在哪一個列上建立索引搜索效率仍舊類似。

2.多列索引:


爲了提升搜索效率,咱們須要考慮運用多列索引,因爲索引文件以B-Tree格式保存,因此咱們不用掃描任何記錄,便可獲得最終結果。

注:在mysql中執行查詢時,只能使用一個索引,若是咱們在lname,fname,age上分別建索引,執行查詢時,只能使用一個索引,mysql會選擇一個最嚴格(得到結果集記錄數最少)的索引

3.最左前綴:顧名思義,就是最左優先,上例中咱們建立了lname_fname_age多列索引,至關於建立了(lname)單列索引,(lname,fname)組合索引以及(lname,fname,age)組合索引。

注:在建立多列索引時,要根據業務需求,where子句中使用最頻繁的一列放在最左邊

創建索引的時機

到這裏咱們已經學會了創建索引,那麼咱們須要在什麼狀況下創建索引呢?通常來講,在WHERE和JOIN中出現的列須要創建索引,但也不徹底如此,由於MySQL只對<,<=,=,>,>=,BETWEEN,IN,以及某些時候的LIKE纔會使用索引。例如:

 
1
SELECT t.Name FROM mytable t LEFT JOIN mytable m ON t.Name=m.username WHERE m.age=20 AND m.city='鄭州'

此時就須要對city和age創建索引,因爲mytable表的userame也出如今了JOIN子句中,也有對它創建索引的必要。

剛纔提到只有某些時候的LIKE才需創建索引。由於在以通配符%和_開頭做查詢時,MySQL不會使用索引。例以下句會使用索引:

下句就不會使用:

 
1
SELECT * FROM mytable WHEREt Name like'%admin'

所以,在使用LIKE時應注意以上的區別。



1.5 索引實戰(摘抄)

人們在使用SQL時每每會陷入一個誤區,即太關注於所得的結果是否正確,而忽略了不一樣的實現方法之間可能存在的性能差別,

這種性能差別在大型的或是複雜的數據庫環境中(如聯機事務處理OLTP或決策支持系統DSS)中表現得尤其明顯。

筆者在工做實踐中發現,不良的SQL每每來自於不恰當的索引設計不充份的鏈接條件不可優化的where子句

在對它們進行適當的優化後,其運行速度有了明顯地提升!

下面我將從這三個方面分別進行總結:

爲了更直觀地說明問題,全部實例中的SQL運行時間均通過測試,不超過1秒的均表示爲(< 1秒)。

 

1、不合理的索引設計----

例:表record620000行,試看在不一樣的索引下,下面幾個 SQL的運行狀況:

---- 1.date上建有一非個羣集索引

---- 2.date上的一個羣集索引

 
1
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (14秒)
2
3
select date,sum(amount) from record group by date(28秒)
4
5
select count(*) from record where date >'19990901' and place in ('BJ','SH')(14秒)
6
7
---- 分析:---- 在羣集索引下,數據在物理上按順序在數據頁上,重複值也排列在一塊兒,於是在範圍查找時,能夠先找到這個範圍的起末點,且只在這個範圍內掃描數據頁,避免了大範圍掃描,提升了查詢速度。

---- 3.placedateamount上的組合索引

---- 4.dateplaceamount上的組合索引

 
1
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000(< 1秒)
2
3
select date,sum(amount) from record group by date(11秒)
4
5
select count(*) from record where date >'19990901' and place in ('BJ','SH')(< 1秒)
6
7
---- 分析:---- 這是一個合理的組合索引。它將date做爲前導列,使每一個SQL均可以利用索引,而且在第一和第三個SQL中造成了索引覆蓋,於是性能達到了最優。

---- 5.總結:----

缺省狀況下創建的索引是非羣集索引,但有時它並非最佳的;合理的索引設計要創建在對各類查詢的分析和預測上。

通常來講:

.有大量重複值、且常常有範圍查詢(between, >,< >=,< =)和order bygroup by發生的列,可考慮創建羣集索引;

.常常同時存取多列,且每列都含有重複值可考慮創建組合索引;

.組合索引要儘可能使關鍵查詢造成索引覆蓋,其前導列必定是使用最頻繁的列。

 

2、不充份的鏈接條件:

例:表card7896行,在card_no上有一個非彙集索引,表account191122行,在account_no上有一個非彙集索引,試看在不一樣的錶鏈接條件下,兩個SQL的執行狀況:

---- 分析:---- 

  • 在第一個鏈接條件下,最佳查詢方案是將account做外層表,card做內層表,利用card上的索引,其I/O次數可由如下公式估算爲:外層表account上的22541+(外層表account191122*內層表card上對應外層表第一行所要查找的3頁)=595907I/O
  • 在第二個鏈接條件下,最佳查詢方案是將card做外層表,account做內層表,利用account上的索引,其I/O次數可由如下公式估算爲:外層表card上的1944+(外層表card7896*內層表account上對應外層表每一行所要查找的4頁)= 33528I/O

可見,只有充份的鏈接條件,真正的最佳方案纔會被執行。

總結:

1.多表操做在被實際執行前,查詢優化器會根據鏈接條件,列出幾組可能的鏈接方案並從中找出系統開銷最小的最佳方案。鏈接條件要充份考慮帶有索引的表、行數多的表;內外表的選擇可由公式:外層表中的匹配行數*內層表中每一次查找的次數肯定,乘積最小爲最佳方案。

2.查看執行方案的方法-- set showplanon,打開showplan選項,就能夠看到鏈接順序、使用何種索引的信息;想看更詳細的信息,需用sa角色執行dbcc(3604,310,302)

 

3、不可優化的where子句

1.例:下列SQL條件語句中的列都建有恰當的索引,但執行速度卻很是慢:

 
1
select * from record where substring(card_no,1,4)='5378'(13秒)
2
3
select * from record where amount/30< 1000(11秒)
4
5
select * from record where convert(char(10),date,112)='19991201'(10秒)
6
7
分析:
8
9
where子句中對列的任何操做結果都是在SQL運行時逐列計算獲得的,所以它不得不進行表搜索,而沒有使用該列上面的索引;
10
11
若是這些結果在查詢編譯時就能獲得,那麼就能夠被SQL優化器優化,使用索引,避免表搜索,所以將SQL重寫成下面這樣:
12
13
select * from record where card_no like'5378%'(< 1秒)
14
15
select * from record where amount< 1000*30(< 1秒)
16
17
select * from record where date= '1999/12/01'(< 1秒)

你會發現SQL明顯快起來!

2.例:表stuff200000行,id_no上有非羣集索引,請看下面這個SQL

 
1
select count(*) from stuff where id_no in('0','1')(23秒)
2
3
分析:----
4
5
where條件中的'in'在邏輯上至關於'or',因此語法分析器會將in ('0','1')轉化爲id_no ='0' or id_no='1'來執行。
6
7
咱們指望它會根據每一個or子句分別查找,再將結果相加,這樣能夠利用id_no上的索引;
8
9
但實際上(根據showplan),它卻採用了"OR策略",即先取出知足每一個or子句的行,存入臨時數據庫的工做表中,再創建惟一索引以去掉重複行,最後從這個臨時表中計算結果。所以,實際過程沒有利用id_no上索引,而且完成時間還要受tempdb數據庫性能的影響。
10
11
實踐證實,表的行數越多,工做表的性能就越差,當stuff有620000行時,執行時間竟達到220秒!還不如將or子句分開:
12
13
select count(*) from stuff where id_no='0' select count(*) from stuff where id_no='1'
14
15
獲得兩個結果,再做一次加法合算。由於每句都使用了索引,執行時間只有3秒,在620000行下,時間也只有4秒。
16
17
或者,用更好的方法,寫一個簡單的存儲過程:
18
19
create proc count_stuff asdeclare @a intdeclare @b intdeclare @c intdeclare @d char(10)beginselect @a=count(*) from stuff where id_no='0'select @b=count(*) from stuff where id_no='1'endselect @c=@a+@bselect @d=convert(char(10),@c)print @d
20
21
直接算出結果,執行時間同上面同樣快!

 

---- 總結:---- 

可見,所謂優化即where子句利用了索引,不可優化即發生了表掃描或額外開銷。

1.任何對列的操做都將致使表掃描,它包括數據庫函數、計算表達式等等,查詢時要儘量將操做移至等號右邊

2.inor子句常會使用工做表,使索引失效;若是不產生大量重複值,能夠考慮把子句拆開;拆開的子句中應該包含索引。

3.善於使用存儲過程,它使SQL變得更加靈活和高效

從以上這些例子能夠看出,SQL優化的實質就是在結果正確的前提下,用優化器能夠識別的語句,充份利用索引,減小表掃描的I/O次數,儘可能避免表搜索的發生。其實SQL的性能優化是一個複雜的過程,上述這些只是在應用層次的一種體現,深刻研究還會涉及數據庫層的資源配置、網絡層的流量控制以及操做系統層的整體設計。


部分引用地址:http://blog.csdn.net/gprime/article/details/1687930