mysql:索引
索引
為什麼使用索引
![img](file:///C:\Users\Boerk\AppData\Local\Temp\ksohtml16464\wps2.png)
假如給資料使用 二叉樹 這樣的資料結構進行儲存,如下圖所示
![img](file:///C:\Users\Boerk\AppData\Local\Temp\ksohtml16464\wps3.png) |
索引概述
MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構。
索引的本質:索引是資料結構。你可以簡單理解為“排好序的快速查詢資料結構”,滿足特定查詢演算法。 這些資料結構以某種方式指向資料, 這樣就可以在這些資料結構的基礎上實現 高階查詢演算法 。
優點
-
類似大學圖書館建書目索引,提高資料檢索的效率,降低 資料庫的IO成本 ,這也是建立索引最主要的原因。
-
通過建立唯一索引,可以保證資料庫表中每一行 資料的唯一性 。
-
在實現資料的參考完整性方面,可以 加速表和表之間的連線 。換句話說,對於有依賴關係的子表和父表聯合查詢時, 可以提高查詢速度。
-
在使用分組和排序子句進行資料查詢時,可以顯著 減少查詢中分組和排序的時間 ,降低了CPU的消耗。
缺點
增加索引也有許多不利的方面,主要表現在如下幾個方面:
-
建立索引和維護索引要 耗費時間 ,並且隨著資料量的增加,所耗費的時間也會增加。
-
索引需要佔 磁碟空間 ,除了資料表佔資料空間之外,每一個索引還要佔一定的物理空間, 儲存在磁碟上 ,如果有大量的索引,索引檔案就可能比資料檔案更快達到最大檔案尺寸。
-
雖然索引大大提高了查詢速度,同時卻會 降低更新表的速度 。當對錶中的資料進行增加、刪除和修改的時候,索引也要動態地維護,這樣就降低了資料的維護速度。
因此,選擇使用索引時,需要綜合考慮索引的優點和缺點。
索引可以增加查詢效率,但是會降低增刪改的效率。
通常情況下,在增刪改之前刪除索引,在完成後再次建立索引
設計一個索引
mysql> CREATE TABLE index_demo( c1 INT, c2 INT, c3 CHAR(1), PRIMARY KEY(c1) ) ROW_FORMAT = Compact; #ROW_FORMAT為行格式 #這個新建的 index_demo 表中有2個INT型別的列,1個CHAR(1)型別的列,而且我們規定了c1列為主鍵,這個表使用 Compact 行格式來實際儲存記錄的。這裡我們簡化了index_demo表的行格式示意圖:
這個新建的 index_demo 表中有2個INT型別的列,1個CHAR(1)型別的列,而且我們規定了c1列為主鍵,這個表使用 Compact 行格式來實際儲存記錄的。這裡我們簡化了index_demo表的行格式示意圖:
-
record_type :記錄頭資訊的一項屬性,表示記錄的型別, 0 表示普通記錄、 2 表示最小記
錄、 3 表示最大記錄、 1 暫時還沒用過,下面講。 -
next_record :記錄頭資訊的一項屬性,表示下一條地址相對於本條記錄的地址偏移量,我們用箭頭來表明下一條記錄是誰。
-
各個列的值 :這裡只記錄在 index_demo 表中的三個列,分別是 c1 、 c2 和 c3 。
-
其他資訊 :除了上述3種資訊以外的所有資訊,包括其他隱藏列的值以及記錄的額外資訊。
我們在根據某個搜尋條件查詢一些記錄時為什麼要遍歷所有的資料頁呢?因為各個頁中的記錄並沒有規律,我們並不知道我們的搜尋條件匹配哪些頁中的記錄,所以不得不依次遍歷所有的資料頁。所以如果 我們想快速的定位到需要查詢的記錄在哪些資料頁中該咋辦?我們可以為快速定位記錄所在的資料頁而建立一個目錄,建這個目錄必須完成下邊這些事:
- 下一個資料頁中使用者記錄的主鍵值必須大於上一個頁中使用者記錄的主鍵值。
-
給所有的頁建立一個目錄項
- 每一個目錄項儲存了當前資料頁中最小主鍵值。以及資料頁號。
- 以第十號資料頁舉例,建立了目錄項1,他記載了最小主鍵值:1和資料頁編號:10。
- 至此,針對資料頁做的簡易目錄就搞定了。這個目錄有一個別名,稱為
索引
。 - 此時,我們需要查詢主鍵為20的資料,則在目錄項中查詢最小主鍵。就會立即排除目錄項1、2、4,找到主鍵20在目錄項4,資料頁9中。
同理,我們可以將目錄項也作為一個數據頁,然後再次建立目錄項資料頁的目錄項。
然後存在多個目錄項資料頁的時候常見一個更高一級的資料頁。
由此迭代,便形成了B+樹
。
-
B+Tree
一個B+樹的節點其實可以分成好多層,規定最下邊的那層,也就是存放我們使用者記錄的那層為第0
層, 之後依次往上加。之前我們做了一個非常極端的假設:存放使用者記錄的頁最多存放3條記錄
,存放目錄項記錄的頁最多存放4條記錄
。其實真實環境中一個頁存放的記錄數量是非常大的,假設所有存放使用者記錄的葉子節點代表的資料頁可以存放 100條使用者記錄 ,所有存放目錄項記錄的內節點代表的資料頁可以存放 1000條目錄項記錄 ,那麼: -
如果B+樹只有1層,也就是隻有1個用於存放使用者記錄的節點,最多能存放 100 條記錄。
-
如果B+樹有2層,最多能存放 1000×100=10,0000 條記錄。
-
如果B+樹有3層,最多能存放 1000×1000×100=1,0000,0000 條記錄。
-
如果B+樹有4層,最多能存放 1000×1000×1000×100=1000,0000,0000 條記錄。相當多的記錄!!!
你的表裡能存放 條記錄嗎?所以一般情況下,我們 用到的B+樹都不會超過4層 ,那我們通過主鍵值去查詢某條記錄最多隻需要做4個頁面內的查詢(查詢3個目錄項頁和一個使用者記錄頁),又因為在每個頁面內有所謂的 Page Directory (頁目錄),所以在頁面內也可以通過 二分法 實現快速定位記錄。
- 常見索引概念
索引按照物理實現方式,索引可以分為 2 種:聚簇(聚集)和非聚簇(非聚集)索引。我們也把非聚集索引稱為二級索引或者輔助索引。
聚簇索引
- 用記錄主鍵值的大小進行記錄和頁的排序,這包括三個方面的含義:
頁內 的記錄是按照主鍵的大小順序排成一個 單向連結串列 。
各個存放 使用者記錄的頁 也是根據頁中使用者記錄的主鍵大小順序排成一個 雙向連結串列 。
存放 目錄項記錄的頁 分為不同的層次,在同一層次中的頁也是根據頁中目錄項記錄的主鍵大小順序排成一個 雙向連結串列 。 - B+樹的 葉子節點 儲存的是完整的使用者記錄。
所謂完整的使用者記錄,就是指這個記錄中儲存了所有列的值(包括隱藏列)。
優點:
- 資料訪問更快 ,因為聚簇索引將索引和資料儲存在同一個B+樹中,因此從聚簇索引中獲取資料比非聚簇索引更快
- 聚簇索引對於主鍵的 排序查詢 和 範圍查詢 速度非常快
- 按照聚簇索引排列順序,查詢顯示一定範圍資料的時候,由於資料都是緊密相連,資料庫不用從多 個數據塊中提取資料,所以 節省了大量的io操作 。
缺點:
- 插入速度嚴重依賴於插入順序 ,按照主鍵的順序插入是最快的方式,否則將會出現頁分裂,嚴重影響效能。因此,對於InnoDB表,我們一般都會定義一個自增的ID列為主鍵
- 更新主鍵的代價很高 ,因為將會導致被更新的行移動。因此,對於InnoDB表,我們一般定義主鍵為不可更新
- 二級索引訪問需要兩次索引查詢 ,第一次找到主鍵值,第二次根據主鍵值找到行資料
二級索引
不由主鍵或者不由非空且唯一的列構成的索引稱為二級索引。
回表:我們根據這個以c2列大小排序的B+樹只能確定我們要查詢記錄的主鍵值,所以如果我們想根據c2列的值查詢到完整的使用者記錄的話,仍然需要到 聚簇索引 中再查一遍,這個過程稱為 回表 。也就是根據c2列的值查詢一條完整的使用者記錄需要使用到 2 棵B+樹!
我的理解:二級索引構成的B+樹中,只存有當前構成索引的欄位和主鍵欄位。當根據當前欄位找到時,根據主鍵回到聚簇索引中查詢詳細資訊。
聯合索引
聯合索引屬於非聚簇索引(二級索引)
根據兩個或多個欄位構成B+樹,根據第一個欄位排列,第一欄位相同時,再根據第二欄位排列,以此類推。
含有的欄位:構成該B+樹的欄位+主鍵
InnoDB的B+樹索引的注意事項
-
根頁面位置萬年不動
- 首先在一個根目錄中儲存資料(葉子節點)
- 當這個資料頁有3條資料再存資料時
- 開闢一個當前資料頁的複製
- 對資料頁進行
頁分裂
得到另一個新頁 - 將新插入的資料存入新頁
- 當前資料頁變為一個目錄頁
-
內節點中目錄項記錄的唯一性
-
要保證二級索引的唯一性,否則會出現一些情況
-
此時檢視插入一個(c1,c2,c3) values(9,1,c)
-
這個二級索引就無法插入了,因為不知道應該插在哪裡,c2為1,可能插在頁4,也可能插在頁5.
-
因此構建目錄頁時,需將主鍵也帶上。
-
-
一個頁面最少儲存2條記錄
MyISAM中的索引方案
MyISAM中只有非聚簇索引,沒有聚簇索引
每一個索引都直接儲存地址值。
MyISAM與InnoDB對比
MyISAM的索引方式都是“非聚簇”的,與InnoDB包含1個聚簇索引是不同的。小結兩種引擎中索引的區 別:
- 在InnoDB儲存引擎中,我們只需要根據主鍵值對 聚簇索引 進行一次查詢就能找到對應的記錄,而在中卻需要進行一次 回表 操作,意味著MyISAM中建立的索引相當於全部都是 二級索引 。
- InnoDB的資料檔案本身就是索引檔案,而MyISAM索引檔案和資料檔案是 分離的 ,索引檔案僅儲存資料記錄的地址。
- InnoDB的非聚簇索引data域儲存相應記錄 主鍵的值 ,而MyISAM索引記錄的是 地址 。換句話說,
InnoDB的所有非聚簇索引都引用主鍵作為data域。 - MyISAM的回表操作是十分 快速 的,因為是拿著地址偏移量直接到檔案中取資料的,反觀InnoDB是通過獲取主鍵之後再去聚簇索引裡找記錄,雖然說也不慢,但還是比不上直接用地址去訪問。
- InnoDB要求表 必須有主鍵 ( MyISAM可以沒有 )。如果沒有顯式指定,則MySQL系統會自動選擇一個可以非空且唯一標識資料記錄的列作為主鍵。如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含欄位作為主鍵,這個欄位長度為6個位元組,型別為長整型。
索引的代價
索引是個好東西,可不能亂建,它在空間和時間上都會有消耗:
-
空間上的代價
每建立一個索引都要為它建立一棵B+樹,每一棵B+樹的每一個節點都是一個數據頁,一個頁預設會 佔用 16KB 的儲存空間,一棵很大的B+樹由許多資料頁組成,那就是很大的一片儲存空間。 -
時間上的代價
每次對錶中的資料進行 增、刪、改 操作時,都需要去修改各個B+樹索引。而且我們講過,B+樹每層節點都是按照索引列的值 從小到大的順序排序 而組成了 雙向連結串列 。不論是葉子節點中的記錄,還是內節點中的記錄(也就是不論是使用者記錄還是目錄項記錄)都是按照索引列的值從小到大的順序 而形成了一個單向連結串列。而增、刪、改操作可能會對節點和記錄的排序造成破壞,所以儲存引擎需 要額外的時間進行一些 記錄移位 , 頁面分裂 、 頁面回收 等操作來維護好節點和記錄的排序。如果我們建了許多索引,每個索引對應的B+樹都要進行相關的維護操作,會給效能拖後腿。
一些其他資料結構
Hash結構
hash的特點:輸入相同的值,輸出的也一定相同。反之則不成立。
將hash(key)存入陣列。
從效率上來說,雜湊比B+樹還要快
雜湊可能存在雜湊衝突。使用連結串列將相同值進行“焊接”
二叉搜尋樹
如果我們利用二叉樹作為索引結構,那麼磁碟的IO次數和索引樹的高度是相關的。
創造出來的二分搜尋樹如下圖所示:
為了提高查詢效率,就需要 減少磁碟IO數 。為了減少磁碟IO的次數,就需要儘量 降低樹的高度 ,需要把原來“瘦高”的樹結構變的“矮胖”,樹的每層的分叉越多越好。
AVL樹
AVL樹,即二叉平衡樹或者平衡二叉樹。
針對同樣的資料,如果我們把二叉樹改成 M 叉樹 (M>2)呢?當 M=3 時,同樣的 31 個節點可以由下面的三叉樹來進行儲存:
你能看到此時樹的高度降低了,當資料量N大的時候,以及樹的分叉數M大的時候,M叉樹的高度會遠小於二叉樹的高度(M>2)。所以,我們需要把樹從“瘦高"變"矮胖”。
B-TREE
B-Tree,即Balance Tree多路平衡查詢樹
一個 M 階的 B 樹(M>2)有以下的特性:
-
根節點的兒子數的範圍是 [2,M]。
-
每個中間節點包含 k-1 個關鍵字和 k 個孩子,孩子的數量 = 關鍵字的數量 +1,k 的取值範圍為
[ceil(M/2), M]。
-
葉子節點包括 k-1 個關鍵字(葉子節點沒有孩子),k 的取值範圍為 [ceil(M/2), M]。
-
假設中間節點節點的關鍵字為:Key[1], Key[2], …, Key[k-1],且關鍵字按照升序排序,即 Key[i]
<Key[i+1]。此時 k-1 個關鍵字相當於劃分了 k 個範圍,也就是對應著 k 個指標,即為:P[1], P[2], …,
P[k],其中 P[1] 指向關鍵字小於 Key[1] 的子樹,P[i] 指向關鍵字屬於 (Key[i-1], Key[i]) 的子樹,P[k] 指向關鍵字大於 Key[k-1] 的子樹。
- 所有葉子節點位於同一層。
上面那張圖所表示的 B 樹就是一棵 3 階的 B 樹。我們可以看下磁碟塊 2,裡面的關鍵字為(8,12),它有 3 個孩子 (3,5),(9,10) 和 (13,15),你能看到 (3,5) 小於 8,(9,10) 在 8 和 12 之間,而 (13,15) 大於 12,剛好符合剛才我們給出的特徵。
然後我們來看下如何用 B 樹進行查詢。假設我們想要 查詢的關鍵字是 9 ,那麼步驟可以分為以下幾步:
- 我們與根節點的關鍵字 (17,35)進行比較,9 小於 17 那麼得到指標 P1;
- 按照指標 P1 找到磁碟塊 2,關鍵字為(8,12),因為 9 在 8 和 12 之間,所以我們得到指標 P2;
- 按照指標 P2 找到磁碟塊 6,關鍵字為(9,10),然後我們找到了關鍵字 9。
你能看出來在 B 樹的搜尋過程中,我們比較的次數並不少,但如果把資料讀取出來然後在記憶體中進行比較,這個時間就是可以忽略不計的。而讀取磁碟塊本身需要進行 I/O 操作,消耗的時間比在記憶體中進行比較所需要的時間要多,是資料查詢用時的重要因素。 B 樹相比於平衡二叉樹來說磁碟 I/O 操作要少 , 在資料查詢中比平衡二叉樹效率要高。所以 只要樹的高度足夠低,IO次數足夠少,就可以提高查詢效能 。
B+樹
B+ 樹和 B 樹的差異:
-
有 k 個孩子的節點就有 k 個關鍵字。也就是孩子數量 = 關鍵字數,而 B 樹中,孩子數量 = 關鍵字數
+1。 -
非葉子節點的關鍵字也會同時存在在子節點中,並且是在子節點中所有關鍵字的最大(或最 小)。
-
非葉子節點僅用於索引,不儲存資料記錄,跟記錄有關的資訊都放在葉子節點中。而 B 樹中,
-
所有關鍵字都在葉子節點出現,葉子節點構成一個有序連結串列,而且葉子節點本身按照關鍵字的大 小從小到大順序連結。
B 樹和 B+ 樹都可以作為索引的資料結構,在 MySQL 中採用的是 B+ 樹。但B樹和B+樹各有自己的應用場景,不能說B+樹完全比B樹好,反之亦然。
InnoDB資料儲存結構
磁碟與記憶體互動的基本單位是頁
。
每一頁的大小是16KB,在資料庫中,無論是讀多行還是讀一行,都是將這些行所在頁進行載入。
也就是說資料庫I/O操作最小的單位是頁。
資料頁的特點
- 在無論上不相連
- 通關雙向連結串列關聯
頁的上層結構
- 區:一個區有64個連續的頁,所以一個區的大小是
1MB
- 段:段是資料庫分配的單位,不同型別的資料庫物件存在不同的段中
- 表空間:邏輯上的容器。
頁
名稱 | 佔用大小 | 說明 |
---|---|---|
File Header | 28位元組 | 檔案頭 |
Page Header | 56位元組 | 頁頭,頁的狀態資訊 |
Infimum & Supremum | 26位元組 | 最大和最小記錄 |
User Records | 不確定 | 使用者記錄 |
Free Space | 不確定 | 休閒記錄 |
Page Directory | 不確定 | 頁目錄,儲存使用者記錄的相對位置 |
File Trailer | 8位元組 | 檔案尾,校驗頁是否完整 |
索引的創建於設計原則
索引的分類
- 從
功能邏輯
上說,索引主要有 4 種,分別是普通索引
、唯一索引
、主鍵索引
、全文索引
。 - 按照
物理實現方式
,索引可以分為 2 種:聚簇索引
和非聚簇索引
。 - 按照
作用欄位個數
進行劃分,分成單列索引
和聯合索引
。
- 普通索引
可以新增在任何欄位上,只是用於提高查詢效率。
- 唯一索引
當一個欄位被宣告唯一約束時,就預設會建立唯一索引。一個表中可以有多個唯一索引
- 主鍵索引
當宣告主鍵約束的時候,就會建立主鍵索引。一張表中最多隻能有一個主鍵索引,也只能有一個主鍵。
主鍵索引是一種聚簇索引,因此主鍵索引或儲存表中的資料。所以只能存在一個。
- 單列索引
只作用在一個欄位上。一個表中可以存在多個單列索引
小結:不同的儲存引擎支援的索引型別也不一樣
- InnoDB :支援 B-tree、Full-text 等索引,不支援 Hash 索引;
- MyISAM : 支援 B-tree、Full-text 等索引,不支援 Hash 索引;
- Memory :支援 B-tree、Hash 等索引,不支援 Full-text 索引;
- NDB :支援 Hash 索引,不支援 B-tree、Full-text 等索引;
- Archive :不支援 B-tree、Hash、Full-text 等索引;
建立索引
語法
# 建立表時,新增約束,會隱式的新增索引
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);#為主鍵添加了主鍵索引
CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE,
dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);#為主鍵添加了主鍵索引、為emp_name添加了非空索引、為dept_id添加了普通索引(外來鍵)
#顯式的建立索引
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]
#檢視索引
SHOW CREATE TABLE book;
SHOW CREATE TABLE book\G #命令列
#或者
SHOW INDEX FROM book;
- 普通索引
CREATE TABLE book(
book_id INT , book_name VARCHAR(100),
authors VARCHAR(100), info VARCHAR(100) ,
comment VARCHAR(100), year_publication YEAR,
INDEX(year_publication)#普通索引
);
- 唯一索引
# 宣告有唯一索引的欄位,在新增資料時,要保證唯一性,但是可以新增null
CREATE TABLE book1(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR,
UNIQUE INDEX uk_idx_cmt(COMMENT)#宣告索引
);
#Duplicate entry '1' for key 'book1.uk_idx_cmt'
- 主鍵索引
#通過定義主鍵約束的方式定義主鍵索引
CREATE TABLE book2(
book_id INT PRIMARY KEY ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
#通過刪除主鍵約束的方式刪除主鍵索引
ALTER TABLE book2
DROP PRIMARY KEY;
- 單列索引
#建立單列索引
CREATE TABLE book3(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR,
#宣告索引
UNIQUE INDEX idx_bname(book_name)
);
- 聯合索引
#建立聯合索引
CREATE TABLE book4(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR,
#宣告索引
INDEX mul_bid_bname_info(book_id,book_name,info)
);
在表已經建立好了的情況下新增索引
CREATE TABLE book5(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
#新增普通索引
ALTER TABLE book5 ADD INDEX idx_cmt(COMMENT);
#新增唯一索引
ALTER TABLE book5 ADD UNIQUE uk_idx_bname(book_name);
#新增多列索引
ALTER TABLE book5 ADD INDEX mul_bid_bname_info(book_id,book_name,info);
#方式二
CREATE INDEX ... ON ...
CREATE TABLE book6(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
#新增普通索引
CREATE INDEX idx_cmt ON book6(COMMENT);
#新增唯一索引
CREATE UNIQUE INDEX uk_idx_bname ON book6(book_name);
#新增多列索引
CREATE INDEX mul_bid_bname_info ON book6(book_id,book_name,info);
刪除索引
#方式1:ALTER TABLE .... DROP INDEX ....
ALTER TABLE book5
DROP INDEX idx_cmt;
#方式2:DROP INDEX ... ON ...
DROP INDEX uk_idx_bname ON book5;