Skip to content

Commit 918cb07

Browse files
committed
add system R
1 parent 394559f commit 918cb07

File tree

4 files changed

+176
-17
lines changed

4 files changed

+176
-17
lines changed
198 KB
Loading
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
IBM System R 优化器的总体执行步骤是:
2+
3+
1. 执行所有的 RBO
4+
2. 为每个单表选出最佳的 Access Path(Seq Scan 或者 Index Scan)。
5+
3. 采用左深树动态规划确定 join 顺序
6+
7+
因为后两步都是 CBO,所以这篇博客主要讲讲 System R 的代价模型。
8+
9+
## 查询结果规模估算
10+
System R 使用 **selectivity factor** 表示一个谓词命中的概率,下面用 $\text{F}$ 指代 **selectivity factor**。$\text{F}$ 是一个 $[0,1]$ 的值,如果 $\text{F}(\text{cond}_i)$ 的值越小,表示 $\text{cond}_i$ 满足的概率越小(过滤能力越强)。
11+
12+
假设一个表 $T$ 有 $\text{NCARD}(T)$ 个元组,则 $\text{NCARD}(T) \times \text{F}(\text{cond}_i)$ 表示应用谓词 $\text{cond}_i$ 之后筛选出的结果大小。
13+
14+
在谓词下推之后,每个表的上方都是是一种主合取范式的形式 $\text{cond}_1 \land \text{cond}_2 \land \text{cond}_3 \ ...$,应用所有谓词之后的结果大小就是 $\text{NCARD}(T) \times \prod \text{F}(\text{cond}_i)$
15+
16+
System R 的统计信息包括每个表的元组数量、每个索引不同 key 的数量、每个索引最大的 key 和最小的 key 等。为了对每个谓词模式计算出恰当的 $\text{F}$ 值,System R 还有一个重要的假设:
17+
18+
> This assumes an even distribution of tuples among the index key values.
19+
>
20+
> 假设所有索引中的 key 是均匀分布的。
21+
22+
$F$ 值的计算方式如下:
23+
24+
1. column = value
25+
26+
- 如果 `column` 有可用的索引, $\text{F} = \frac{1}{\text{ICARD(column index)}}$,其中 $\text{ICARD(I)}$ 表示索引 $\text{I}$ 中不同 key 的个数
27+
28+
- 否则,$\text{F} = \frac{1}{10}$
29+
30+
2. column1 = column2
31+
32+
- 如果 `column1``column2` 都有可用的索引, $\text{F} = \frac{1}{\max(\text{ICARD(column1 index)},\text{ICARD(column2 index)})}$
33+
34+
- 如果只有 `column-i` 有可用的索引, $\text{F} = \frac{1}{\text{ICARD(column-i index)}}$
35+
36+
- 否则,$\text{F} = \frac{1}{10}$
37+
38+
39+
3. column > value
40+
41+
- 如果`column` 有可用的索引、 且 `column` 是算术式、且 `value` 的值固定, $\text{F} = \frac{\text{max key} - \text{value}}{\text{max key} - \text{min key}}$
42+
43+
- 否则,$\text{F} = \frac{1}{3}$
44+
45+
这里第二种情况包括:`value` 值不固定、`column` 没有可用的索引、`column` 不是算术式(例如字符串,无法相减)等。取 $\text{F} = \frac{1}{3}$ 这个值是因为假定了用户的过滤条件能过滤掉至少一半的元组,但这个值又会比 `column = value` 的 $\frac{1}{10}$ 大一些。
46+
47+
4. column BETWEEN value1 AND value2
48+
49+
- 如果`column` 有可用的索引、 且 `column` 是算术式、且 `value1``value2` 的值固定, $\text{F} = \frac{\text{value2} - \text{value1}}{\text{max key} - \text{min key}}$
50+
- 否则,$\text{F} = \frac{1}{4}$
51+
52+
选取 $\frac{1}{4}$ 要比 `column > value` 的 $\frac{1}{3}$ 小一些。
53+
54+
5. column IN (list)
55+
- $\text{F} = \text{len(list)} \times \text{F}(\text{column = value})$
56+
57+
58+
6. columnA IN subquery
59+
- $\text{F} = \frac{\text{子查询的估计规模}}{\text{子查询 FROM 中所有的表的元组的乘积}}$
60+
61+
62+
7. (pred1) OR (pred2)
63+
- $\text{F} = \text{F(pred1)} + \text{F(pred2)} - \text{F(pred1)} \times \text{F(pred2)}$
64+
65+
66+
8. (pred1) AND (pred2)
67+
- $\text{F} = \text{F(pred1)} \times \text{F(pred2)}$
68+
69+
70+
9. NOT pred
71+
- $\text{F} = 1 - \text{F(pred)}$
72+
73+
74+
75+
## Access Path 成本估算
76+
77+
对于一个单表,可以选择用 Index Scan 或者 Seq Scan,每种方式称为一条 Access Path,Access Path 的成本估算的通用公式为:
78+
$$
79+
\text{COST} = \text{PAGE FETCHES} + W \times (\text{RSI CALLS})
80+
$$
81+
其中,$\text{PAGE FETCHES}$ 表示 IO 了多少个页面(估计值),$\text{RSI CALLS}$(也即 $\text{RSICARD}$ ) 表示应用了所有可搜索(sargable,原文有详细的定义)的谓词之后得到的元组数量(估计值),$W$ 是一个系数,用来量化磁盘 IO 和计算的开销倍数。
82+
83+
总的来说,这个公式就是综合了磁盘 IO 的成本和 CPU 计算的成本。
84+
85+
选用不同的索引或全表扫描,带来的成本也是不同的,但本质都是上面公式的特例,具体如下:
86+
87+
1. 如果有一个等值谓词,且该列有唯一键索引,则 $\text{COST} = 1 + 1 + W$
88+
89+
这里假定 B+ 树的非叶子节点都在内存池中,所以最多 IO 一个叶子节点索引页、一个数据页,以及一次 RSI CALL。
90+
91+
2. 如果有聚集索引 $\text{I}$ 命中了一个或多个谓词,则 $\text{COST} = \text{F(preds)} \times (\text{NINDS(I)} + \text{TCARD}) + W \times \text{RSICAR}$,其中 $\text{NINDS(I)}$ 表示索引 $\text{I}$ 的页面数量,$\text{TCARD}$ 表示这个表的页面数量。
92+
93+
这个公式直接用 $\text{F(preds)}$ (这里的 $\text{preds}$ 指的是命中索引的谓词,而不是所有可搜索谓词)作为页面的比例。
94+
95+
3. 如果有辅助索引 $\text{I}$ 命中了一个或多个谓词,分两种情况:
96+
97+
- 如果内存池装得下足够多的页面,则 $\text{COST} = \text{F(preds)} \times (\text{NINDS(I)} + \text{TCARD}) + W \times \text{RSICAR}$,和聚集索引一致。
98+
99+
- 否则,$\text{COST} = \text{F(preds)} \times (\text{NINDS(I)} + \text{NCARD}) + W \times \text{RSICAR}$,其中 $\text{NCARD}$ 表示这个表的元组数量。
100+
101+
这里因为内存池装不下,且是辅助索引,最坏的情况就是每读一个元组都要把数据页 IO 进来,所以最坏就是有 $\text{NCARD}$ 次页面 IO。
102+
103+
4. 如果有聚集索引 $\text{I}$ 没有命中任何谓词,则 $\text{COST} = (\text{NINDS(I)} + \text{TCARD}) + W \times \text{RSICAR}$
104+
5. 如果有辅助索引 $\text{I}$ 没有命中任何谓词,分两种情况:
105+
- 如果内存池装得下足够多的页面,则 $\text{COST} = (\text{NINDS(I)} + \text{TCARD}) + W \times \text{RSICAR}$,和聚集索引一致。
106+
- 否则,$\text{COST} = (\text{NINDS(I)} + \text{NCARD}) + W \times \text{RSICAR}$
107+
108+
6. 全表扫描,$\text{COST} = \frac{\text{TCARD}}{P} + W \times \text{RSICAR}$,这里 $P$ 表示这个表在一个 Segment 的非空页面比例,所以把这个 $\frac{\text{TCARD}}{P}$ 的含义就是表所在的 Segment 的非空页面数量。(受当时计算机硬件的限制,System R 会把很多个表放在同一个 Segment,所以全表扫描实际是扫的一整个 Segment)
109+
110+
## 有趣顺序
111+
112+
原文是 **interesting order**,指的是对哪些列排序感兴趣,有三种来源:
113+
114+
1. Order By 中的列
115+
2. Group By 中的列
116+
3. 出现在 Join 条件中的列
117+
118+
一个表并不是只考虑一种 Access Path,而是考虑所有的有趣顺序,这是因为如果一个表以某个有趣顺序扫描出来的话,就可以免去后续的排序开销。(当时没有哈希的 Group By 和 Hash Join,所以对于后两种,也被视为有趣的顺序)。
119+
120+
每个表需要考虑所有需要用到的有趣顺序,估算出对应的成本。对于单表来说,获得一种有趣的顺序有两种办法:通过对应的索引扫描,或者通过其他扫描方式(包括全表扫描),再加上排序的成本。
121+
122+
## 多表 Join 成本估算
123+
124+
经典的自下而上的动态规划,由于还需考虑后续的有趣顺序,所以实现上,应该是用 $dp[s][i]$ 表示状态为 $s$ 的表已经 Join,且有趣的顺序为 $i$ 的最低成本。
125+
126+
在 dp 的时候可以应用以下规则减少搜索空间:
127+
128+
1. 尽量避免会产生笛卡尔积的 Join。
129+
2. 只考虑左深树以减少时间复杂度。(受限于当时的硬件,如果不用左深树,一方面时间复杂度高,另一方面 Join 之后的中间表会变的很大,不用左深树就意味着会有两个以上的大表,内存池可能装不下,如果把大表刷回磁盘就又会有开销)
130+
131+
System R 发表时还没有哈希 join,join 的方式只有两种:NLJ 和 Sort Merge Join:
132+
133+
### NLJ 成本估算
134+
135+
考虑外表和内表,设外表满足所有可搜索谓词的元组数量为 $N$,则 $N = \text{外表所有关系的元组数量的乘积} \times \text{外表所有可搜索谓词的 F 值}$
136+
137+
可以理解为,$N$ 就是外表预计产生的元组数量。NLJ 的成本公式为:
138+
$$
139+
C_\text{NLJ(pathl,path2)}= C_\text{outer}(\text{path1}) + N * C_\text{inner}(\text{path2})
140+
$$
141+
这个公式比较简单,外层扫出 $N$ 条,然后扫内层即可。
142+
143+
### Merge Join 成本估算
144+
145+
Merge Join 可以获得有趣的顺序,但 Merge Join 的前提是内表和外表都是有序的。和前面所说的一样,要想获得有序的表,要么在 dp 的时候直接使用这个有趣的顺序,要么就在其他顺序中选一个排序之后成本最低的参与计算。成本公式为:
146+
$$
147+
C_\text{Merge(pathl,path2)}= C_\text{outer}(\text{path1}) + \text{InnerPage} + N \times W \times \text{RSICAR}
148+
$$
149+
上述公式忽略了排序的成本。(这个公式和原文是等价的)
150+
151+
## 总结
152+
153+
作为最经典的优化器论文,System R 提供了一个简单、切实可行的优化器架构。许多现代优化器都能看到 System R 的影子。
154+
155+
其优点有:
156+
157+
1. 不仅涵盖了 RBO,而且有 CBO,能搜索出相对较好的连接方式。
158+
2. 介绍了一种简单可行的代价模型。
159+
3. 能够优化多表 join 场景,对于嵌套子查询也能优化一部分。
160+
161+
其缺点有:
162+
163+
1. 代价模型认为所有 key 是均匀分布的,这不符合实际情况,需要更加复杂的统计信息、代价模型。
164+
165+
2. 在处理嵌套子查询的时候,System R 实际是对多个子查询进行分解,也就是先物化内层的子查询,然后才会开始上层的优化(成本估算)。这种方式有时候效果并不好(因为会多出一些 IO 的成本)
166+
167+
详细如图(图片来自 cmu 15-799)
168+
169+
![1](././SystemR-1.png)
170+
171+
172+
System R 的执行器是会物化中间表,而不是像 Volcano 一样一次只返回一条数据,就会有这样的问题

