INNODB索引與算法


在之前的博文中簡單提到了索引的分類與索引的可選擇性查看:Click HERE

這片博客主要包含內容:索引組織表,索引算法B+樹簡單介紹

索引組織表

在innodb存儲引擎中,表都是根據主鍵順序組織存放的,使用這種存儲方式的表就叫做索引組織表(index  organized table 簡稱IOT表)。

在innodb存儲引擎中,每張表都有個主鍵(primary key),如果創建表是沒有顯式的定義主鍵,則INNODB存儲引擎會按如下方式選擇或創建主鍵。

  • 首先判斷表中是否有非空唯一索引,如果有,則該列即主鍵。
  • 如果不符合上述條件,INNODB存儲引擎會自動創建一個6字節大小的指針。

當表中有多個非空的唯一索引時,INNODB存儲引擎將選擇建表時第一個定義的非空唯一索引為主鍵。這里需要說明的是,主鍵的選擇根據的是定義索引的順序而不是建表時列的順序

CREATE TABLE t1 (
    a INT NOT NULL,
    b INT NULL,
    c INT NOT NULL,
    d INT NOT NULL,
    UNIQUE KEY (b),
    UNIQUE KEY (d),
    UNIQUE KEY (c)
);

insert into t1 select 1,2,3,4;
insert into t1 select 5,6,7,8;
insert into t1 select 9,10,11,12;
#上面創建了表, 並且創建了索引,注意索引的順序,然后插入了數據。
#使用字段_rowid可以查看表中的主鍵,_rowid只可以用來查看單列索引的主鍵。 #注意主鍵非空的唯一索引 mysql
> select a, b, c, d, _rowid from t1; +---+------+----+----+--------+ | a | b | c | d | _rowid | +---+------+----+----+--------+ | 1 | 2 | 3 | 4 | 4 | | 5 | 6 | 7 | 8 | 8 | | 9 | 10 | 11 | 12 | 12 | +---+------+----+----+--------+ 3 rows in set (0.00 sec) mysql>

這里提到INNODB存儲引擎是索引組織表?那么在INNODB的內部是如何使用主鍵將表組織起來的呢?

INNODB存儲引擎概述

存儲引擎的索引分類(安裝索引的內部實現不同):

  • B+樹索引
  • 哈希索引(INNODB是自適應哈希索引)
  • 全文索引

B+樹索引就是傳統意義上的索引,也就是上面提到過那種類型的索引,這是目前關系型數據庫系統中查找最為常用和最為有效的索引。B+樹索引的構造類似於二叉樹。

哈希索引,INNODB存儲引擎是自適應的,INNODB存儲引擎會根據表的使用情況自動為表生成哈希索引,不能認為干預是否在一張表中生成哈希索引。

全文索引,會在后面講述。

B+樹索引

B+樹索引並不能找到一個給定鍵值的具體的行,只能找到這個記錄所在的數據頁。然后把這個數據頁讀到內存(innodb_buffer_pool_size)中,然后找到其中的記錄。

一個問題:把對應的頁讀到內存中,那么是如何在這個頁中找到對應的記錄呢?

上面提到到INNODB時索引組織表,是按照主鍵的順序排放的,這里按照順序是按照邏輯順序存放的,每個頁之間通過雙向鏈表鏈接。但是數據在一個物理頁上卻是物理有序的。也就是說,讀到內存中的這個頁是物理有序,這時候我們要在這個有序的隊列中找到想要的記錄,就會很方便了。但是這里具體是怎么查找的?INNODB使用的是二分查找法。

二分查找法

二分查找法也稱折半查找法,用來查找一組有序的記錄數組中某一記錄,其基本思想是:將記錄按有序化排列,在查找過程中采用跳躍式方式查找,即先以有序數列的中點位置為比較對象,如果要找的元素值小於該元素中點元素,則將待查序列縮小為左半部分,否則為右半部分。通過一次比較,將查找區間縮小一半。

通過一個例子說明二分查找法:
一個有序數列如下:
5  10  19  21  23   25    27  31   33
尋找31這個數字:
第一次找到中間數23,
31>23, 所以第二次要在后半段查找也就是23 25 27  31  33中查找。
然后重復上面的步驟。

注意:二分查找法通過降低比較的次數,也就是降低的是cpu的使用。
#!/usr/bin/env python
#*-* coding:utf -8 *-*
#二分法查找數值

import sys
import random

