代价模型

共 22 题
📑 题目列表 22 题
#
★★★

1. MySQL 的代价模型,query_cache、engine_condition_pushdown 的影响?

MySQL 的代价模型如何评估查询?optimizer_switch 中的 engine_condition_pushdown(索引下推 ICP)对代价与执行有什么影响?query_cache 对代价模型有什么历史影响?

  • MySQL 代价模型:估算行数 × 块读取与行评估代价
  • ICP(index_condition_pushdown)下推过滤减少回表,EXPLAIN 显示 Using index condition
  • query_cache 已在 8.0 移除,命中时无需执行计划

MySQL 优化器按"估算行数 + 代价系数"评估:全表扫描代价 ≈ 读取页数 × 每块读取代价,索引扫描还需叠加回表代价。ICP(optimizer_switch 的 index_condition_pushdown,默认 ON)把"索引无法定位但属于索引列的非前缀条件"下推到存储引擎,在索引扫描时提前过滤,只有通过过滤的行才回表,EXPLAIN 显示 Using index condition——代价模型中"回表行数"的估算随之下降,这是 5.6 以来对二级索引扫描最有效的优化之一。

query_cache(8.0 已移除)的历史影响:命中缓存时直接返回结果、完全不产生执行计划,谈不上代价评估;但失效机制(表更新即整库清相关项)引发争用与抖动,8.0 彻底移除后代价评估更加纯粹,也消除了"缓存命中率"对调优的干扰。理解这一点有助于解释为何老调优文档中的 query_cache 建议已不适用。

本题考察代价模型的两个外部因素:ICP 通过减少回表行数进入代价公式,query_cache 则是"绕过代价模型"的历史通道。回答时区分"影响代价"与"绕过代价"两类机制。

#
★★★

2. PostgreSQL 的 seq_page_cost、random_page_cost、cpu_tuple_cost 等参数的语义?

PostgreSQL 代价模型中的 seq_page_cost、random_page_cost、cpu_tuple_cost 等参数分别表示什么?它们如何影响计划选择?

  • 各参数语义与默认值:seq_page_cost=1.0、random_page_cost=4.0、cpu_tuple_cost=0.01 等
  • random_page_cost 调低使索引/随机访问路径更受青睐(SSD 场景)
  • 总代价 = 页数×页代价 + 行数×行处理代价 + 算子代价

这些参数构成 PG 代价模型的权重:seq_page_cost(顺序读一个数据页的代价,默认 1.0,是代价基准)、random_page_cost(随机读一页,默认 4.0,反映 HDD 机械寻道昂贵;SSD 上应调低至 1.1 左右,否则优化器系统性偏向顺序扫描而回避索引扫描)、cpu_tuple_cost(处理一行元组 0.01)、cpu_index_tuple_cost(处理一行索引项 0.005)、cpu_operator_cost(一次表达式/比较求值 0.0025)。

总代价 = 读取页数 × 对应页代价 + 处理行数 × 行代价 + 算子求值代价。调低 random_page_cost 后,索引扫描、位图扫描等随机访问路径的总代价下降,优化器更倾向于选择它们;反之调高则更保守。这些参数按库级/会话级可调,调整前应确认存储介质(HDD/SSD/NVMe)与真实 workload。

本题考察 PG 代价参数的语义与默认值:先逐个解释参数,再给"页代价+行代价+算子代价"的总公式,最后落到 random_page_cost 与存储介质的匹配。

#
★★★

3. 不同操作的代价系数,Nested Loop、Hash Join、Sort、Aggregate 的代价?

Nested Loop、Hash Join、Sort、Aggregate 等操作的代价如何构成?彼此之间的大致关系是什么?

  • Nested Loop:外层行数 × 内层探测代价,乘积膨胀快
  • Hash Join:建表 + 探测近似线性,work_mem 溢出时落盘暴涨
  • Sort 与 Aggregate 的 n log n / 线性代价特征

Nested Loop 代价 ≈ 外层扫描 + 外层行数 × 内层单次探测代价(索引探测含随机读,受 random_page_cost 影响),随内外层行数乘积膨胀,适合"驱动集小 + 内层有高效索引"。Hash Join 代价 ≈ 读入内层 + 建哈希表(每行哈希计算与插入)+ 外层逐行探测,整体 O(|A|+|B|) 近似线性;但哈希表超出 work_mem 后分批落盘(batch 溢出),引入磁盘 I/O 使代价超线性上升。

