内容提要
本文讨论了数据库迁移编号冲突问题,即两个开发者从同一提交分支创建迁移时可能分配相同编号,导致合并后执行顺序模糊。文章比较了Flyway、Liquibase等工具的处理方式,并介绍了pgmi工具,它通过SQL查询暴露计划,允许项目自定义验证规则,如检测重复键或固定计划,从而在合并前或部署时强制执行策略。
延伸解读
迁移编号冲突的本质
文章指出,迁移编号冲突并非简单的文件名问题,而是源于三个核心选择:变更如何获得身份、顺序如何表示、以及不变量在哪里强制执行。顺序编号将身份和顺序融合为一次无协调的分配,导致不同分支可能独立分配相同编号,而Git合并时不会检查唯一性。理解这一本质有助于团队选择更合适的迁移工具和策略。
不同工具的应对策略
Flyway通过版本号唯一性校验并在冲突时中止,Liquibase使用id+作者+路径三元组作为身份,Alembic采用依赖图,Sqitch使用声明式顺序和依赖边,Atlas通过生成校验和文件强制冲突。这些工具各有侧重,但都试图解决身份、顺序和强制执行的平衡。pgmi则通过暴露计划为SQL关系,让项目自定义验证规则,提供了另一种思路。
pgmi的独特之处
pgmi并非迁移框架,不维护已应用迁移的账本,而是将计算出的计划暴露为PostgreSQL关系,部署脚本直接查询并执行该关系。这使得验证逻辑与执行逻辑完全一致,避免了工具内部计划与外部检查不一致的风险。项目可以用SQL编写任意策略,如检测重复键或固定计划,并在CI或部署时强制执行。
实践中的注意事项
pgmi的排序键是字典序字符串,不提供依赖图,也不内置账本,需要项目自行实现。多位置文件必须幂等,且计划是会话级的,审计需自行持久化。此外,策略评估需要PostgreSQL会话,静态检查不可用。团队需权衡这些限制,选择适合自身工作流的迁移工具。
Q&A
为什么两个开发者从同一提交分支创建迁移时会发生迁移编号冲突?
因为两个开发者各自从同一提交分支创建迁移时,都会读取相同的历史前缀,独立分配下一个编号(如V42),而Git合并时不会检查编号唯一性,导致两个迁移文件具有相同编号,造成执行顺序模糊。
Flyway和Liquibase如何处理迁移编号冲突?
Flyway在遇到重复版本号时会中止执行,并提示顺序模糊;Liquibase则避免使用纯数字作为变更集标识,而是使用id+author+changelog路径的组合来唯一标识变更集,id仅作为标识符而非排序值。
pgmi工具如何暴露迁移计划?
pgmi将计算出的迁移计划暴露为一个普通的PostgreSQL关系,即pg_temp.pgmi_plan_view,可以通过SQL查询该视图来查看执行顺序、排序键和文件路径,并且deploy.sql会直接查询并执行该计划。
pgmi如何检测重复的排序键?
pgmi允许项目在deploy.sql中编写自定义SQL断言,例如使用GROUP BY和HAVING count(*) > 1来检测重复的排序键,并在发现重复时抛出异常,从而在部署前阻止执行。
pgmi的迁移计划排序规则是什么?
pgmi的排序规则是:如果文件没有声明排序键,则使用文件路径作为排序键;如果声明了排序键,则使用声明的键。排序时使用COLLATE "C"(字节顺序)来确保跨服务器的一致性,避免受数据库区域设置影响。
pgmi如何支持在合并前强制执行迁移策略?
pgmi的部署过程是自包含的会话,可以指向任意数据库。项目可以在CI中使用一次性数据库运行pgmi deploy,执行deploy.sql中的断言,从而在合并前检测重复排序键等问题,实现策略的提前执行。
pgmi与Flyway、Liquibase等工具的主要区别是什么?
pgmi的主要区别在于它将计算出的迁移计划暴露为PostgreSQL关系,并允许项目使用SQL编写自定义验证规则,直接在部署会话中执行。而Flyway和Liquibase等工具通常内置了固定的验证逻辑,且不提供这种可编程的计划检查方式。
pgmi有哪些局限性?
pgmi的局限性包括:策略评估需要PostgreSQL会话,没有内置的静态检查;排序键是字典序字符串,不支持依赖图;没有内置的已应用迁移记录;默认不提供冲突检测,需要项目自行编写;计划是会话级的,审计需要自行持久化;仅支持PostgreSQL,且需要编写SQL。