def UnsortList():         ###如果沒有指定參數,隨機生成一個序列
    list = []
    long = random.randint(0,100)
    for i in range(long):
        list.append(random.randint(0,10000))
    return list

def BinarySearch(list, mark, low=0,uplow=None):   #二分法查找
    if not uplow:
        uplow = len(list) -1
    if low == uplow:
        assert mark == list[uplow]
        return uplow
    else:
        mid = (low + uplow) // 2
        if mark > list[mid]:
            return BinarySearch(list, mark,mid+1,uplow)
        else:
            return  BinarySearch(list,mark,low,uplow=mid)    

def SuijiMark(list):            ###在列表中隨機挑選一個要查找的數據
    l = len(list)
    mark = list[random.randint(0,l) - 1]
    return mark

def main():           ####主函數
    Ulist = []
    print "1:隨機產生列表,驗證二分法"
    print "2:用戶自己輸入數值生成列表,驗證二分法"
    answer = input("請輸入對應的數字: ")
    if answer == 1:
        Ulist = UnsortList()
        mark = SuijiMark(Ulist)
        print "The list is %s" % Ulist
        print "The mark is %s" % mark
        print "The len of the list is %s " % len(Ulist)
    elif answer == 2:
        lang = input("請輸入列表長度: ")      ##根據輸入的數值,組成列表
        for i in range(lang):          
            Ulist.append(input("請輸入列表第%d個值:" % (i + 1)))
        mark = SuijiMark(Ulist)
        print "the list is %s" % Ulist
        print "the mark is %s" % mark
    else:
        print "請輸入合法的數字"
    Ulist.sort()
    index = BinarySearch(Ulist, mark)
    print "The index %s is %s" % (index, mark)


if __name__ == "__main__":
    main()
用python寫的二分查找法,python2.6版本

數據頁讀到內存中之后,INNODB就是根據二分查找法在指定的數據頁中,查找對應的記錄。

二叉樹

在介紹B+樹前,需要先了解一下二叉樹。B+樹是通過二叉樹,再由平衡二叉樹,B樹演化而來的。

一個簡單的二叉樹如下圖:

在一個二叉樹中,每個節點最多只能有兩個分支,每個節點與其子節點又構成一棵二叉樹。二叉樹中的左子節點總是小於根節點,右子節點總是大於根節點。因此二叉樹中,左子節點<根節點<右子節點。

二叉樹有三種遍歷方法,中序遍歷,前序遍歷,后序遍歷。

上面二叉樹中序遍歷的結果為: 2 3 5 6 7 8.

在二叉樹中查找一個數據時,先與根節點比較,若是大於根節點,則在右子樹繼續比較,若是小於根節點,則選擇左子樹繼續比較。

對上圖中這棵二叉樹進行查找,如查找鍵值為5的記錄,先找到根,其鍵值是6,6大於5,因此查找6的左子樹,找到3;而5大於3,再找到其右子樹;一共找了3次。如果按2、3、5、6、7、8的順序來找同樣需要3次。用同樣的方法再查找鍵值為8的這個記錄,這次用了3次查找,而順序查找需要6次。

順序查找需要的次數平均為:(1+2+3+4+5+6)/6=3.3次。

二叉查找法需要的平均次數: (1+2+2+3+3+3)/6=2.3次。

當數據量比較大時,這個差值就會比較高了。

二叉查找樹可以任意構造,同樣是2、3、5、6、7、8這六個數字,也可以按照下圖的方式建立二叉樹查找樹。

這樣的二叉樹的平均查找為:(1+2+3+4+5+5)/6=3.16,這個查找法和順序查找就差不多,顯然效率比較低。

因此若想最大性能地構造一棵二叉查找樹,需要這棵二叉查找樹是平衡的,從而引入了新的定義–平衡二叉樹,或稱為AVL樹。

平衡二叉樹的定義如下:首先符合二叉查找樹的定義,其次必須滿足任何節點的兩個字數的高度最大差為1.顯然,上圖不滿足平衡二叉樹的定義。平衡二叉樹的查找性能是比較高的,但不是最高的,只是接近最高性能。最好的性能需要建立一棵最優二叉樹,但是最優二叉樹的建立和維護需要大量的操作,因此,用戶一般只需要建立一棵平衡二叉樹即可。

