内容提要
迁移文件与Git不兼容:迁移是追加式事件日志,而非Git管理的可变状态文件,导致冲突检测、代码审查、回滚和分支切换均失效。合并顺序与应用顺序可能不一致,回滚仅删除文件而非撤销数据库变更。声明式快照是部分修补,但无法检测漂移。安全需依赖数据库实际状态与仓库声明的对比。
延伸解读
迁移文件与Git的“管理”错位
迁移文件虽然存放在Git仓库中,但Git的管理机制并不适用于它们。迁移是追加式事件日志,而Git擅长管理可变的状态文件。因此,Git提供的保护——如可读的差异、冲突检测、回滚和分支切换——在迁移文件上都会失效。理解这一点,有助于避免将Git的保障错误地套用在迁移上。
冲突检测为何失效
当两个开发者同时修改同一张表时,如果使用声明式模式文件,Git会检测到冲突;但迁移文件各自独立,文件名不同,文本上无冲突,合并时不会报错。这导致语义不兼容的变更可能被静默合并,最终在应用时引发问题。这是迁移格式本身造成的,而非Git的缺陷。
回滚与切换分支的陷阱
对迁移提交执行git revert只会删除文件,不会撤销数据库中的变更;切换分支时,代码回到旧状态,但本地数据库可能停留在新状态,导致“在我机器上能跑”的问题。这些操作看似正常,实则制造了新的不一致,需要额外注意。
声明式快照的局限
Rails和Prisma等工具通过维护声明式快照来弥补迁移的不足,使差异可读、冲突可检测。但快照只是迁移重放的结果,描述的是仓库声明的状态,而非数据库实际状态。因此,它无法检测漂移,且作为单一文件容易成为冲突焦点。真正的安全需要对比数据库实际状态与仓库声明。
Q&A
为什么说迁移文件与Git不兼容?
因为迁移文件是追加式事件日志,而不是Git管理的可变状态文件。Git的保护机制(如冲突检测、代码审查、回滚、分支切换)都是针对状态文件的,而迁移文件是事件日志,导致这些保护机制失效。
迁移文件与声明式模式文件在Git管理上有什么不同?
声明式模式文件(如schema.prisma、models.py)是状态文件,Git可以很好地管理:diff可读、冲突检测有效、回滚和切换分支都有意义。而迁移文件是事件日志,diff只显示变更步骤而非结果,冲突检测失效,回滚和切换分支也无法正确反映数据库状态。
为什么迁移文件在合并时不会产生冲突?
因为每个开发者创建的是不同的迁移文件,文件名不同,文本上不会冲突。即使两个迁移在语义上不兼容(比如都修改了同一张表),Git也不会检测到冲突,因为并发修改不会出现在同一个文件中。
迁移文件的合并顺序和应用顺序不一致会有什么问题?
如果分支A的迁移时间戳是周一,分支B的是周五,但B先合并,那么仓库中的顺序是周一→周五,但数据库可能先应用了周五的迁移,再应用周一的。这导致历史记录与实际执行顺序不一致,且工具通常只记录已应用的迁移集合,无法察觉这种不一致。
为什么git revert迁移文件不能真正回滚数据库变更?
因为git revert只是删除了迁移文件,但数据库中的变更已经应用,历史表也记录了该迁移。删除文件后,仓库声称该迁移不存在,但数据库记得它已应用,这反而制造了新的不一致。
切换分支时,本地数据库状态会怎样?
切换分支时,代码会回到分支对应的状态,但本地数据库不会自动回退,仍然保留之前分支的迁移。多次切换后,本地数据库可能达到一个不属于任何分支的状态,导致“在我机器上能跑”的问题。
声明式快照(如schema.rb)有什么局限性?
声明式快照是迁移重放的结果,它描述的是文件声称的状态,而不是实际数据库的状态,因此无法检测漂移。此外,快照是一个文件,所有更改都汇聚于此,容易成为冲突的焦点。
如何确保数据库安全?
需要将仓库中的模式声明视为待验证的声明,而不是事实。通过对比数据库实际状态与仓库声明(如进行漂移检查)来确保安全。