Alexey Evlampiev:事务边界应置于程序中,而非文件名

Alexey Evlampiev:事务边界应置于程序中,而非文件名

💡 原文英文,约2900词,阅读约需11分钟。
📝

内容提要

PostgreSQL部署工具pgmi将事务边界置于SQL程序中,而非文件名元数据。它通过首个顶层COMMIT划分原子阶段与自动提交阶段,使CREATE INDEX CONCURRENTLY合法。文章强调锁超时、失败恢复与幂等重跑,并列出六项检查清单,确保部署安全可控。

🔎

延伸解读

事务边界放在程序中的优势

pgmi将事务边界放在SQL程序中,而非文件名或元数据,使得部署脚本的锁行为一目了然。通过首个顶层COMMIT划分原子阶段与自动提交阶段,既保证了事务性DDL的原子性,又允许CREATE INDEX CONCURRENTLY等非事务语句在事务外执行。这种设计让开发者能直接看到锁的持有时间,便于优化和排查问题。

锁超时与失败恢复的实践

文章强调设置lock_timeout的重要性,并展示了有无超时对部署的影响:无超时时,部署可能长时间排队等待锁,导致后续读请求阻塞;有超时时,部署快速失败,避免影响线上服务。同时,幂等重跑设计使得失败后可以安全重试,但需注意CREATE INDEX CONCURRENTLY的IF NOT EXISTS无法处理失败遗留的INVALID索引,需额外清理。

工具对比与适用场景

pgmi不自动判断DDL安全性,需要工程师自行控制事务边界。相比pgroll和reshape等提供更多自动化(如视图、双写触发器和批量回填),pgmi更轻量,适合希望手动控制部署细节的场景。但pgmi依赖会话级临时表和SET LOCAL,在事务池化(如pgBouncer)下不可用,需使用直连或会话池化连接。

Q&A

为什么CREATE INDEX CONCURRENTLY不能在事务块中执行?

因为PostgreSQL规定CREATE INDEX CONCURRENTLY不能在事务块内运行,也不能在函数、过程或DO块中执行。如果在一个多语句查询中,整个查询被视为一个隐式事务块,所以即使语句后面有COMMIT,也会报错。

pgmi如何表示事务边界?

pgmi将事务边界放在deploy.sql文件中,通过第一个顶层COMMIT(或END、ROLLBACK、ABORT)来划分:之前的所有语句作为一个事务执行,之后的每个顶层语句单独自动提交。

在pgmi中,如何实现一个包含并发索引创建的分阶段部署?

在deploy.sql中,将需要原子性的操作放在第一个COMMIT之前,例如使用BEGIN开始事务,执行ALTER TABLE等,然后COMMIT。之后,在自动提交模式下执行CREATE INDEX CONCURRENTLY等非事务性语句。每个阶段可以显式使用BEGIN和COMMIT来包裹需要原子性的操作。

为什么在部署中设置lock_timeout很重要?

设置lock_timeout可以限制语句等待锁的时间,避免长时间阻塞其他查询。例如,在部署中,如果没有设置lock_timeout,一个ACCESS EXCLUSIVE锁请求可能会在队列中等待10秒,导致后续读者排队;而设置了lock_timeout,部署会在约1秒内失败,避免造成长时间阻塞。

pgmi如何处理部署失败后的恢复?

pgmi会报告失败的具体单元和已提交的单元数。由于自动提交阶段无法原子回滚,需要编写幂等的部署脚本,使得重新运行可以收敛。例如,使用IF NOT EXISTS和清理无效索引等操作,确保重跑不会冲突。

pgmi与pgBouncer的兼容性如何?

pgmi依赖会话级临时表、SET/RESET和会话级咨询锁,这些在pgBouncer的事务池模式下不受支持。因此,使用pgmi部署时需要通过直接连接或会话池模式连接。

pgmi与pgroll、reshape等工具相比有什么不同?

pgmi只负责将事务边界显式化,不提供自动化的版本化视图或双写触发器等高级功能。pgroll和reshape提供更自动化的扩展/收缩迁移,但需要应用层协调search_path。pgmi则让工程师完全控制事务边界,适合需要精细控制锁和事务的场景。

部署时如何测量锁持有时间?

可以在事务开始前和COMMIT前使用clock_timestamp()记录时间,计算差值。但要注意,CLI报告的墙钟时间包含连接、文件扫描等开销,可能高估锁持有时间。例如,实际锁持有时间约20ms,而CLI报告约400ms。

🏷️

标签

➡️

继续阅读