平衡二叉樹的查詢速度的確很快,但是維護一棵平衡二叉樹的代價是非常大的。通常來說,需要1次或多次左旋和右旋來得到插入活更新后樹的平衡性。如下圖中需要多次旋轉的平衡二叉樹示例。(這個旋轉不太理解,但inside君的書中是這樣說的,先寫這里)

上圖列舉了向一棵平衡二叉樹插入一個新的節點后,平衡二叉樹需要做的旋轉操作。除了插入操作,還有更新和刪除操作,不過這和插入沒有本質的區別,都是通過左旋或右旋來完成的。因此對一棵平衡樹的維護是有一定開銷的,不過平衡二叉樹多用於內存結構對象中,因此維護的開銷相對較小。然后對於MySQL這種依靠磁盤來完成數據的存儲跟查詢工作的,無法使用這種平衡二叉樹

隨着數據庫中數據的增加,索引本身大小隨之增加,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高幾個數量級。可以想象一下一棵幾百萬節點的二叉樹的深度是多少?如果將這么大深度的一顆二叉樹放磁盤上,每讀取一個節點,需要一次磁盤的I/O讀取,整個查找的耗時顯然是不能夠接受的。那么如何減少查找過程中的I/O存取次數?

一種行之有效的解決方法是減少樹的深度,將二叉樹變為m叉樹(多路搜索樹),而B+Tree就是一種多路搜索樹

二叉樹的是一個數據結構中一個很重要的概念,這里只是一個簡單引用。

B+樹

B+樹和二叉樹、平衡二叉樹一樣,都是經典的數據結構。B+樹由B樹(就是B-樹)和索引順序訪問方法演化而來,但是在現實使用過程中幾乎已經沒有使用B樹的情況了。其實也可以理解為B+樹就是結合了平衡二叉樹+二分查找法的精華。

B+樹的定義在任何一本數據結構書中都能找到,其定義十分復雜,這里,精簡地對B+樹做個介紹:B+樹是為磁盤或其他直接存取輔助設備設計的一種平衡查找樹,在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點上,由各葉子節點指針進行連接。

其實理解B+Tree時,只需要理解其最重要的兩個特征即可【摘自博客: http://www.ywnds.com/?p=5300】:

第一,所有的關鍵字(可以理解為數據)都存儲在葉子節點(Leaf Page),非葉子節點(Index Page)並不存儲真正的數據,所有記錄節點都是按鍵值大小順序存放在同一層葉子節點上。

第二,所有的葉子節點增加一個指向相鄰葉子節點的指針,就形成了帶有順序訪問指針的B+Tree。InnoDB B+Tree結構是在經典B+Tree的基礎上進行了優化,增加了順序訪問指針,做這個優化的目的是為了提高區間訪問的性能。

如下圖,是一顆高度為2,每頁可存放4條記錄,扇出(fanout)為4,簡化版的B+Tree。扇出就是第一層指向第二層的四個箭頭。

所有記錄都在葉子節點上,並且是順序存放的,如果用戶從最左邊的葉子節點開始順序遍歷,可以得到所有鍵值的順序排序:5、10、15、20、25、30、50、55、60、65、75、80、85、90。另外需要注意的是,B+樹是無法根據某一個查找Key直接定位到value的。比如我們查找“30”這條記錄,其只能通過B+樹定位到“30”這條記錄在這個頁中,然后再通過二分查找法定位到具體的記錄,只不過二分查找法的速度很快,基本忽略這個查詢,但這個開銷本身是存在的。

那么怎么理解B+Tree這兩個重要特征呢?MySQL將每個節點的大小設置為一個頁的整數倍(原因下文會介紹),也就是在節點空間大小一定的情況下,每個節點可以存儲更多的內結點,這樣每個節點能索引的范圍更大更精確。所有的葉子節點使用指針鏈接的好處是可以進行區間訪問,比如上圖中,如果查找大於20而小於30的記錄,只需要找到節點20,就可以遍歷指針依次找到25、30;只需順着節點和指針順序遍歷就可以一次性訪問到所有數據節點,極大提到了區間查詢效率。如果沒有鏈接指針的話,就無法進行區間查找。這也是MySQL使用B+Tree作為索引存儲結構的重要原因。

局部性原理與磁盤預讀

MySQL為何將節點大小設置為頁的整數倍,這就需要理解磁盤的存儲原理。磁盤本身存取就比主存慢很多,在加上機械運動損耗(特別是普通的機械硬盤),磁盤的存取速度往往是主存的幾百萬分之一,為了盡量減少磁盤I/O,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存,預讀的長度一般為頁的整數倍。

