sqlite索引的原理


引言

這篇文章,里面講到對於一個41G大小、包含百萬條記錄的數據庫進行查詢操作,如果利用了索引,可以把操作耗時從37s降到0.2s。
那么什么是索引呢?利用索引可以加快數據庫查詢操作的原理是什么呢?

索引的基本原理

數據庫提供了一種持久化的數據存儲方式,從數據庫中查詢數據庫是一個基本的操作,查詢操作的效率是很重要的。
對於查詢操作來說,如果被查詢的數據已某種方式組織起來,那么查詢操作的效率會極大提高。
在數據庫中,一條記錄會有很多列。如果把這些記錄按照列Col1以某種數據結構組織起來,那么列Col2一定是亂序的。
因此,數據庫在原始數據之外,維護了滿足特定查找算法的數據結構,指向原始數據,稱之為索引
舉例來說,在下面的圖中,數據庫有兩列Col1、Col2。在存儲時,按照列Col1組織各行,比如Col1已二叉樹方式組織。如果查找col1中的某一個值,利用二叉樹進行二分查找,不需要遍歷整個數據庫。
這樣一來列Col2就是亂序的。為了解決這個問題,為Col2建立了索引,即把Col2也按照某種數據結構(這里是二叉樹)組織起來。這樣子查找列Col2時只需要進行二分查找即可。

索引的實現

由於數據庫是存儲在磁盤上的,因此實現索引用的數據結構會存儲在磁盤上。磁盤的IO是需要注意的問題。

  1. 二叉樹
    二叉樹是一種經典的數據結構,但是並不適合進行數據庫索引。
    原因在於二叉樹中每一個節點的度只有2,樹的深度較高。在存儲時,一般一個節點需要一次磁盤IO,樹的深度較高,查詢一個數據需要的磁盤IO次數越高,查找需要的時間越長。
  2. B樹
    B樹是二叉樹的變種,主要區別在於每一個節點的度可以大於2,即每一個節點可以分很多叉,大大降低了樹的深度。

    • 每條數據表示為[key,data]
    • 每個非葉子節點有(n-1)條數據n個指針組成
    • 所有葉節點具有相同的深度,等於樹高h
    • 指針指向節點的key大於左邊的記錄小於右邊記錄

    上面這些特點使得B+樹的深度大大降低,並且實現了對數據的有序組織。

  3. B+樹

    B+樹是對B樹的擴展,特點在於非葉子節點不存儲data,只存儲key。如果每一個節點的大小固定(如4k,正如在sqlite中那樣),那么可以進一步提高內部節點的度,降低樹的深度。

    • 非葉子節點只存儲key,葉子節點不存儲指針
    • 每一個節點大小固定,需要一次讀磁盤操作(page)
  4. 順序訪問指針的B+樹

    對B+樹做了一點改變,每一個葉子節點增加一個指向相鄰葉子節點的指針,這樣子可以提高區間訪問的性能。

    如圖,訪問key在15到30的data。

    • 如果沒有水平的指針
      B+樹查找找到key=15的data,在同一個塊中找到key=18的data。然后進行第二次B+查找,找到key=20的data,在同一個塊中找到key=30的data。
    • 有水平的指針
      B+樹查找找到key=15的data,查找同一個塊的內容,或沿着水平指針依次向右遍歷。

Sqlite中數據存儲方式

  • 表(table)和索引(Index)都是帶順序訪問指針的B+樹
  • table對應的B+樹中,key是rowid,data是這一行其他列數據(sqlite為每一行分配了一個rowid)
  • index對應的B+樹種,key是需要索引的列,data是rowid

根據索引查找數據時,分兩步

  1. 根據索引找到rowid(第一次B+樹查找)
  2. 根據rowid查找其他列的數據(第二次B+樹查找)

通過兩次B+樹查找避免了一次全表掃描。

1. 對某一行或某幾行添加PRIMARY KEY或UNIQUE約束,那么數據庫會自動為這些列創建索引 2. 指定某一列為INTEGER PRIMARY KEY,那么這一列和rowid被指定為同一列。即可以通過rowid來獲取,也可以通過列名來獲取。 

一個例子

下面是一個數據庫中一個表的統計信息,通過sqlite3_analyzer工具得到。

可以看到表中一共有3651條記錄,B樹的深度只有2,有33個葉子節點,1個非葉子節點。因此最多只需要2次磁盤IO就可以根據rowid找到一行的數據。

利用索引提高查找效率

比如我們有這么一個表

  1. benchmark
    查詢語句如下

    SELECT price FROM fruitsforsale WHERE fruit=‘Peach’ 

    由於沒有索引,因此不得不做一次全表掃描。通過順序訪問指針遍歷各個記錄(record),比較fruit這一列和‘peatch’是否一致,如果一致,返回這一行的price列的值。

  2. 對‘fruit’列加索引
    如下,運行同樣的語句,可以根據索引找到目標列對應的rowid為4,然后根據rowid找到對應行,從而選出price。通過兩次B+樹查找避免了全表查找。這也是最簡單的情況 

  3. 多條索引命中
    建立索引時,不要求索引是uique的,即索引表中的key可以是一樣的。
    如下圖,索引表中有orange兩條記錄,找到第一條記錄時,根據順序訪問指針可以輕易找到下一條索引,避免另一次B+樹查找。(rowid=1和rowid=23可能位於兩個不同的葉子節點中)
    即這個查找索引的過程,可以通過一次B+樹查和一次next操作完成,而next操作是很快的。

  4. 利用索引加快搜索和排序
    在大多情況下,我們需要同時進行查找和排序操作,這時如果建立適當的索引,可以提高查找效率。
    比如下面表中對fruit和state兩列做了索引,運行下面的sql語句時,就不需要進行排序操作了,因為索引表是帶有順序的。

    SELECT price FROM fruitforsale WHERE fruit='Orange' ORDER BY state 

解釋引言中問題

在sqlite中有一個命令叫做explain query plan,可以查看sqlite是如何執行查找操作的。下面的數據庫語句不是引言中的查詢語句,原理一樣

  • 37s的操作(沒有用索引)

  • 0.2s的操作(用了索引)

注意detail列。不用索引時,使用的是“SCAN”這個詞,即全表掃描。使用索引時,使用的是“SEARCH”這個詞。
對於一個41G的表來說,進行全表掃描的代價顯然是很大的。

參考鏈接

    1. 淺談算法和數據結構: 十 平衡查找樹之B樹
    2. MySQL索引背后的數據結構及算法原理
    3. Query Planning(這篇是sqlite關於索引的文檔)
    4. EXPLAIN QUERY PLAN
    5. MySQL單表百萬數據記錄分頁性能優化


免責聲明!

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



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