Sort 代价 ≈ 读入全量行 + n log n 次比较 + 内存不足时外部归并的临时文件 I/O;Aggregate 分 HashAggregate(线性)与 SortAggregate(需先排序 n log n),小数据量下排序分组可能更便宜。理解各算子代价构成,就能解释优化器在临界点切换计划的动机:行数小时 NL 胜、行数大时 Hash 胜、输入有序时 Merge 胜。

本题考察算子代价的数学模型:NL 是乘积、Hash 是线性、Sort 是 n log n。回答时给出各自的代价来源与"临界点切换"的应用含义。

#
★★★

4. 代价模型(Cost Model)的组成,CPU、磁盘 I/O、网络 I/O 的代价权重?

代价模型由哪些部分构成?CPU、磁盘 I/O、网络 I/O 的代价权重如何配置与权衡?

  • 单机代价构成:磁盘 I/O(页读取)+ CPU(行处理、比较、哈希)
  • 分布式数据库必须计入网络 shuffle 代价
  • 权重配置:PG 的 cost 参数、MySQL 的 server_cost/engine_cost 表

代价模型通常分三类资源:磁盘 I/O(顺序/随机页读)、CPU(元组处理、比较运算、哈希计算)、网络(分布式架构下的数据传输与 shuffle)。单机数据库中 I/O 权重占主导:PG 以"页"为单位给出 seq/random_page_cost,CPU 代价是次要项(cpu_tuple_cost 等);MySQL 8.0 通过 mysql.server_cost 与 mysql.engine_cost 表配置行评估与块读取的单位代价。

分布式数据库(TiDB、StarRocks 等)必须把网络传输计入代价:Join 重分布(shuffle)的字节量与耗时可能主导计划选择,如"广播小表 vs 重分布大表"的决策本质上就是网络代价比较。优化的本质是在"多花 CPU 少花 I/O"(哈希、排序避免重复随机读)与相反策略间折中;权重校准依赖真实硬件与 workload,调整后必须用基准验证。

本题考察代价模型的资源维度:单机看 I/O 与 CPU 的折中,分布式叠加网络。回答时给出三类资源、两种配置入口与校准原则。

#
★★★

5. 基于规则的改写(RBO)如子查询展开(unnest)、谓词推导、外连接消除与 Join 消除各自的适用条件与前提(如无空值放大、约束保证唯一)是什么?

子查询展开、谓词推导、外连接消除与 Join 消除等基于规则的改写分别在什么条件下适用?各自的前提(无空值放大、唯一约束等)是什么?

  • 子查询展开(unnest)的前提:无 LIMIT、无聚合副作用、无易变函数
  • 外连接消除的前提:右表连接键唯一、无空值放大风险
  • Join 消除的前提:唯一约束保证不增行不删行;谓词推导基于等值传递

子查询展开(unnest/decorrelation)把 IN/EXISTS 子查询改写为半连接/反半连接,前提是子查询无 LIMIT、无聚合、无易变函数、无阻碍去相关的外层引用,改写后优化器可自由调整连接顺序(MySQL 的半连接转换、PG 的子查询提升均属此类)。外连接消除:若 LEFT JOIN 的右表在连接键上有唯一约束、且查询不依赖 NULL 补齐输出(即不会因右表无匹配而输出 NULL 行),可把外连接改写为内连接——前提是"无空值放大":连接键无重复、无 NULL 重复,否则改写后行数被错误放大。

Join 消除:被连接的表仅贡献可空列之外的列、且存在主键/唯一约束保证连接不增行不删行时,可从计划中删除该 join 及其扫描。谓词推导利用等值传递律(a=b 且 b=1 → a=1)与约束(CHECK/外键)生成更紧的过滤条件,并把谓词尽量下推减少上游行数。改写前提不满足时优化器保留原形式,计划可能明显变差——这是"改写失败难以察觉"的常见原因。

本题考察 RBO 改写的前提条件学:每个改写都有"安全前提",违反则结果错误或低效。回答时按改写类型给出适用条件(唯一约束、无空值放大、无副作用),体现对正确性的敬畏。

#
★★★

6. 优化器如何实现物化视图的自动查询改写(查询包含匹配 query subsumption、补偿谓词与聚合上卷)?为什么多数数据库需要显式开启且对改写失败难以诊断?