這樣做的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用;程序運行期間所需要的數據通常比較集中。由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高I/O效率。

預讀的長度一般為頁(page)的整倍數,頁是計算機管理存儲器的邏輯塊,硬件及OS往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(許多OS中,
頁的大小通常為4K)。主存和磁盤以頁為單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始
位置並向后連續讀取一頁或幾頁載入內存中,然后異常返回,程序繼續運行。

MySQL巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現了讀取一個節點只需一次I/O。假設B+Tree的高度為h,一次檢索最多需要h-1I/O(根節點常駐內存),復雜度$O(h) = O(log_{M}N)$。實際應用場景中,M通常較大,常常超過100,因此樹的高度一般都比較小,通常不超過2-4層。

一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。

總結一下B+樹,B+樹是為磁盤或其他直接存取輔助設備設計的一種平衡查找樹。在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點上,由各葉子節點指針進行連接,也就可以利用二分查找法進行數據查找了,對於范圍查詢特別有優勢。另外,B+Tree屬於高扇區(fanout)性(大於100),簡單說就是上層節點指向下層節點的指針數多,每一個指針可以稱之為一個扇區。所以高扇區特性意味這棵樹不會太高(一般2-4層最多),對應IO操作次數的減少。

B+樹的操作(插入和刪除)

【站位】

B+樹索引

上面說的基本都是B+樹的數據結構及其一般操作,B+樹索引的本質是B+樹再數據庫中的實現。但是B+樹索引在數據庫中有一個特點就是高扇出,樹的高度一般是2~4層,這也就是說查找某一個記錄最多只需要2到4次IO。

數據庫中B+樹索引可以分為聚集索引和輔助索引。但是不管聚集索引還是輔助索引,其內部都是B+樹,即高度平衡的。不同的是,聚集索引葉子節點存放着所有的數據,輔助索引葉子節點存放的只是一個指向聚集索引的指針。

聚集索引

INNODB存儲引擎是索引組織表,即表中的數據按照主鍵順序存放。而聚集索引就是按照每張表的主鍵構造一棵B+樹,同時葉子節點中的存放的即為整張表的行記錄數據,也將聚集索引的葉子節點稱為數據頁。聚集索引的這個特性絕定了索引組織表中數據也是索引的一部分。同B+樹數據結構一樣,每個數據頁都通過一個雙向鏈表來連接。

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

聚集索引並不是物理上連續的,而是邏輯上連續的。數據頁都是通過雙向鏈表鏈接,頁按照主鍵的順序排序;另一個是每個頁中的記錄也是通過雙向鏈表進行維護的,
物理存儲上可以不按照主鍵存儲。

上面提到聚集索引對於主鍵的排序查找和范圍查找速度非常快,葉子節點的數據就是用戶所需要的數據。

mysql> select * from employees ignore index(pri) order by  emp_no  desc limit 10;          #禁用索引
+--------+------------+------------+--------------+--------+------------+
| emp_no | birth_date | first_name | last_name    | gender | hire_date  |
+--------+------------+------------+--------------+--------+------------+
| 499999 | 1958-05-01 | Sachin     | Tsukuda      | M      | 1997-11-30 |
| 499998 | 1956-09-05 | Patricia   | Breugel      | M      | 1993-10-13 |
| 499997 | 1961-08-03 | Berhard    | Lenart       | M      | 1986-04-21 |
| 499996 | 1953-03-07 | Zito       | Baaz         | M      | 1990-09-27 |
| 499995 | 1958-09-24 | Dekang     | Lichtner     | F      | 1993-01-12 |
| 499994 | 1952-02-26 | Navin      | Argence      | F      | 1990-04-24 |
| 499993 | 1963-06-04 | DeForest   | Mullainathan | M      | 1997-04-07 |
| 499992 | 1960-10-12 | Siamak     | Salverda     | F      | 1987-05-10 |
| 499991 | 1962-02-26 | Pohua      | Sichman      | F      | 1989-01-12 |
| 499990 | 1963-11-03 | Khaled     | Kohling      | M      | 1985-10-10 |
+--------+------------+------------+--------------+--------+------------+
10 rows in set (0.32 sec)

