使用 1 2 3 4 String sql = "SELECT * FROM test where id > 1 and name='name3'" ;ResultSet rs = stmt.executeQuery(sql);
查询 case:
1 2 3 4 主键查询 覆盖索引查询 索引查询+回表 多表关联查询
功能 模块 类 总体流程 1 2 3 4 5 6 7 8 9 10 11 12 13 Select 语句 SQL 解析 Select 语句 SQL 查询优化 计算执行计划的成本 创建查询优化器 优化查询计划 计算最佳查询计划 测试并更新最优查询计划 创建计划 计算计划成本 先计算 MVPrimaryIndex 查询计划的成本 再获取所有候选的索引计算对应的查询计划 更新成本最低的查询计划并返回 Select 语句执行
解析&优化 H2 在执行查询操作之前,会根据不同索引计算对应的成本,然后选择成本最小的索引进行后续查询操作。 单表查询时,会获取表的所有索引。按照主键索引、二级索引的顺序依次计算对应的成本。 然后选择成本最小的索引作为查询计划。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 org.h2.command.Parser#parse 解析sql org.h2.command.query.Query#prepare 优化sql org.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 查询条件,最终决定是否要把该行返回给客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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#prepare org.h2.mvstore.db.MVPrimaryIndex#find 从 index 里获取 row, 比如 primary index org.h2.mvstore.db.MVPrimaryIndex#getMap 获取 transaction map org.h2.mvstore.tx.TransactionMap#entryIterator 根据不同隔离级别创建不同迭代器,比如 CommittedIterator org.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。 相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public boolean hasNext () { if (cursorPos != null ) { int increment = reverse ? -1 : 1 ; while (current == null ) { Page<K,V> page = cursorPos.page; int index = cursorPos.index; if (reverse ? index < 0 : index >= upperBound(page)) { CursorPos<K,V> tmp = cursorPos; cursorPos = cursorPos.parent; if (cursorPos == null ) { return false ; } tmp.parent = keeper; keeper = tmp; } else { while (!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); if (to != null && Integer.signum(page.map.getKeyType().compare(key, to)) == increment) { return false ; } current = last = key; lastValue = page.getValue(index); lastPage = page; } } cursorPos.index += increment; } } return current != null ; }
代码流程 参考