docs/index.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,5 @@
1010
1111
最近更新:
1212

13+
- (20250521) [System R 优化器框架精读](./dev/db/optimizer/System_R/SystemR.md)
1314
- (20250325) [mini-lsm week1 思考小结](./dev/db/mini-lsm/week1.md)
14-
- (20250109) [ABC386(A-F) 题解](./algorithm/AtCoder/abc386.md)
15-
- (20250109) [ABC385(A-F) 题解](./algorithm/AtCoder/abc385.md)
16-
- (20250102) [ABC384(A-G) 题解](./algorithm/AtCoder/abc384.md)
17-
- (20250102) [2024总结 & 2025计划](./essay/diary/2025-00.md)
18-
- (20250101) [ABC383(A-F) 题解](./algorithm/AtCoder/abc383.md)
19-
- (20241231) [ABC382(A-F) 题解](./algorithm/AtCoder/abc382.md)
20-
- (20241230) [C++ 算法模板 for VSCode](./algorithm/cpp_templete.md)
21-
- (20241230) [ABC381(A-F) 题解](./algorithm/AtCoder/abc381.md)
22-
- (20241228) [ABC380(A-G) 题解](./algorithm/AtCoder/abc380.md)
23-
- (20241226) [ABC379(A-G) 题解](./algorithm/AtCoder/abc379.md)
24-
- (20241224) [ABC378(A-F) 题解](./algorithm/AtCoder/abc378.md)
25-
- (20241223) [ABC377(A-G) 题解](./algorithm/AtCoder/abc377.md)
26-
- (20241221) [ABC376(A-G) 题解](./algorithm/AtCoder/abc376.md)
27-
- (20241219) [ABC375(A-G) 题解](./algorithm/AtCoder/abc375.md)
28-
- (20241216) [ABC374(A-G) 题解](./algorithm/AtCoder/abc374.md)
29-
- (20241210) [ABC373(A-G) 题解](./algorithm/AtCoder/abc373.md)

mkdocs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ nav:
6060

6161
- 开发:
6262
- dev/index.md
63-
- 数据库与存储:
63+
- 数据库:
64+
- CMU 15-799 | 优化器:
65+
- System R 论文精读: dev/db/optimizer/SystemR.md
6466
- CMU 15-445:
6567
- 缓冲池: dev/db/15445/buffer_pool_manager.md
6668
- 常见哈希索引方案介绍: dev/db/15445/hash.md

0 commit comments

Comments
 (0)