mysql> select * from employees  order by  emp_no  desc limit 10;                      #使用主鍵查詢
+--------+------------+------------+--------------+--------+------------+
| emp_no | birth_date | first_name | last_name    | gender | hire_date  |
+--------+------------+------------+--------------+--------+------------+
| 499999 | 1958-05-01 | Sachin     | Tsukuda      | M      | 1997-11-30 |
| 499998 | 1956-09-05 | Patricia   | Breugel      | M      | 1993-10-13 |
| 499997 | 1961-08-03 | Berhard    | Lenart       | M      | 1986-04-21 |
| 499996 | 1953-03-07 | Zito       | Baaz         | M      | 1990-09-27 |
| 499995 | 1958-09-24 | Dekang     | Lichtner     | F      | 1993-01-12 |
| 499994 | 1952-02-26 | Navin      | Argence      | F      | 1990-04-24 |
| 499993 | 1963-06-04 | DeForest   | Mullainathan | M      | 1997-04-07 |
| 499992 | 1960-10-12 | Siamak     | Salverda     | F      | 1987-05-10 |
| 499991 | 1962-02-26 | Pohua      | Sichman      | F      | 1989-01-12 |
| 499990 | 1963-11-03 | Khaled     | Kohling      | M      | 1985-10-10 |
+--------+------------+------------+--------------+--------+------------+
10 rows in set (0.00 sec)

#數據時間差可以看出還是有差別的,看一下這兩條查詢的計划任務。
mysql> explain select * from employees order by  emp_no  desc limit 10\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: employees
   partitions: NULL
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)
#使用order by時對記錄排序,但是在實際過程中並沒有進行所謂的filesort操作。
mysql> explain select * from employees ignore index(pri) order by  emp_no  desc limit 10\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: employees
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 299246
     filtered: 100.00
        Extra: Using filesort
1 row in set, 1 warning (0.02 sec)

#禁用主鍵(也就是聚集索引)看到這里使用了filesort排序操作
使用聚集索引排序查詢
mysql> explain select * from employees where emp_no > 125389 and emp_no < 314534;
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table     | partitions | type  | possible_keys | key     | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 149623 |   100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from employees ignore index(pri) where emp_no > 125389 and emp_no < 314534;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 299246 |    11.11 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

#返回的行數只是一個大概的數字,並不是一個確切的數字。這里看到第二個查詢執行了全表掃描。過濾率只有11.11%
使用聚集索引進行范圍查找

輔助索引

對於輔助索引(非聚集索引),葉子節點並不包含行記錄的全部數據。葉子節點除列包含鍵值以外,每個葉子節點中的索引行中還包含了一個書簽。該書簽用來告訴INNODB存儲引擎哪里可以找到與索引相對應的行數據。由於INNODB存儲引擎是索引組織表,因此INNODB存儲引擎的輔助索引的書簽就是相應行數據的聚集索引鍵。

上面這段話,可以發現如果用輔助索引來查找數據的話,需要兩個步驟:首先找到輔助索引的位置,然后輔助索引給出目標數據的聚集索引的位置,最后再根據這個聚集索引來找到對應數據行。這個第二階段的過程叫做回表

這里需要說明的從輔助索引的指針得道的聚集索引的位置是無序的,也就是回表這個過程是隨機讀取的,隨機的過程一般很慢的?為了優化這個回表的過程,從mysql5.6版本開始加入了mrr優化!

MRR優化

從MySQL5.6開始支持對Multi-Range Read(MRR)優化。MRR優化的目的就是為了減少磁盤的隨機訪問,對於MySQL的二級索引(非聚集索引)而言,過於隨機的回表會造成隨機讀取過於嚴重,范圍掃描(range access)中MySQL將掃描到的數據存入read_rnd_buffer_size,然后對其按照Primary Key(RowID)排序,然后使用排序好的數據進行順序回表,因為我們知道InnoDB中葉子節點數據是按照PRIMARY KEY(ROWID)進行排列的,那么這樣就轉換隨機讀取為順序讀取了。這對於IO-bound類型的SQL查詢語句帶來性能極大的提升。MRR優化可用於range,ref,eq_ref類型的查詢。

MRR優化有以下幾個好處:

  • 使得數據訪問變得較為順序,在查詢輔助索引時,先對得到的查詢結果按照主鍵進行排序,並按照主鍵排列的順序進行書簽查找。
  • 減少緩沖池中頁被替換的次數。
  • 批量處理對鍵值的查詢操作。