优化器如何对物化视图做自动查询改写?query subsumption、补偿谓词与聚合上卷各指什么?为什么多数数据库需要显式开启且改写失败难以诊断?

  • query subsumption:查询谓词范围被物化视图覆盖
  • 补偿谓词:查询更窄的部分附加在物化视图结果上再过滤
  • 聚合上卷:查询分组是视图分组的超集且聚合函数可叠加;多数库默认关闭与诊断困难

自动查询改写指优化器识别"用户查询可以从物化视图结果推导"并改写执行路径,包含三个匹配层次:① query subsumption——查询的谓词范围是物化视图定义谓词的子集(查询更窄),可先在物化视图上扫描;② 补偿谓词——查询比视图窄的条件作为补偿条件附加在视图结果之上再过滤;③ 聚合上卷——查询的 GROUP BY 列是视图分组列的父集(rollup)且聚合函数可叠加(SUM/COUNT 可加、AVG 不可直接叠加)时,可对视图的细粒度聚合结果再聚合。

多数数据库默认关闭或受限:Oracle 需物化视图属性与 QUERY REWRITE 权限、SQL Server 需 NOEXPAND 提示或成本比较、PG 无内置自动改写(依赖扩展或手动改写),因为正确性判定复杂(NULL 语义、聚合可加性、视图未刷新)且可能引入重复扫描。诊断难:改写失败通常无任何报错,只能通过 EXPLAIN 是否引用物化视图、对比计划成本与视图规模判断,配合视图刷新滞后检查。

本题考察物化视图改写的三层匹配机制与工程现实:先解释 subsumption/补偿谓词/上卷,再说明默认关闭的原因与"无报错失败"的诊断难点。

#
★★★

7. 代价模型对优化器选择的影响,全表扫描 vs 索引扫描?

优化器如何通过代价模型在全表扫描与索引扫描之间选择?哪些因素会改变天平?

  • 核心变量:估算选择率决定回表行数
  • 偏向索引的因素:覆盖索引、SSD 上降低 random_page_cost、统计准确
  • 偏向全表的因素:高 random_page_cost、低基数列、统计高估选择率

优化器比较"全表扫描代价(页数×seq_page_cost + 行数×行代价)"与"索引扫描代价(索引页随机读 + 回表行数×random_page_cost + 过滤代价)",核心变量是估算选择率:返回行数占比越低,索引扫描越占优;占比升高到一定程度(经验上约 5%~20%,取决于 random_page_cost 与缓存命中)时,随机回表代价超过顺序全扫,优化器转向全表扫描。

让天平偏向索引的因素:覆盖索引(免回表,代价只剩索引页读取)、SSD 上降低 random_page_cost、统计准确(避免高估选择率);偏向全表的因素:高 random_page_cost、数据集中在 OS 缓存使顺序预读极快、低基数列条件、统计高估选择率(陈旧/缺直方图)。理解天平逻辑,就能解释"同一 SQL 为何参数值不同计划不同"以及"统计刷新后计划突变"。

本题考察扫描方式选择的代价逻辑:选择率是主变量,代价参数与统计质量是修正项。回答时给出对比公式与两类影响因素。

#
★★★

8. 谓词上拉/下推与常量折叠如何改变执行计划?哪些改写必须小心外连接的 NULL 补齐语义与函数的副作用(非确定性函数)?

谓词下推、谓词上拉与常量折叠如何改变执行计划?为什么涉及外连接的 NULL 补齐语义与非确定性函数时必须谨慎?

  • 下推减行、上拉供外层利用、常量折叠在规划期求值
  • 外连接中 WHERE 条件下推会改变 NULL 补齐语义
  • 非确定性函数不能折叠、不能下推到循环内层重复求值

谓词下推把过滤条件尽量靠近数据源(扫描层、join 输入侧),减少中间结果行数与传输量;谓词上拉相反,把子查询内条件提升到外层(如 IN 子查询的范围条件上拉后供主查询走索引),常与子查询展开配合。常量折叠把可静态求值的表达式在规划期算好(WHERE id = 1+1 → id=2),减少运行时计算;对含 now() 等部分可折叠表达式则做部分折叠。

风险点:① 外连接中条件所属位置决定语义——ON 条件(右表侧)与 WHERE 条件(最终过滤)不同:把 WHERE 中的右表条件下推到右表扫描内会提前丢弃将补 NULL 的行,等价于悄悄改写成内连接,必须保证下推后行数不变(无空值放大)才合法;② 非确定性函数(random()、now()、uuid_generate 等)不能折叠,否则每次执行结果一致破坏语义;也不能下推到嵌套循环内层——否则每轮循环重新求值,行为与语义和求值次数绑定,结果不可预测。

