查询语义、连接与索引
先确定查询应该返回什么,再选择访问路径和解释执行计划。
查询先定义结果
查询将已保存信息按条件组合成结果。筛选选择哪些记录,投影选择哪些属性,连接建立满足条件的配对,聚合按组形成新的结果。查询语义说明应得到什么;物理计划说明某个实现怎样取得它。
连接并不天然保持“一项对象一行”。左侧一条记录匹配右侧三条记录时,可以得到三条配对结果。需要每个对象一行,应明确聚合、选取或存在性判断。未经规则说明的去重可能丢掉真实联系。
外连接中的条件位置
设左侧记录为 a、b、c:a 有符合条件的右侧记录,b 没有任何右侧记录,c 只有不符合条件的记录。将条件放进外连接的配对规则后,三项左侧记录均被保留;b 和 c 得到右侧缺失值。
若先按身份配对,再用最终筛选只保留符合条件的右侧记录,b 的补值行和 c 的已有不合格行都可能被排除。增加“右侧身份缺失”只能恢复 b:c 已经配对,没有补值行。这里的关键是配对、补值与筛选的关系。SQLite SELECT明确说明了 ON 与 WHERE 在外连接中的语义差别;这是一种结果模型,不能据此断言优化器的物理执行顺序。
NULL、顺序和分页
NULL 的比较和真值规则需要明确。条件筛选通常仅保留结果为真的行,未知值会影响等值判断、否定和聚合。排序必须显式声明;没有排序规则的查询结果不能被当作稳定顺序。相同排序值还需要稳定的次级键,分页才容易重复定位。
偏移分页在每次查询时跳过指定数量的结果;数据插入或删除可能使页面边界移动。基于游标的分页表达“从某组排序键之后继续”,仍需处理排序字段变化、唯一次级键和快照条件。选择取决于需要随机跳页还是连续读取。
索引提供另一条访问路径
索引为记录建立便于查找的附加结构。顺序索引适合范围和排序;散列结构适合等值;倒排结构连接词项与包含它的记录。索引不会自动改变查询结果,应该改变的是为取得结果需要检查的候选范围和读写代价。
新增索引还要在写入时维护,消耗空间,并可能影响锁和构建过程。能利用哪部分索引取决于查询条件、运算符、排序和实现。复合索引的前导属性通常决定有效范围,具体实现也可能提供跳跃扫描等额外能力;单一“最左前缀”口诀不足以描述所有数据库版本。
根据执行计划定位代价
计划器估计候选数量与访问代价,再选择扫描、连接和排序方案。估计依赖统计信息和模型,可能与实际分布不同。需要比较实际执行时,应核对输入规模、缓存状态、查询参数、返回行数和时间口径。
一个顺序扫描可能适合读出大部分表,索引扫描可能适合较小候选;看到索引未用不能立即判定优化器错误。先观察估计与实际行数差异,再判断索引、统计、表达式或模型是否需要调整。
PostgreSQL 的 Indexes与 Using EXPLAIN提供了具体实现条件。本文没有声明某种索引在未知数据集上一定更快。
最后更新于