對於InnoDB和MyISAM存儲引擎的范圍查詢和聯接查詢,MRR的工作方式如下:

  • 將查詢得到的輔助索引鍵值存放於一個緩存中,這時緩存中的數據是根據輔助索引鍵值排序的。
  • 將緩存中的鍵值根據RowID進行排序。
  • 根據RowID的排序順序來訪問實際的數據文件。

此外,若InnoDB存儲引擎緩沖池不是足夠大,即不能存放下一張表中的所有數據,此時頻繁的離散讀取操作還會導致將緩存中的頁替換出緩沖池,然后又不斷地讀入緩沖池。若按照主鍵順序進行訪問,則可以將重復行為的次數降為最低。

執行簡易過程如圖:

 

#不起用mrr的時候,這條sql語句大概執行了3.17秒鍾.
mysql> explain select * from salaries where salary > 10000 and salary < 40000; +----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+-----------------------+ | 1 | SIMPLE | salaries | NULL | range | idx_index | idx_index | 4 | NULL | 21450 | 100.00 | Using index condition | +----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+-----------------------+ 1 row in set, 1 warning (0.00 sec)
#啟用mrr之后,這條sql語句大概執行了0.06秒。
mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Query OK, 0 rows affected (0.00 sec)

mysql> explain select * from salaries where salary > 10000 and salary < 40000;
+----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+----------------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key       | key_len | ref  | rows  | filtered | Extra                            |
+----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+----------------------------------+
|  1 | SIMPLE      | salaries | NULL       | range | idx_index     | idx_index | 4       | NULL | 21450 |   100.00 | Using index condition; Using MRR |
+----+-------------+----------+------------+-------+---------------+-----------+---------+------+-------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)

#會在extra字段標識使用了MRR
#salary上有一個輔助索引idx_salary,因此除了通過輔助索引查找鍵值外,還需要通過書簽查找來進行對整行數據的查找。這里使用了ICP(pushed-down conditions)和MRR,
#因為MRR的代價評估一般較高,所以這里使用mrr_cost_based=off。

此外,MRR還可以將某些范圍查詢拆分為鍵值對,以此來完成批量的數據查詢。這樣做的好處是可以在拆分過程中,直接過濾一些不符合查詢條件的數據,例如:

SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 <= 2000 AND key_part2 = 10000;

表t中有(key_part1, key_part2)的聯合索引,因此索引根據key_part1、key_part2的位置關系進行排序。若沒有MRR,此時查詢類型為Range,SQL優化器會先將key_part1大於1000小於2000的數據都取出,就是使key_part2不等於10000,待取出行數據后再根據key_part2的條件進行過濾,這會導致無用數據被取出。如果存在大量的數據並且其key_part2不等於10000,則啟用MRR優化性能會有巨大的提升。

倘若啟用了MRR優化,那么優化器會先將查詢條件進行拆分,然后再進行數據的查詢。就上述查詢語句而言,優化器會將查詢條件拆分為(1000, 10000),(1001, 10000),(1002, 10000),…,(1999, 10000),最后再根據這些拆分出的條件進行數據查詢。

mysql> show variables like "read_rnd_buffer_size";
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| read_rnd_buffer_size | 262144 |
+----------------------+--------+
1 row in set (0.00 sec)

默認值是256KB。

 ICP優化

和MRR一樣,ICP(index  condition pushdown)同樣是MySQL5.6開始支持的一種根據索引進行查詢的優化方式。之前MySQL數據庫不支持ICP,當進行索引查詢時,首先根據索引來查找記錄了,然后再根據where條件來過濾記錄。在支持ICP之后,MySQL數據庫會在取出索引的同時,判斷是否可以進行where條件過濾,也就是將where的部分過濾操作放在了存儲引擎層。在一些查詢下,可以大大減少上層sql對記錄的索取,從而提高數據庫的整體性能。

ICP優化支持對range, ref,eq_ref, ref_or_null類型的查詢,當前僅支持myisam和INNODB存儲引擎。當優化器選擇ICP時,可在執行計划列EXTRA看到Using index condition提示。

哈希算法

哈希算法是一種常見算法,時間復雜度為o(1),且不只存在於索引中,每個數據庫應用中都存在該數據庫結構。我們知道bp中緩存的有許多數據頁,那么我們如何在內存的眾多數據頁中,查找到我們需要的數據頁?雖然內存中查詢速度很快,但是也不可能每次都要遍歷所有的內存來進行查找,這時對於字典的操作只需要o(1)的哈希算法就由了很好的用武之地。

哈希表

全文索引

【暫缺】

 


免責聲明!

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



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