1. 程式人生 > 實用技巧 >sql server索引入門

sql server索引入門

索引原理

  索引的目的在於提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然後定位到該章下的一個小節,然後找到頁數。相似的例子還有:查字典,查火車車次,飛機航班等,下面內容看不懂的同學也沒關係,能明白這個目錄的道理就行了。 那麼你想,書的目錄佔不佔頁數,這個頁是不是也要存到硬盤裡面,也佔用硬碟空間。你再想,你在沒有資料的情況下先建索引或者說目錄快,還是已經存在好多的資料了,然後再去建索引,哪個快,肯定是沒有資料的時候快,因為如果已經有了很多資料了,你再去根據這些資料建索引,是不是要將資料全部遍歷一遍,然後根據資料建立索引。你再想,索引建立好之後再新增資料快,還是沒有索引的時候新增資料快,索引是用來幹什麼的,是用來加速查詢的,那對你寫入資料會有什麼影響,肯定是慢一些了,因為你但凡加入一些新的資料,都需要把索引或者說書的目錄重新做一個,所以索引雖然會加快查詢,但是會降低寫入的效率。索引類似一本書籍的目錄

索引影響

​ 1、在表中有大量資料的前提下,建立索引速度會很慢

​ 2、在索引建立完畢後,對錶的查詢效能會發幅度提升,但是寫效能會降低

本質都是:通過不斷地縮小想要獲取資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查詢方式來鎖定資料。

但是資料庫就不想是一本書籍的目錄一樣簡單,因為資料庫中的查詢種類有很多:

  等值查詢(=)、

  範圍查詢(>、<、between、in)、

  模糊查詢(like)、

  並集查詢(or)等等

資料庫在讀取資料的時候必然要進行IO的操作從磁碟中讀取資料然後把資料放到記憶體中。IO操作是非常浪費時間的,索引的出現就是讓計算機系統進行少量的IO操作。計算機系統也有對IO操作的優化,一次IO可以讀取一頁的資料一般為4k或8k。,當一次IO時,不光把當前磁碟地址的資料,而是把相鄰的資料也都讀取到記憶體緩衝區內。


索引的資料結構

索引怎麼做到減少IO,加速查詢的。任何一種資料結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種資料結構能夠做些什麼,其實很簡單,那就是:每次查詢資料時把磁碟IO次數控制在一個很小的數量級,最好是常數數量級。那麼我們就想到如果一個高度可控的多路搜尋樹是否能滿足需求呢?就這樣,b+樹應運而生(B+樹是通過二叉查詢樹,再由平衡二叉樹,B樹演化而來)

如上圖,是一顆b+樹,最上層是樹根,中間的是樹枝,最下面是葉子節點,關於b+樹的定義可以參見B+樹,這裡只說一些重點,淺藍色的塊我們稱之為一個磁碟塊或者叫做一個block塊,這是作業系統一次IO往記憶體中讀的內容,一個塊對應四個扇區,可以看到每個磁碟塊包含幾個資料項(深藍色所示,一個磁碟塊裡面包含多少資料,一個深藍色的塊表示一個數據,其實不是資料,後面有解釋)和指標(黃色所示,看最上面一個,p1表示比上面深藍色的那個17小的資料的位置在哪,看它指標指向的左邊那個塊,裡面的資料都比17小,p2指向的是比17大比35小的磁碟塊),如磁碟塊1包含資料項17和35,包含指標P1、P2、P3,P1表示小於17的磁碟塊,P2表示在17和35之間的磁碟塊,P3表示大於35的磁碟塊。真實的資料存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不儲存真實的資料,只儲存指引搜尋方向的資料項,如17、35並不真實存在於資料表中。

