使用

1
2
3
4
// 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:

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

代码流程

参考