本题考察改写正确性的红线:下推/折叠是"收益",NULL 补齐与副作用是"红线"。回答时给出两类风险的具体场景与规避原则。

#
★★★

9. Hash Join 的代价模型,哈希表内存预算与 work_mem 不足时的落盘行为,为什么其代价与输入规模近似线性、与 Nested Loop 的交叉点如何判断?

Hash Join 的代价模型如何构成?work_mem 不足时哈希表如何落盘?其代价为何近似线性,与 Nested Loop 的交叉点如何判断?

  • 建表 + 探测的线性代价 O(|A|+|B|)
  • work_mem 不足时分批落盘的 batch 溢出行为
  • 交叉点判断:驱动行数小且内层有索引时 NL 胜出

Hash Join 代价 ≈ 内层全扫描 + 哈希表构建(每行哈希计算与桶插入)+ 外层逐行探测(每行哈希 + 桶查找,期望 O(1)),总体 O(|A|+|B|) 近似线性——这正是它与 Nested Loop(O(|A|×|B|))的本质区别:行数越大,线性优势越明显。内存预算由 work_mem(PG)或 join buffer/临时表(MySQL 8.0)控制:哈希表超过预算时,PG 将内层数据按哈希键分成若干批(batch)写临时文件,分批处理并回读,引入磁盘 I/O 使代价超线性上升,执行时间可能恶化一个数量级。

交叉点判断:比较 NL 的"外层行数 × 内层探测代价"与 HJ 的"两表扫描 + 建表 + 探测 + 潜在落盘":驱动行数极小(如 100 行)且内层有高效索引时,NL 启动快、无哈希表开销而胜出;两表都大且等值连接时 HJ 胜出;输入已有序时 Merge Join 加入竞争。优化器在估算临界点附近频繁切换计划,正是计划抖动的高发区。

本题考察 Hash Join 的代价构成与场景决策:线性代价来源、落盘代价拐点、与 NL 的交叉判断。回答时给出公式与"小驱动集+索引→NL、大表→HJ"的决策规则。

#
★★★

10. MySQL optimizer_cost_model?

MySQL 的 optimizer_cost_model 是什么?如何查看与调整代价模型?

  • mysql.server_cost 与 mysql.engine_cost 表驱动的代价模型
  • 8.0 中 optimizer_cost_model 参数已移除,始终使用表驱动模型
  • UPDATE 代价表后需 FLUSH OPTIMIZER_COSTS 生效

MySQL 从 5.7 起把代价模型从硬编码改为表驱动:mysql.server_cost 存储服务层单位代价(如 row_evaluate_cost 默认 0.2、key_compare_cost 默认 0.05),mysql.engine_cost 存储引擎层代价(io_block_read_cost 默认 1.0、memory_block_read_cost 默认 0.25),可按库/引擎维度覆盖。旧参数 optimizer_cost_model 在 5.7 用于在"默认模型(1)"与"表驱动模型(2)"间切换,8.0 中该参数已移除,优化器始终使用表驱动模型。

调整方式:UPDATE mysql.server_cost SET cost_value = ... WHERE cost_name = 'row_evaluate_cost',然后执行 FLUSH OPTIMIZER_COSTS 使新值生效(不刷新则使用缓存值)。代价表修改是全局性变更:调大 io_block_read_cost 会让优化器更回避 I/O 重的计划,调大 row_evaluate_cost 会让它更在意扫描行数,需基于真实 workload 验证,避免"调了没测"。

本题考察 MySQL 代价模型的表驱动机制:两张代价表、参数历史(5.7 的切换参数在 8.0 移除)、生效方式(FLUSH OPTIMIZER_COSTS)。

#
★★★

11. MySQL 的代价模型差异?

MySQL 与 PostgreSQL 的代价模型有何差异?各自调优手段有何不同?

  • MySQL:块读取与行评估代价为主,不区分顺序/随机页权重
  • PG:显式区分 seq_page_cost 与 random_page_cost,算子代价细分
  • 调优入口差异:代价表/优化器开关 vs cost 参数与 target