b+樹的查詢過程
如圖所示,如果要查詢資料項29,那麼首先會把磁碟塊1由磁碟載入到記憶體,此時發生一次IO,在記憶體中用二分查詢確定29在17和35之間,鎖定磁碟塊1的P2指標,記憶體時間因為非常短(相比磁碟的IO)可以忽略不計,通過磁碟塊1的P2指標的磁碟地址把磁碟塊3由磁碟載入到記憶體,發生第二次IO,29在26和30之間,鎖定磁碟塊3的P2指標,通過指標載入磁碟塊8到記憶體,發生第三次IO,同時記憶體中做二分查詢找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的資料,如果上百萬的資料查詢只需要三次IO,效能提高將是巨大的,如果沒有索引,每個資料項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。除了葉子節點,其他的樹根啊樹枝啊儲存的就是資料的索引,他們是為你建立這種資料之間的關係而存在的。 3次IO代表了在記憶體中快取了3次資料,

b+樹的查詢過程
如圖所示,如果要查詢資料項29,那麼首先會把磁碟塊1由磁碟載入到記憶體,此時發生一次IO,在記憶體中用二分查詢確定29在17和35之間,鎖定磁碟塊1的P2指標,記憶體時間因為非常短(相比磁碟的IO)可以忽略不計,通過磁碟塊1的P2指標的磁碟地址把磁碟塊3由磁碟載入到記憶體,發生第二次IO,29在26和30之間,鎖定磁碟塊3的P2指標,通過指標載入磁碟塊8到記憶體,發生第三次IO,同時記憶體中做二分查詢找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的資料,如果上百萬的資料查詢只需要三次IO,效能提高將是巨大的,如果沒有索引,每個資料項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。除了葉子節點,其他的樹根啊樹枝啊儲存的就是資料的索引,他們是為你建立這種資料之間的關係而存在的。

b+樹性質
1.索引欄位要儘量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h或者說層級,這個高度或者層級就是你每次查詢資料的IO次數,假設當前資料表的資料為N,每個磁碟塊的資料項的數量是m,則有h=㏒(m+1)N,當資料量N一定的情況下,m越大,h越小;而m = 磁碟塊的大小 / 資料項的大小,磁碟塊的大小也就是一個數據頁的大小,是固定的,如果資料項佔的空間越小,資料項的數量越多,樹的高度越低。這就是為什麼每個資料項,即索引欄位要儘量的小,比如int佔4位元組,要比bigint8位元組少一半。這也是為什麼b+樹要求把真實的資料放到葉子節點而不是內層節點,一旦放到內層節點,磁碟塊的資料項會大幅度下降,導致樹增高。當資料項等於1時將會退化成線性表。

​ 所以我們需要將樹建的越低越好,因為每個磁碟塊的大小是一定的,那麼意味著我們單個數據庫裡面的單個數據的大小越大越好還是越小越好,你想啊,你現在葉子節點的磁碟塊,兩個資料就沾滿了,你資料要是更大的話,你這一個磁碟塊就只能放一個數據了親,這樣隨著你資料量的增大,你的樹就越高啊,我們應該想辦法讓樹的層數低下來,效率才高啊,所以我們應該讓每個資料的大小盡可能的小,那就意味著,你每個磁碟塊存的資料就越多,你樹的層級就越少啊,樹就越低啊,對不對。並且資料的數量越大,你需要的磁碟塊越多,磁碟塊越多,你需要的樹的層級就越高,所以我們應該儘可能的用更少的磁碟塊來裝更多的資料項,這樣樹的高度才能降下來,怎麼才能裝更多的資料項啊,當然是你的資料項越小,你的磁碟塊盛放的資料量就越多了,所以如果一張表中有很多的欄位,我們應該用什麼欄位來建立索引啊,如果你有id欄位、name欄位、描述資訊欄位等等的,你應該用哪個來建立索引啊,當然是id欄位了,你想想對不對,因為id是個數字,佔用空間最少啊。

