H2 Database Select 语句执行流程

发布于:2025-05-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

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 
优化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 查询条件,最终决定是否要把该行返回给客户端。

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。
相关代码如下:

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 里获取当前 index
            if (reverse ? index < 0 : index >= upperBound(page)) { // 判断是否到达当前page的边界
                // traversal of this page is over, going up a level or stop if at the root already
                CursorPos<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 path
                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); // 获取当前 page 指定 index 里存储的 key
                    if (to != null && Integer.signum(page.map.getKeyType().compare(key, to)) == increment) {
                        return false;
                    }
                    current = last = key;
                    lastValue = page.getValue(index); // 根据 key 获取 value
                    lastPage = page;
                }
            }
            cursorPos.index += increment; // 增加 index, 移动到下一个位置
        }
    }
    return current != null;
}

网站公告

今日签到

点亮在社区的每一天
去签到