两者都是"估算行数 × 单位代价",但构成不同:MySQL 以"块读取代价(io_block_read_cost/memory_block_read_cost)+ 行评估代价(row_evaluate_cost)"为主,不区分顺序读与随机读的页级权重——随机 I/O 的代价差异隐含在"估算需要读多少块"中;PG 显式区分 seq_page_cost(默认 1.0)与 random_page_cost(默认 4.0),随机读默认贵 4 倍,并把代价细分到元组处理(cpu_tuple_cost)、索引项(cpu_index_tuple_cost)与算子求值(cpu_operator_cost)。

后果与调优差异:同一硬件与 workload 下,两者对"索引扫描 vs 全表扫描"的偏好可能不同;MySQL 靠 index dive 让范围估算更准、靠代价表与 optimizer_switch 调优,PG 更依赖直方图、扩展统计与 cost 参数校准。迁移场景中"SQL 在 MySQL 走索引、在 PG 走全表"的常见原因之一正是两套代价参数与估算机制的差异。

本题考察跨引擎代价模型的比较:结构差异(块/行 vs 页/行/算子)与调优入口差异。回答时给出两套模型的具体参数与各自的调优手段。

#
★★

12. PostgreSQL 的 cost-based optimizer?

PostgreSQL 的 CBO 如何工作?从解析、重写到规划、执行的完整链路中代价模型处于什么位置?

  • 链路:parser → analyzer → rewriter → planner → executor
  • planner 生成多种路径并用代价函数比较选择
  • 动态规划枚举 join 顺序,连接数过多时回退 GEQO

PG 查询执行链路:解析(parser 生成语法树)→ 分析(analyzer 绑定语义、解析类型)→ 重写(rewriter 应用视图与规则)→ 规划(planner)→ 执行(executor)。planner 是 CBO 的核心:对每个基表生成访问路径(顺序扫描、索引扫描、位图扫描等),用对应 cost 函数估算每条路径;再通过动态规划枚举连接顺序与连接方法(Nested Loop/Hash/Merge),评估中间结果基数,剪枝保留有希望的候选;最终选择总代价最小的计划树。

代价函数的输入是统计信息(pg_class.reltuples/relpages、pg_statistic 的分布)与代价参数(seq/random_page_cost、cpu_* 代价);重写阶段(视图合并、谓词下推)先改变计划空间,规划阶段再在空间内搜索。当 FROM 表数超过 geqo_threshold(默认 12)时启用遗传算法近似搜索,避免动态规划的组合爆炸——这是 PG 在"搜索精度"与"规划时间"间的自动权衡。

本题考察 PG 优化器的整体架构:链路各阶段职责、代价模型在 planner 中的位置、动态规划与 GEQO 的分工。

#
★★

13. 代价模型在 JOIN 顺序选择?

代价模型如何决定 JOIN 顺序?为什么"小表驱动大表"不一定总是最优?

  • join 顺序影响中间结果大小与 join 方法代价
  • Nested Loop 下小表驱动成立,Hash Join 的策略是小表建哈希表
  • 统计失真时顺序选择失误,需 Hint/改写兜底

优化器用动态规划枚举可能的连接顺序与形状(左深/右深/稠密树),对每个中间结果估算基数,按 join 方法代价函数计算总代价取最小:Nested Loop 用"外层行数 × 内层探测代价",Hash Join 用"建表 + 探测"。经验法则"小表驱动大表"只在 Nested Loop 下严格成立(外层循环次数少);对 Hash Join 而言,正确策略是"小表建哈希表、大表探测"——哈希表越小越不易内存溢出,因此即使候选顺序不同,代价模型也会自然偏向小表建表的方向。

"小表驱动"失效的场景:统计不准确(join 选择率被低估/高估)导致中间结果基数误判、内层有可复用索引(大表驱动反而省事)、Hash Join 内存预算充足时两表顺序对代价影响有限。此时用 Hint(PG pg_hint_plan 的 Leading()、MySQL 的 JOIN_ORDER)或改写 SQL(子查询物化)兜底,但根因仍是统计与基数校准。

本题考察 join 顺序决策的代价逻辑:区分 NL 与 Hash 两种方法的"驱动"含义,并点出经验法则的适用边界与统计依赖。

#
★★

14. SSD vs HDD 的代价权重差异,random_page_cost 从 4 调为 1.1?

