H2 Database Select 語句執行流程
使用
// CREATE TABLE IF NOT EXISTS test(id INT primary key, name VARCHAR(255))
// insert into test(id, name) values(1, 'name1'), (2, 'name2'), (3, 'name3'), (4, 'name4');
String sql = "SELECT * FROM test where id > 1 and name='name3'";
ResultSet rs = stmt.executeQuery(sql);
查詢 case:
主鍵查詢
覆蓋索引查詢
索引查詢+回表
多表關聯查詢
功能
模塊
類
總體流程
Select 語句 SQL 解析
Select 語句 SQL 查詢優化計算執行計劃的成本創建查詢優化器優化查詢計劃計算最佳查詢計劃測試并更新最優查詢計劃創建計劃計算計劃成本先計算 MVPrimaryIndex 查詢計劃的成本再獲取所有候選的索引計算對應的查詢計劃更新成本最低的查詢計劃并返回
Select 語句執行
解析&優化
H2 在執行查詢操作之前,會根據不同索引計算對應的成本,然后選擇成本最小的索引進行后續查詢操作。
單表查詢時,會獲取表的所有索引。按照主鍵索引、二級索引的順序依次計算對應的成本。
然后選擇成本最小的索引作為查詢計劃。
org.h2.command.Parser#parse
解析sql
org.h2.command.query.Query#prepare
優化sqlorg.h2.command.query.Select#preparePlan 計算執行計劃的成本org.h2.command.query.Optimizer#<init> 創建查詢優化器org.h2.command.query.Optimizer#optimize 優化查詢計劃org.h2.command.query.Optimizer#calculateBestPlan 計算最佳查詢計劃org.h2.command.query.Optimizer#testPlan 測試并更新最優計劃org.h2.table.Plan#<init> 創建一個計劃org.h2.table.Plan#calculateCost 計算計劃成本初始化成本為1,作為后續成本累乘的基礎值遍歷所有的表過濾器來計算總成本org.h2.table.TableFilter#getBestPlanItem 獲取最佳的計劃項org.h2.table.Table#getBestPlanItem 獲取最佳的計劃項org.h2.table.PlanItem#<init> 初始化一個 PlanItem 對象來存儲最佳計劃org.h2.mvstore.db.MVPrimaryIndex#getCost 計算初始計劃項的成本(primary key)org.h2.index.Index#getCostRangeIndex具體計算成本org.h2.mvstore.db.MVTable#getIndexes 獲取所有候選索引遍歷所有索引計算當前索引的成本(不同索引有不同索引的計算公式)如果當前索引的成本低于當前最佳計劃項的成本,更新最佳計劃項更新總成本返回最終計算的成本選擇最優計劃
執行
執行查詢時,會根據上面選定的索引創建對應的迭代器,同時會根據不同的事務隔離級別選擇創建不同隔離級別的迭代器(比如 CommittedIterator)。
然后根據索引列的條件迭代索引獲取數據,獲取后的數據會通過 isConditionMet
判斷是否滿足 SQL 查詢條件,最終決定是否要把該行返回給客戶端。
org.h2.command.query.Select#queryWithoutCache
執行查詢org.h2.table.TableFilter#lock 加讀鎖org.h2.command.query.Select#queryFlat執行查詢org.h2.result.LazyResult#init0 創建一個LazyResultQueryFlat對象org.h2.result.FetchedResult#next 獲取下一行org.h2.result.LazyResult#hasNext 判斷是否有下一條記錄org.h2.command.query.Select.LazyResultQueryFlat#fetchNextRow 獲取下一行org.h2.table.TableFilter#next 獲取下一行數據org.h2.index.IndexCursor#find 創建 index cursor, 用于迭代數據org.h2.index.IndexCursor#prepareorg.h2.mvstore.db.MVPrimaryIndex#find 從 index 里獲取 row, 比如 primary indexorg.h2.mvstore.db.MVPrimaryIndex#getMap 獲取 transaction maporg.h2.mvstore.tx.TransactionMap#entryIterator 根據不同隔離級別創建不同迭代器,比如 CommittedIteratororg.h2.index.IndexCursor#next 通過迭代器獲取下一行數據org.h2.mvstore.db.MVPrimaryIndex.MVStoreCursor#next 通過主鍵索引獲取下一行數據org.h2.mvstore.tx.TransactionMap.CommittedIterator#fetchNext 獲取讀已提交數據org.h2.mvstore.Cursor#hasNext 迭代到葉子節點獲取下一行數據,返回是否存在下一行數據org.h2.mvstore.Page.Leaf#getValue獲取當前值org.h2.mvstore.Cursor#next 獲取下一行數據org.h2.command.query.Select#isConditionMet判斷當前行是否滿足查詢條件
迭代索引獲取數據
Cursor#hasNext
方法主要用于判斷迭代器是否還有下一個元素。
如果有下一個元素,則更新 cursor 里的當前 index 和 當前 index 里對應的 lastValue。
在迭代時,如果到達頁面邊界時需要向上移動到父頁面(從父頁面再查找對應的孩子節點),當處于非葉頁面時需要向下移動到葉頁面,然后從葉子節點上根據 index 獲取 value。
相關代碼如下:
public boolean hasNext() {if (cursorPos != null) {int increment = reverse ? -1 : 1;while (current == null) { // 循環Page<K,V> page = cursorPos.page; // 當前頁面int index = cursorPos.index; // 從 pos 里獲取當前 indexif (reverse ? index < 0 : index >= upperBound(page)) { // 判斷是否到達當前page的邊界// traversal of this page is over, going up a level or stop if at the root alreadyCursorPos<K,V> tmp = cursorPos;cursorPos = cursorPos.parent; // 如果 index 達到當前頁的邊界,則向上回溯到父節點if (cursorPos == null) {return false;}tmp.parent = keeper;keeper = tmp;} else {// traverse down to the leaf taking the leftmost pathwhile (!page.isLeaf()) { // 如果當前頁不是葉子節點,則向下遍歷到葉子節點page = page.getChildPage(index);index = reverse ? upperBound(page) - 1 : 0;if (keeper == null) {cursorPos = new CursorPos<>(page, index, cursorPos);} else {CursorPos<K,V> tmp = keeper;keeper = keeper.parent;tmp.parent = cursorPos;tmp.page = page;tmp.index = index;cursorPos = tmp;}}if (reverse ? index >= 0 : index < page.getKeyCount()) {K key = page.getKey(index); // 獲取當前 page 指定 index 里存儲的 keyif (to != null && Integer.signum(page.map.getKeyType().compare(key, to)) == increment) {return false;}current = last = key;lastValue = page.getValue(index); // 根據 key 獲取 valuelastPage = page;}}cursorPos.index += increment; // 增加 index, 移動到下一個位置}}return current != null;
}