内容提要
PostgreSQL 无法直接删除枚举值。一种方法是创建新枚举类型、转换列、删除旧类型并重命名,但会全表重写并加排他锁,耗时较长。更推荐的做法是重命名“已删除”值并添加检查约束。查找表的外键检查有性能开销,但优化器能正确推断条件。
延伸解读
删除枚举值的替代方案与代价
文章指出,PostgreSQL 无法直接删除枚举值。一种方法是创建新枚举类型、转换列、删除旧类型并重命名,但这会导致全表重写并持有 ACCESS EXCLUSIVE 锁,期间表不可用,耗时可能很长。更推荐的做法是重命名“已删除”值并添加检查约束,避免重写和长时间锁表。
查找表的外键性能开销
使用查找表时,外键检查会带来额外开销,相当于执行 SELECT 1 FROM state_l WHERE state_id = 42 FOR KEY SHARE,并且 FOR SHARE 锁会修改被引用表的行。对于写入负载重的表,这种开销在每次事务中累积,可能影响性能。文章主要关注可用性,但性能也是重要考量。
查找表设计对优化器的影响
如果查找表使用长字符串作为主键,则需要在所有表行中存储长字符串,没有优势,不如使用检查约束。对于查询条件,优化器可以从连接条件推断出 tab.col = 'official',从而得到正确的估算。因此,即使使用查找表,优化器也能有效工作。
Q&A
PostgreSQL 中如何删除枚举类型的某个值?
不能直接删除枚举值。一种方法是创建不包含该值的新枚举类型,将使用旧类型的列通过 ::text 转换后改为新类型,然后删除旧类型并将新类型重命名为旧名称。但此操作会重写整个表并获取 ACCESS EXCLUSIVE 锁,耗时较长。更推荐的做法是重命名“已删除”的枚举值并添加检查约束。
为什么 PostgreSQL 不自动处理枚举值的删除?
因为自动处理需要扫描数据库中所有使用该数据类型的表,并对每个表执行重写操作,这个过程非常缓慢且影响较大,PostgreSQL 认为这种“魔法”操作风险过高,因此没有实现自动化。
使用查找表时,外键检查对写负载高的表有什么性能影响?
每次写操作都需要检查外键约束,相当于执行 SELECT 1 FROM state_l WHERE state_id = 42 FOR KEY SHARE,并且会在被引用表上加 FOR SHARE 锁,修改该行。这会带来一定的性能开销。
ALTER TABLE 修改枚举类型列时,会获取什么锁?对表有什么影响?
会获取 ACCESS EXCLUSIVE 锁,这是 PostgreSQL 中最严格的锁,导致整个表在事务期间无法进行任何其他操作。同时会进行全表扫描和重写,可能耗时很长,期间表不可用。
如果查找表使用文本主键(如 value TEXT PRIMARY KEY),性能会怎样?
这种设计不好,因为需要在所有表行中存储长字符串,没有优势,不如直接使用检查约束。但查询时优化器仍能正确推断条件,例如从 protoenum.value = 'official' 和 tab.col = protoenum.value 推断出 tab.col = 'official',从而得到正确的估算。
查找表和枚举类型,哪个更好?
没有绝对答案,取决于使用场景。枚举类型更简单,但删除值麻烦;查找表更灵活,但外键检查有性能开销。文章建议根据实际需求权衡,并提到重命名枚举值加检查约束是一种折中方案。