SSD 与 HDD 的随机/顺序 I/O 代价差异如何影响参数调优?为什么 SSD 上常把 random_page_cost 从 4 调为 1.1?

  • HDD 随机读有寻道与旋转延迟,比顺序读贵一个数量级
  • SSD 随机读与顺序读延迟差距缩小到约 1.1~2 倍
  • random_page_cost 默认 4 针对 HDD,SSD 下调至 1.1 左右并用 workload 验证

HDD 的随机读需要磁头寻道与盘片旋转,单次延迟约 5~10ms,比顺序读贵一到两个数量级,因此 PG 把 random_page_cost 默认设为 4.0(随机读一页的代价是顺序读的 4 倍)。SSD 无机械寻道,随机读与顺序读的延迟差距缩小到约 1.1~2 倍;若保持默认 4.0,优化器会高估随机访问代价、系统性回避索引扫描与位图扫描,导致低选择率查询也走全表。

生产经验:全 SSD 集群常把 random_page_cost 调至 1.1 左右(NVMe 甚至可到 1.0),使索引路径重新进入竞争。注意 1.1 是经验起点而非普适真理:并发下的 4K 随机小 I/O 仍可能劣于顺序大块读取,且该参数只影响相对权重(与 seq_page_cost 之比),调参后必须用真实 workload 对比计划变化与 P95 延迟验证。

本题考察代价参数与硬件的匹配:默认 4.0 的物理依据、SSD 下调至 1.1 的原因与验证方法,避免机械套用数值。

#
★★

15. 代价估算的公式,rows × cost_per_row + pages × cost_per_page?

代价估算的基本公式是什么?"rows × cost_per_row + pages × cost_per_page"如何理解?

  • 通用公式:总代价 = 页数×每页代价 + 行数×每行代价 + 算子开销
  • PG 中对应 seq/random_page_cost 与 cpu_tuple_cost/cpu_operator_cost
  • 行数与页数来自统计估算,公式本身是近似模型

代价估算的本质公式:Cost ≈ pages × cost_per_page + rows × cost_per_row + operator_overhead。第一项覆盖 I/O:读取的页数乘以每页代价(顺序读用 seq_page_cost、随机读用 random_page_cost 加权);第二项覆盖 CPU:处理的行数乘以每行代价(PG 为 cpu_tuple_cost 0.01,逐行表达式再叠加 cpu_operator_cost);第三项是算子特定开销(哈希构建、排序比较、聚合等)。

其中 pages 与 rows 均由统计信息估算(relpages/reltuples 与选择率推导),因此统计失真直接传导为代价失真——这是"统计陈旧导致计划错误"的数学根源。该公式也是理解一切参数调整的钥匙:改 cost 参数是调整系数,刷新统计/建直方图是修正输入,两者目的都是让"模型代价"接近"真实代价"。

本题考察代价公式的抽象结构:I/O 项、CPU 项、算子项三部分,以及"统计作为输入"的传导关系。

#
★★

16. random_page_cost 默认值?

PostgreSQL 的 random_page_cost 默认值是多少?设置过大会有什么后果?

  • 默认 4.0:随机读一页代价是顺序读的 4 倍
  • 设置过大会系统性回避索引/位图扫描、偏向全表扫描
  • SSD 场景的典型误配置

random_page_cost 默认 4.0,表示"随机读一个数据页的代价是顺序读(seq_page_cost=1.0)的 4 倍",这一数值针对传统 HDD 的机械寻道特性设计。若在 SSD/NVMe 存储上保持默认,优化器会系统性高估索引扫描、位图堆扫描等随机访问路径的代价:即使查询选择率极低(如仅返回 0.1% 的行),也可能选择全表顺序扫描,使原本毫秒级的点查变成秒级全扫。

后果与修正:症状是"明明有合适索引却总是全表扫描";把 random_page_cost 调低(SSD 常见 1.1、NVMe 可 1.0)后索引路径重新进入竞争。反向场景:若存储确为 HDD 且随机读确实昂贵,保持或调高该值可避免无谓的随机回表。修改后需对比 EXPLAIN 计划与真实耗时验证。

本题考察单个代价参数的默认值与误配置后果:4.0 的来源、过大的计划偏好(偏向全表)、修正方向与验证。

#
★★

17. Nested Loop 的代价估算,外层驱动行数 × 内层索引探测代价,为何驱动集小且内层有高效索引时胜出,其启动成本与 Merge/Hash Join 的差异?

Nested Loop 的代价如何估算?为什么驱动集小且内层有高效索引时它胜出?与 Merge/Hash Join 在启动成本上有何差异?

  • 代价 = 外层扫描 + 外层行数 × 内层探测代价
  • 小驱动集 + 内层索引 → 总代价小且启动快
  • 启动成本差异:NL 输出第一行最快,Hash Join 需先建表,Merge 需先排序列

Nested Loop 总代价 ≈ 外层扫描代价 + 外层行数 × 内层单次探测代价。当驱动集(外层)行数很小(如 10 行)且内层有主键/唯一索引可 O(log n) 探测、回表代价低时,总代价近似"10 次随机探测",远小于扫描并连接两张大表的 Hash/Merge 方案,因此胜出。

启动成本差异:NL 输出第一行只需外层第一行完成一次内层探测,启动极快,天然适合 LIMIT、流式与交互式查询;Hash Join 必须先完整读入内层构建哈希表才能输出第一行;Merge Join 需要两路按连接键有序(先排序或利用已有索引),启动成本介于两者之间。优化器据此决策:驱动集小 + 内层有索引 → NL;大表等值连接 → Hash;输入有序或需要排序输出 → Merge;这些是理解 EXPLAIN 中 join 方法选择的钥匙。

本题考察 NL 的代价公式与启动成本特性:乘积公式决定"何时赢",启动成本决定"何时先出结果"。回答时给出公式、胜出条件与三类 join 的启动对比。

#
★★

18. PostgreSQL 的代价参数?

PostgreSQL 有哪些常用的代价参数?它们分别控制什么,如何验证参数调整的效果?

  • I/O 类:seq_page_cost、random_page_cost;CPU 类:cpu_tuple_cost、cpu_index_tuple_cost、cpu_operator_cost
  • 并行类:parallel_setup_cost、parallel_tuple_cost;effective_cache_size 的间接影响
  • 验证:EXPLAIN 对比、真实 workload 基准、auto_explain 采样

PG 代价参数分三类:① I/O 类——seq_page_cost(默认 1.0)、random_page_cost(默认 4.0),定义页读取的相对权重;② CPU 类——cpu_tuple_cost(0.01)、cpu_index_tuple_cost(0.005)、cpu_operator_cost(0.0025),定义行处理与表达式求值代价;③ 并行类——parallel_setup_cost(1000)、parallel_tuple_cost(0.1),调低两者会鼓励优化器采用并行计划。另有 effective_cache_size:不直接进入代价公式,但用于估算索引扫描可从 OS 缓存命中的比例,间接影响随机 I/O 代价的折减。

验证方法:先确认存储介质与 workload 特征(读多写少/OLTP/OLAP),修改后对比 EXPLAIN 计划形态差异;再用真实查询基准(pgbench 或业务压测)配合 auto_explain(log_min_duration 采集慢查询的实际计划与耗时)对比 P50/P95 与执行时间,确认改善而非"调了没测"。任何代价参数改动都应记录基线并支持回滚。

本题考察代价参数全景与校准流程:三类参数的默认值、effective_cache_size 的间接角色、以及"计划对比 + 真实基准"的验证闭环。

#
★★

19. Sort 的代价估算,内存排序与磁盘归并的成本如何建模,work_mem 不足时的 external sort 对执行时间的影响?

Sort 的代价如何估算?内存排序与磁盘归并(external sort)的成本差异?work_mem 不足对执行时间的影响?

  • 内存排序:N log N 次比较 + 读入 N 行,无 I/O 放大
  • external sort:归并段落盘 + 多趟归并,I/O 代价显著上升
  • work_mem 与临时表空间、Top-N 堆排序的优化

Sort 代价建模分两阶段:内存排序假设全部数据装入 work_mem,代价 ≈ 读取 N 行(cpu_tuple_cost)+ N log N 次比较(cpu_operator_cost),无 I/O 放大;当数据量超出 work_mem,PG 采用 external sort:先读入内存排序生成若干初始归并段(run)写临时文件,再按归并树多趟合并,代价 = N log N 次比较 + 每趟读写的临时文件 I/O(约 2 倍数据量的顺序 I/O,趟数与内存大小对数相关)——总代价超线性上升,排序可能慢一个数量级,临时文件还会占用 temp_tablespaces 空间。

优化手段:① 提高 work_mem(注意并发连接 × work_mem 的总内存预算,全局调大需谨慎);② 让 ORDER BY 走索引(免排序);③ Top-N 场景(ORDER BY ... LIMIT 小值)优化器可用堆排序只维护 K 个元素,避免全量排序;④ 监控 EXPLAIN ANALYZE 中 Sort 节点的"Sort Method: external merge Disk"字样确认落盘发生。

