MySQL索引原理與演算法
B+ 樹索引
B 代表 balance 平衡;
B+ 樹索引 通過鍵值(如 id=3 ) 並不能直接找到具體的行。 它是把 行(資料行 記錄)所在的頁,讀入記憶體,再從記憶體中查詢, 最後得到要找的記錄(資料)。
二分查詢法:(折半查詢法)
首先 是 有一組 排好順序的 記錄。 如 10, 20,30, 40,50,60,70, 80,90,100
問題是 從這樣的一組排好順序的記錄中 查詢 某一個指定 記錄。?
採取的方法是:1. 首先將 中間位置的記錄 作為比較物件。 2; 要找的元素和 比較物件 比較 ,如果小於比較物件 縮小到左半部分;如果大於比較物件 縮小到右半部分。
結論: 平均來說 二分查詢法 比順序查詢法要好,效率高。
二叉樹:/(二叉查詢樹)。
二叉樹有哪些遍歷方法?
前序遍歷: 先訪問根結點, 再訪問左子樹, 再訪問右子樹;
中序遍歷: 先訪問左子樹,在父節點, 再右子樹;
後序遍歷:先訪問左子樹,再右子樹; 最後是訪問根結點;
層序遍歷: 先訪問根結點, 從上到下逐層遍歷。同一層中從左到右訪問。
二叉排序樹 / 二叉查詢樹。 binary sort tree
它 特點1: 左子樹上 所有結點的值 均小於 它的 根結點的值;
2. 右子樹上 所有結點的值,均大於 它的根結點的值;
平衡二叉樹 (Self -Balancing Binary Search Tree) : 首先是一種二叉排序樹; 其中每一個結點 的左子樹 和 右子樹的 高度差 小於等於 1;
維護一個平衡二叉樹 ,比如 插入,更新 和刪除操作。 這些都是通過 左旋 或右旋 來實現的, 這都是開銷。
B+ 樹: 是一種 平衡查詢樹。 葉子結點上 從小到大排序順序排序。各個葉子結點 使用指標連線。
示意圖: todo;
B+ 樹 插入操作: 3種情況 ; Leaf Page滿; Index Page滿 操作 表 todo; 有拆頁的可能
旋轉發生在Leaf Page已滿。 但是其左右結點沒有滿的情況下,這時 B+樹 不急於拆分 頁的操作,而是將記錄移到頁的 兄弟結點上。
旋轉 使B+ 樹減少了一次 拆分操作。
B+ 樹的刪除操作。
B+ 樹 使用 填充因子 fill factor 來控制 樹的刪除; /依據填充因子來 決定怎麼刪除; 填充因子 >= 50%
葉子節點 小於填充因子, 中間節點小於 填充因子 , 操作 三種情況 表 todo; 有合併頁的可能。
B+ 樹 索引 ; B+ 樹 在資料庫的應用/實現。
B+ 樹 索引 特點 :高 扇出性,B+ 樹 的高度 一般 2-4 層。 查詢一行記錄(ID = xxx, ID 是主鍵) 最多需要2到4次 IO; 假如機械硬碟每秒100次IO, 則查詢一次需要時間 0.02——0.04 秒。
B+ 樹 索引 分為 : 聚集索引(clustered index) ; 輔助索引(secondary index) / 非聚集索引(non-clustered index);
葉子節點 存放資料; 聚集索引的 葉子節點 存放時一整行的資料(完整的記錄);
聚集索引, 中每個葉子節點 都是一個頁; 葉子節點 之間使用 雙向連結串列來進行連結。
可以使用 py_innodb_page_info.py 工具來分析表空間。
使用 hexdump 工具來 檢視資料。
圖: 5-14 todo;
注意:儲存方式: 首先頁不是 物理上連續的;通過雙向連結; 再者。頁中的記錄 也是通過雙向連結串列進行維護的。
聚集索引 好處 : 對 主鍵的 排序 查詢 ; 和 範圍查詢 查詢速度非常快。
mysql > explain select * from Profile order by id limit 10;
mysql > explain select * from Profile where id > and id < 10000\G;
輔助索引:
葉子節點 包含 鍵值(索引列欄位值); 還包含 bookmark(主鍵的值)
每張表中可以有多個輔助索引。
例如: 通過輔助索引 怎麼找到一行資料 ?
例如: 在一棵高度 為3的 輔助索引樹 中查詢資料,首先需要對這顆輔助索引遍歷3次 找到 指定主鍵, 如果
指定的聚集索引樹的高度 同樣 為 3, 那麼還需要對 聚集索引樹進行3次 查詢。最終找到一個完整的資料行所在的頁。算下來,一共需要6次邏輯IO得到最終的一個數據頁。
例子分析 圖 5-16 todo;
B+ 樹索引的分裂( 拆分頁)
InnoDB儲存引擎 的 Page Header 中有幾個部分來儲存插入的順序資訊。 PAGE_LAST_INSERT PAGE_DIRECTION PAGE_N_DIRECTION
增值插入 時 分裂點就是插入記錄本身(如果要分裂的話);其他插入情況 暫時不深究。
索引建立和刪除:
兩種方式:
一:
ALTER TABLE tbl_name
ADD {INDEX| KEY } [index_name] [index_type] (index_col_name ,…) [index_option] …
ALTER TABLE tbl_name
DROP PRIMARY KEY
| DROP FOREIGN KEY fk_symbol
| DROP {INDEX| KEY} index_name
二:
CREATE 【UNIQUE | FULLTEXT | SPATIAL ] INDEX index_name
[index_type]
ON tbl_name (index_col_name,…) [index_option] [algorithm_option | lock_option] …
DROP INDEX index_name ON tbl_name [algorithm_option | lock_option] …
algorithm_option :
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
lock_option :
LOCK [=] {DEFAULT |NONE |SHARED | EXCLUSIVE}
檢視索引:
SHOW INDEX FROM tbl_name;
例子: 使用者可以設定整個列的資料進行索引,也可以只索引一個列的開頭部分資料, 如 b 為 varchar(8000) , 使用者可以只索引 前 100個欄位,如:
ALTER TABLE t ADD KEY idx_b (b(100));
SHOW INDEX 結果 每一列的含義。
Collation: 列以什麼方式儲存在索引中, B+ 樹 總是 A
Cardinality: 索引中 唯一值的數目的估計值。; 它不是 實時更新的 ,是個大概的值。
優化器 會 根據 Cardinality 的值來選擇是否使用這個索引。
ANALYZE TABLE 操作 會跟新 Cardinality 的值。
對 現有的資料表 (有很多資料) 進行 索引的 建立 或刪除 , 會造成 什麼影響,效率怎麼樣?以前是怎麼做的,現在是怎麼做的?
InnoDB 1.0.x 開始支援 快速索引建立 Fast Index Creation 簡稱: FIC。 針對的是輔助索引。
對於輔助索引的建立; InnoDB儲存引擎 會 對建立索引的表 加上一個 S 鎖。 在建立過程中不需要重新建表。
輔助索引的刪除: 更新內部檢視, 將輔助索引的空間標記為可讀, 同時刪除內部檢視上 對該表的索引定義。
主鍵的建立 和刪除 同樣需要重建一張表。
線上資料定義 Online DDL
MySQL 5.6版本開始 支援 Online DDL 線上資料定義 操作; 允許輔助索引建立的同時,還可以允許其他 像 INSERT UPDATE DELETE 這類DML 操作,
這極大地提高了Mysql 資料庫在 生成環境中的可用性。
還支援的“線上”操作如:
輔助索引的建立與 刪除
改變自增長值
新增或刪除外來鍵約束
列的重新命名。
CREATE 【UNIQUE | FULLTEXT | SPATIAL ] INDEX index_name
[index_type]
ON tbl_name (index_col_name,…) [index_option] [algorithm_option | lock_option] …
ALGORITHM 指定了 建立 或刪除索引的演算法 可以取值如: COPY INPLACE DEFAULT; 預設 採用 DEFAULT 方式。
LOCK 建立或刪除索引 新增鎖的情況 ,可以取值如:
NONE, //不加鎖,這種模式可以獲得最大的併發度
SHARE, // S 鎖, 併發的讀可以,遇到寫的事務,寫事務就要等待。
EXCLUSIVE, // X 鎖, 對目標 表 加上一個X 鎖。 讀寫事務都不能進行。
DEFAULT , // 1, 首先判斷能不能 使用 NONE, 2, 能不能使用 SHARE , 3 能不能使用 EXCLUSIVE .
Online DDL的原理: 在執行 建立或刪除操作的同時,將 INSERT ,UPDATE, DELETE, 這類DML操作日誌寫入到一個快取中,等到完成索引建立後,再將重做應用到表上。 這個快取預設大小是 128M (由引數 innodb_online_alter_log_max_size 引數控制)。
在索引的建立過程中,SQL 優化器 不會 選擇 正在建立中的索引。
什麼樣的情況下,適合加索引? 哪些欄位適合加索引?
像 性別, 地區, 型別 欄位 ,他們的取值範圍很小,低選擇性; 所以沒必要加索引
像 姓名 就可以加索引。
Cardinality/ n_rows_in_table 應儘可能接近1。 如果非常小,那麼使用者需要考慮是否有必要加索引。
Cardinality 是怎麼統計的? 是怎麼計算的?
統計時通過取樣 來完成的; Cardinality統計更新發生在 INSERT 和 UPDATE 。
策略: 1. 表中 1/16 的資料 已發生過 變化。
2. stat_modified_counter > 2 000 000 000 .
預設取樣數量 是 8
當 執行SQL 語句:
ANALYZE TABLE;
SHOW TABLE STATUS;
SHOW INDEX;
以及 訪問 information_schema 下的表 tables 和 statistics
時, 會導致InnoDB 儲存引擎去重複計算索引 Cardinality 值。
如果表中 資料量 很大,並且表中有多個輔助索引,執行上述操作可能會非常慢。
不同應用中B+ 樹索引的應用?
OLTP 應用 一般只從資料庫中取得一小部分資料,一般 10條 ,這種建立 B+樹索引有意義。
OLAP 應用, 都需要訪問大量資料,多是面向分析的查詢。 這個時候通常對時間欄位進行索引。因為大多數統計需要根據時間維度來進行資料的篩選。
聯合索引:
create table t(
a int,
b int,
primary key (a),
key idx_a_b (a,b)
)engine = innoDB
圖 5-22 todo;
select * from t where a=xxx and b=xxx //可以使用到索引;
select * from t where b=xxx; //使用不到這棵索引;
select * from t where a=xxx order by b; //可以使用到聯合索引
聯合索引的好處是 : 已經對 第二個鍵值進行了 排序處理。例如:
create table buy_log(
userid int unsigned not null,
buy_date date
)engine=InnoDB
alter table buy_log add key(userid);
alter table buy_log add key(userid, buy_date);
select * from buy_log where userid=2;
//分析 有兩個索引 可以使用; 最終選擇的是索引 userid;
select * from buy_log where userid=1 order by buy_date desc limit 3;
//分析 可以用使用 userid, (userid, buy_date) 兩個索引; 最終選擇了聯合索引 userid_2; 因為 聯合索引中buy_date已經排好了, 根據聯合索引取出資料,無須對buy_date做一次額外的排序操作。
對 a, b, c新增 聯合 索引(a, b, c); 如下:
select … from table where a=xxx order by b; //可以使用索引
select … from table where a=xxx and b=xxx order by c; //可以使用索引。
覆蓋索引: / 索引覆蓋 (covering index):
從輔助索引中可以得到查詢的話; 就不需要查詢聚集索引中的記錄了。 使用 覆蓋索引的好處 是輔助索引中不包含整行記錄的所有資訊),所以大小要遠小於聚集索引,因此可以減少大量的IO操作。
若 葉子節點 存放的資料 為 (primary key1, primary key2, …, key1, key2, ….). 下面語句都可以僅使用 一次輔助聯合索引來完成查詢。
select key2 from table where key1=xxx;
select primary key2, key2 from table where key1=xxx;
select primary key1, key2 from table where key1=xxx;
select primary key1, primary key2, key2 from table where key1=xxx;
- 對於某些統計 問題 也可以 僅使用 輔助索引。
如 select count(*) from buy_log; //Extra Using index 代表使 優化器 進行了覆蓋索引操作。
select count(*) from buy_log where buy_date>=’2011-01-01’ and buy_date<’2011-02-01’; //
//(a, b) 的這種聯合索引,一般是b 作為查詢條件 是使用不到索引的,但是 如果是統計操作則 優化器 會進行選擇。
什麼情況下 使用不到索引? 什麼情況下優化器 不使用索引。
多 發生在 範圍查詢 , join連結 等情況下。
select * from orderdetails where orderid > 10000 and orderid < 102000;
如果 要求訪問的資料量很小, 則優化器還是會選擇輔助索引; 如果當訪問的資料佔整個表中資料蠻大一部分(20% 左右),優化器會選擇聚集索引來來查詢資料。因為 順序讀取的速度遠遠快於離散讀。
索引提示:index hint
以下兩種情況 可以用到 index hint
- MySQL 資料庫的優化器錯誤地選擇了某個索引。 很少見
- 某個SQL語句可以使用的索引很多,這時 查詢優化器執行計劃時間的開銷可能會大於 SQL語句本身。
語法:
USE index 只是告訴優化器可以選擇索引, 實際上優化器還是根據自己的判斷進行操作。 可以使用 FORCE index 來強制使用索引。
Multi-Range Read 優化 / MRR 優化;(InnoDB MyISAM 都支援)
MySQL 5.6 開始支援 MRR 優化; MRR 適用於 range, ref, eq_ref 型別 的查詢。
MRR 的工作原理:/方式:
- 將 查詢得到的輔助索引 鍵值存放於一個 快取中( 預設 256k), 這時快取中的資料是安裝輔助索引 的 鍵值 進行排序的。
- 將快取中的鍵值 根據 rowID(主鍵ID)進行排序。
- 根據RowID 的排序順序來訪問實際的資料檔案。
之所以稱為 優化,就是因為 避免了 離散讀取。
select * from salaries where salary > 10000 and salary < 40000;
開不開 差 10倍。
Multi-Range Read 還可以將某些範圍查詢 ,拆分為鍵值 對, 來進行批量查詢。如:
select * from t where key_part1 >=1000 and key_part1 < 2000 and key_part2 = 10000;
//優化器 會將 查詢條件 拆分為(1000, 1000), (1001, 1000), (1002, 1000)…, (1999, 1000);
總是開啟MRR:
mysql > set @@optimizer_switch=‘mrr=on, mrr_cost_based=off’;
//檢視快取的大小
mysql > select @@read_rnd_buffer_size\G;
Index Condition Pushdown ICP 優化;
msyql5.6 開始支援: 開啟 ICP 後, 會在取出索引的同時,判斷是否可以進行where 條件的過濾。
ICP 優化支援 range ,ref, eq_ref, ref_or_null 型別的查詢。
如: 某表有聯合 索引
開啟 ICP 後 執行時間的對比 表5-5 todo;
雜湊表:
一般來說都將關鍵字轉換為自然樹,然後通過除法散列表。 h(k) = k mod m
例如: innodb_buffer_pool_size 的大小 為 10M ,則共有 640個 16KB的頁。 對 雜湊表來說 需要 640 X 2 = 1280個槽, 但不是質數,應該 是 1399;
在InnoDB 儲存引擎 的 緩衝池中 對於其中的 頁 是怎麼進行查詢的呢?
關鍵字 K= space_id<<20 + space_id + offset;
自適應雜湊:
hash 索引只能用來搜尋等值的查詢。範圍查詢是不能使用雜湊索引的
select * from table where index_col=‘xxx’;
全文索引 【暫時不深入研究】
參考書: MySQL技術內幕: