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;
}