本题考察排序代价的两阶段模型:内存排序的 N log N 与外部归并的 I/O 放大。回答时给出落盘机制、影响与三条优化路径。

#
★★

20. 代价模型中的 cost 参数如何通过实验校准,基于真实 workload 测量顺序扫描、索引扫描与随机 I/O 的相对代价,如何避免拍脑袋调参?

如何通过实验校准代价参数?如何测量顺序扫描、索引扫描与随机 I/O 的相对代价,避免拍脑袋调参?

  • 介质基准:实测顺序扫描、点查、随机回表的相对耗时
  • workload 校准:核心 SQL 基准集 + 计划/耗时回归
  • 单一变量原则与可重复压测脚本

校准核心是用实测数据替代经验值:① 介质基准——对目标表执行 SELECT COUNT(*)(纯顺序扫描)、主键单点查询(随机 I/O)、批量 IN 列表(随机+顺序混合),记录每行/每页的实际耗时,换算顺序读与随机读的相对倍数(如实测 1.3 倍则 random_page_cost 应设为 1.3 而非默认 4);② workload 校准——挑选 20~50 条核心 SQL 作为基准集,记录调参前后各自的计划形态与耗时;③ 修正与验证——一次只改一个参数,用 EXPLAIN ANALYZE 确认计划符合预期,再跑全量回归防止"按下葫芦浮起瓢"。

同时用 effective_cache_size 匹配真实缓存命中率(可通过 pg_stat_database 的缓存命中统计反推)。整个过程需要可重复的压测脚本、明确基线与记录文档,任何调整都可追溯、可回滚——这是避免拍脑袋调参的制度保障。

本题考察参数校准的工程方法:介质实测、workload 回归、单一变量、基线管理。回答时给出完整的校准步骤而非具体数值。

#

21. cpu_tuple_cost 默认值?

PostgreSQL 的 cpu_tuple_cost 默认值是多少?其含义与调优意义?

  • 默认 0.01:处理一行元组的估算 CPU 代价
  • 与 cpu_index_tuple_cost(0.005)、cpu_operator_cost(0.0025)的对比
  • 调大使优化器回避行处理重的计划

cpu_tuple_cost 默认 0.01,表示优化器认为"处理一行元组的 CPU 代价"约为顺序读一页(1.0)的 1%。它乘以节点预估处理的行数进入总代价,行数越大贡献越显著;与 cpu_index_tuple_cost(0.005,处理一行索引项)和 cpu_operator_cost(0.0025,一次表达式/比较求值)共同刻画 CPU 侧成本。

调优意义:若服务器 CPU 极快而 I/O 昂贵,可适度调小;若 CPU 成为瓶颈(复杂表达式、超大行数的扫描与连接),调大可让优化器更倾向"减少行处理"的计划(如用索引缩减扫描行数、避免重嵌套循环)。默认值对绝大多数系统是合理基线,一般仅在明确以 CPU 为瓶颈的 workload 下微调,且需基准验证。

本题考察单个 CPU 代价参数的默认值与语义:0.01 的基准含义、与同类参数的对比、调优方向。

#

22. seq_page_cost 默认值?

PostgreSQL 的 seq_page_cost 默认值是多少?其含义与设置原则?

  • 默认 1.0,是代价体系的基准单位
  • 其他代价参数相对它衡量
  • 设置原则:保持 1.0 作锚点,只调整相对值

seq_page_cost 默认 1.0,它是 PG 代价体系的基准单位:一次顺序读取一个 8KB 数据页的代价被定义为 1,random_page_cost(默认 4.0)、cpu_tuple_cost(0.01)等都相对它衡量,因此总代价是无量纲数值,仅用于候选计划之间的相对比较。

设置原则:seq_page_cost 通常保持 1.0 作为锚点不变,调优重点是它与其他参数的相对关系——例如 NVMe 上随机读几乎不比顺序读贵,就把 random_page_cost 调到 1.0~1.1;若想表达"顺序读也很昂贵"(如网络存储),可整体放大 seq 并同步放大 random 保持比例。注意:同时改动两个参数时,影响的是相对比例而非绝对值。

本题考察代价基准单位:1.0 的锚点角色、无量纲比较语义、相对调整原则。