#database # 1. Cost model - 通过预估来决定哪个 plan 更好(不需要实际运行)。 - 是一种内部统计,数值在不同 DBMS 之间不具有可比性。 - 预估可以通过统计不同资源的消费: - **CPU**:开销小,很难估算 - **Disk**:多少 block transfers - **Memory**: DRAM读取量 - **Network**:多少 messages - 预估读/写了多少 tuples --------- # 2. Statistics - 为了实现预估成本,DBMS在内部 catalog 里存储了关于 tables、attributes、indexes 等内部统计信息。 - 不同的系统可能在不同时机更新统计信息,也可以通过手动方式触发。 > 更新一般需要全表扫描,代价很大,对于 OLTP 系统可能选择在请求低峰时进行。 - 对于每个关系 R,DBMS 维护了以下信息: - $N_R$:R 中有多少 tuples - $V(A, R)$:R 中关于列 A 有多少 distinct values ## 2.1 Derivable Statistics - **Selection cardinality** - 记作 $SC(A,R)$ ,表示列 A 等于某一个值的平均有多少行记录。即 $N_R/V(A,R)$。 - 问题:**data uniformity** ## 2.2 Selection Statistics - unique keys 的相等条件容易预估: ```SQL SELECT * FROM people WHERE id = 123; ``` 这是简单的 predicate。 ### Complex Predicates - 一个谓词 **P** 的 selectivity(**sel**) 是值符合条件的元组数量与总数量之比。 - 计算方式取决于谓词的类型: - 相等(Equality) - 范围(Range) - 不等(Negation) - Conjunction - Disjunction - **Equality Predicate** - 谓词为列A = constant,如 `SELECT * FROM people WHERE age = 2` - $\large sel(A=constant)\ =\ SC(P)\ / \ N_R$ // 假设分布均匀 - **Range Predicate** - $\large sel(A >= a) \ = \ (A_{max}-a)\ / \ (A_{max}-A_{min})$ - **Negation Predicate** - $\large sel(not\ P)\ = \ 1- sel(P)$ > $\large Selectivity\ \approx \ Probability$ - **Conjunction** - $\large sel(P1 \wedge P2) = sel(P1) \times sel(P2)$ - 假定谓词之间是相互独立的(independent) - **Disjunction** - $\begin{aligned} \large &sel(P1 \vee P2) \\ &\ \ = sel(P1) + sel(P2) - sel(P1 \wedge P2) \\& \ \ = sel(P1) + sel(P2) - sel(P1) \times sel(P2) \end{aligned}$ - 同样, 假定谓词之间是相互独立的(independent) - 前面讨论的假定前提条件: - **Uniform** **Data**:每个 value 的分布是相同的 - **Independent Predicates** - **Inclusion Principle**:join keys 的 domain 是重叠的,即 inner relation 的每个 key 在 outer 中也有对应的 ### Correlated Attributes ^1b06de 示例:一个关于汽车的 database - Makes = 10,Models=100 考虑以下查询: - `(make = "Honda" AND model = "Accord")` 如果假定谓词之间相互独立且分布均匀,则 selectivity 为: $1/10 \times 1/100 = 0.001$ 而实际上只有 Honda 会生产 Accord 型号,所以实际的 selectivity 为: $1/100 = 0.01$ ## 2.3 Cost Estimations ### Histograms - **背景**: - 数据往往分布不均匀; - 为每个 distinct value 维护一个 count 代价非常高; - **Histogram Ranges** - 每 N 个 values (width)划分为一个 bucket,统计该 bucket 内所有 values 出现的次数(counts)。 - 问题:bucket 内可能有些 values 特别频繁,有些特别不频繁,造成有些 values 统计失真严重 - **Quantiles** - bucket 的宽度 width 是变化的,让每个 bucket 的 counts 总和是几乎一样的 - ![](https://img.jonahgao.com/oss/note/2025p1/cmu_dbms_opt_hist_bucket.png) <br> ### Sampling - smaller copy of table(如每个100行采样一条数据) <br> - 用途:估算 selectivities <br> - 对比直方图: - 直方图更快,Sampling使用时需要 sequential scan > SQL Server:简单查询使用直方图,复杂查询使用 smapling ----------- # 3. Query Optimization - 执行完 RBO后,DBMS 会枚举不同的 plan,然后估算它们的成本: - **单表**,如 predicate 顺序选择 - **多表**,如 N-way join - **嵌套子查询** 然后穷举所有 plans 或者到达一定时限后 来挑选最好的 <br> ## 3.1 Single Relation Query Planning - 挑选最好的 access method: - 顺序扫描 Sequential Scan - 二分搜索(clustered indexes) - Index scan - Predicate evaluation ordering:谓词应用的顺序选择 ### OLTP Query Planning - 比较简单,一般查询的数据很小,且常常是单点查询。 > sargable( Search Argument Able) - 通知只需要挑选最好的索引就行 - Join 几乎都是基于外键,且基数很小 - 可以基于一些启发式机制来实现 ## 3.2 Multi-Relation Query Planning - Join 变多,可供选择的 plans 急速变大 > **需要限制 search space** - Join 基本策略:只考虑 left-deep join trees - System R 中实现 - ![](https://img.jonahgao.com/oss/note/2025p1/cmu_dbms_opt_join_ldt.png) - 优势:允许 fully pipelined plans,中间结果不需要写入临时文件。 > 但不是所有的 left-deep tree 都是 fully pipelined。 - **如何枚举 query plans**: - 枚举 orderings(逻辑层面) - 如多表 Join 顺序 - 为每个 operaotr 枚举 plan - Hash、Sort-Merge、Nested Loop,... - 枚举每个 table 的 access paths - Index、Seq Scan - 使用 **dynamic programming** 来减少 cost estimations 的数量 - 每次挑选 lowest cost for path ## 3.3 Postgres Optimizer 支持两种: - Dynamic Programming - Genetic Query Optimizer(GEQO。复杂场景下使用) > 查询中的 tables 数量小于 12 时使用第一种,大于等于 12 时使用 GEQO。 ![](https://img.jonahgao.com/oss/note/2025p1/cmu_dbms_opt_pg_2019.png) - 靠随机生成第一代 - 每一代剔除最差的,同时记录下最好的(跟全局最好的进行比较更新) - 剩下的相互混合,生成下一代 -------------- # 4 Nested Sub-Queries(Rewrite) - DBMS 将 Where 语句中的嵌套子查询看作是一个函数: - 接受参数 - 返回一个或者一组 values - 两种方式(rewrite阶段,不需要 CBO): - Rewrite to de-correlate and/or flatten them - Decompose nested query and store result to temporary table - **de-correlate** ```SQL SELECT name FROM sailors AS S WHERE EXISTS ( SELECT * FROM reservers AS R WHERE S.sid = R.sid AND R.day = '2018-10-15' ) ``` 重写为 Join: ```SQL SELECT name FROM sailors AS S, reservers AS R WHERE S.sid = R.sid AND R.day = '2018-10-15' ``` - **decomposing** ![](https://img.jonahgao.com/oss/note/2025p1/cmu_dbms_nest_block_2019.png) - 将子查询执行,替换为执行结果。 实现方式:optimizer 将查询分割为 blocks,然后连接不同 blocks - 好处:子查询不用每遍都执行。