​ 2.索引的最左匹配特性:簡單來說就是你的資料來了以後,從資料塊的左邊開始匹配,在匹配右邊的,知道這句話就行啦~~~~,我們繼續學下面的內容。當b+樹的資料項是複合的資料結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜尋樹的,比如當(張三,20,F)這樣的資料來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的資料;但當(20,F)這樣的沒有name的資料來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜尋樹的時候name就是第一個比較因子,必須要先根據name來搜尋才能知道下一步去哪裡查詢。比如當(張三,F)這樣的資料來檢索時,b+樹可以用name來指定搜尋方向,但下一個欄位age的缺失,所以只能把名字等於張三的資料都找到,然後再匹配性別是F的資料了, 這個是非常重要的性質,即索引的最左匹配特性。

聚集索引與輔助索引

聚集索引是什麼呢,其實就是我們說的那個主鍵,之前我們說Innodb儲存引擎的表,必須有一個主鍵,還記得為什麼嗎,我們說過的...不記得了吧,看下面

    還記得MyISAM儲存引擎在建立表的時候會在硬碟上生成哪些檔案嗎,是不是有三個.frm.MYD.MYI結尾的三個檔案,frm結尾的是表結構,MYD結尾的是資料檔案,MYI結尾的就是索引檔案,也就是說索引也是存在硬碟上的,那InnoDB引擎呢,建立一個表,在硬碟上會生成.frm.idb結尾的兩個檔案,那索引的呢,難道InnoDB就用不了索引嗎?怎麼可能?之前咱們有沒有建立過索引啊,primary key、unique key是不是都叫做索引啊,但是索引那個檔案去哪了呢,索引是不可能在表結構.frm(存什麼欄位什麼型別這些東西)的檔案中,那就只剩下.idb結尾的資料檔案了,索引就在這裡面,InnoDB引擎的表,它的索引和資料都在同一個檔案裡面,所以我一直強調,使用InnoDB儲存引擎的時候,每建一個表,就需要給一個主鍵,是因為這個主鍵是InnoDB儲存引擎的.idb檔案來組織儲存資料的依據或者說方式,也就是說InnoDB儲存引擎在儲存資料的時候預設就按照索引的那種樹形結構來幫你存。這種索引,我們就稱為聚集索引,也就是在聚集資料組織資料的時候,就用這種索引。InnoDB這麼做就是為了加速查詢效率,因為你經常會遇到基於主鍵來查詢資料的情況,並且通常我們把id欄位作為主鍵,第一點是因為id佔用的資料空間不大,第二點是你經常會用到id來查資料。如果你的表有兩個欄位,一個id一個name,id為主鍵,當你查詢的時候如果where後面的條件是name=多少多少,那麼你就沒有用到主鍵給你帶來的加速查詢的效果(需要主鍵之外的輔助索引),如果你用where id=多少多少,就會按照我們剛才上面說的哪種樹形結構來給你找尋資料了(當然不僅僅有這種樹形結構的資料結構型別),能夠快速的幫你定位到資料塊。這種聚集索引的特點是它會以id欄位作為依據,去建立樹形結構,但是葉子節點存的是你表中的一條完整記錄,一條完整的資料。記住這一點昂,一會將輔助索引的時候,和這個內容有關係,會講到一個回表的概念。

  在資料庫中,B+樹的高度一般都在24層,這也就是說查詢某一個鍵值的行記錄時最多隻需要2到4次IO,這倒不錯。因為當前一般的機械硬碟每秒至少可以做100次IO,24次的IO意味著查詢時間只需要0.02~0.04秒。

  資料庫中的B+樹索引可以分為聚集索引(clustered index)和輔助索引(secondary index),

  聚集索引與輔助索引相同的是:不管是聚集索引還是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放著所有的資料。

  聚集索引與輔助索引不同的是:葉子結點存放的是否是一整行的資訊

聚集索引

#InnoDB儲存引擎表示索引組織表,即表中資料按照主鍵順序存放。而聚集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即為整張表的行記錄資料,也將聚集索引的葉子結點稱為資料頁。聚集索引的這個特性決定了索引組織表中資料也是索引的一部分。同B+樹資料結構一樣,每個資料頁都通過一個雙向連結串列來進行連結。

#如果未定義主鍵,MySQL取第一個唯一索引(unique)而且只含非空列(NOT NULL)作為主鍵,InnoDB使用它作為聚簇索引。

#如果沒有這樣的列,InnoDB就自己產生一個這樣的ID值,它有六個位元組,而且是隱藏的,使其作為聚簇索引。

#由於實際的資料頁只能按照一棵B+樹進行排序,因此每張表只能擁有一個聚集索引。在多少情況下,查詢優化器傾向於採用聚集索引。因為聚集索引能夠在B+樹索引的葉子節點上直接找到資料。此外由於定義了資料的邏輯順序,聚集索引能夠特別快地訪問針對範圍值得查詢

它對主鍵的排序查詢和範圍查詢速度非常快,葉子節點的資料就是使用者所要查詢的資料。如使用者需要查詢一張表,查詢最後的10位使用者資訊,由於B+樹索引是雙向連結串列,所以使用者可以快速找到最後一個數據頁,並取出10條記錄

#參照第六小結測試索引的準備階段來創建出表s1 
mysql> desc s1; #最開始沒有主鍵
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> explain select * from s1 order by id desc limit 10; #Using filesort,需要二次排序這裡可以看一下rows掃描行數 Extra:執行情況的描述和說明
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2633472 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
1 row in set, 1 warning (0.11 sec)

mysql> alter table s1 add primary key(id); #新增主鍵
Query OK, 0 rows affected (13.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 order by id desc limit 10; #基於主鍵的聚集索引在建立完畢後就已經完成了排序,無需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.04 sec)

聚集索引的好處之二:範圍查詢(range query),即如果要查詢主鍵某一範圍內的資料,通過葉子節點的上層中間節點就可以得到頁的範圍,之後直接讀取資料頁即可

mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.12 sec)

mysql> explain select * from s1 where id > 1 and id < 1000000; #沒有聚集索引,預估需要檢索的rows數如下,explain就是預估一下你的sql的執行效率
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2690100 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 where id > 1 and id < 1000000; #有聚集索引,預估需要檢索的rows數如下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 1343355 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.09 sec)

輔助索引

 就是我們在查詢的時候,where後面需要寫id之外的其他欄位名稱來進行查詢,比如說是where name=xx,沒法用到主鍵索引的效率,怎麼辦,就需要我們新增輔助索引了,給name新增一個輔助索引。

    表中除了聚集索引外其他索引都是輔助索引(Secondary Index,也稱為非聚集索引)(unique key啊、index key啊),與聚集索引的區別是:輔助索引的葉子節點不包含行記錄的全部資料。

    葉子節點存放的是對應的那條資料的主鍵欄位的值,除了包含鍵值以外,每個葉子節點中的索引行中還包含一個書籤(bookmark),其實這個書籤你可以理解為是一個{'name欄位',name的值,主鍵id值}的這麼一個數據。該書籤用來告訴InnoDB儲存引擎去哪裡可以找到與索引相對應的行資料。如果我們select 後面要的是name,我們直接就可以在輔助索引的葉子節點找到對應的name值,比如:select name from tb1 where name='xx';這個xx值你直接就在輔助索引的葉子節點就能找到,這種我們也可以稱為覆蓋索引。如果你select後面的欄位不是name,例如:select age from tb1 where name='xx';也就是說,我通過輔助索引的葉子節點不能直接拿到age的值,需要通過輔助索引的葉子節點中儲存的主鍵id的值再去通過聚集索引來找到完整的一條記錄,然後從這個記錄裡面拿出age的值,這種操作有時候也成為回表操作,就是從頭再回去查一遍,這種的查詢效率也很高,但是比覆蓋索引低一些,再說一下昂,再輔助索引的葉子節點就能找到你想找的資料可稱為覆蓋索引。