Rails db/schema.rb 数据库迁移后的数据库模式文件需要加入到版本控制中吗?

lehug · 发布于 2017年11月04日 · 最后由 lehug 回复于 2017年11月11日 · 739 次阅读
25193

在实际项目,团队对于是否把schema.rb加入到版本控制中做了一些讨论。想延伸下,听听各位的意见。

目前我们把其加入到了版本控制,带来了一些问题: 经常冲突,然后解决冲突,存在解决冲突不佳导致的副作用。 同时发现不加入版本控制,交给migrate文件自行构建,暂未发现副作用。 并且大部分github项目都将schema.rb加入到了.gitignore。

但是rails guide或者rails风格指引中都提到建议加到版本控制

6.3 数据库模式转储和源码版本控制
数据库模式转储是数据库模式的可信来源,因此强烈建议将其纳入源码版本控制。

db/schema.rb 文件包含数据库的当前版本号,这样可以确保在合并两个包含数据库模式文件的分支时会发生冲突。一旦出现这种情况,就需要手动解决冲突,保留版本较高的那个数据库模式文件。

其中提到了一个好处是

当需要部署 Rails 应用的新实例时,不必把所有迁移重新运行一遍,直接加载当前数据库的模式文件要简单和快速得多。

但是我们实际项目中有200+的表,空库执行rails db:migrate耗时也不超过一分钟(甚至更少),所以我觉得此好处可忽略。

你们将其加入到版本控制了吗?不加入版本控制后,你们遇到了什么副作用?

共收到 26 条回复
3753

这个问题。。。我觉得schema.rb是无辜的。。。 你们应该解决协同开发的问题,并不是schema.rb的问题。。。因为如果版本控制出问题的话。。。不止schema.rb会冲突啊。。。

1

要,用来看当前有什么字段。

25193
3753zj0713001 回复

多人同时修改一个表的结构时,schema中很容易出现冲突,尤其是version那。不把其 加入版本控制,而是通过migrate文件重建schema.rb,这样就不会存在冲突的情况。另外拉去新代码携带新的migrate文件后,也必然会执行db:migrate,这个动作必定会重建schema.rb。那么我为何需要跟踪它呢?

25193
1Rei 回复

不加入版本控制,而是拉取新的migrate文件后,必然会执行rails db:migrate,执行后本地的schema文件会重建的呀。那么我不加入版本控制,也是可以实现获取数据库结构的目的的。

另外为何homeland项目的.gitignore里面有 /db/schema.rb ?

1
25193lehug 回复

我就见过一个项目重跑 migrate 之后和线上数据库 schema 不一致,不知道过程做了什么,也没有把 schema.rb 加到 git 里,开发一直基于错误的 schema 做然后上线出问题,最后把线上的 schema 导出重新加回。schema 有冲突就应该当即解决冲突。

现在 homeland 没有 ignore db/schema.rb https://github.com/ruby-china/homeland/blob/master/.gitignore

运行 migration 的时间不是取决于表数而是 migration 的数量,看看 discourse 的 migration 把字段改了又改,重跑一次不会很浪费时间吗? https://github.com/discourse/discourse/tree/master/db/migrate

discourse 不算好的 Rails 范例,创始人用 .net 的风格编码,用前后端分离,不是遵循 Rails 最佳实践的项目。

25193
1Rei 回复

我自测了一下,数据库里面直接删除一个字段,然后执行一个空的migrate,接着schema.rb会更新为当前数据库的结构。而不和migrate文件定义的一致。似乎schema.rb只是rails db:migrate执行后,rails根据数据库结构重建的结果,只反应本地数据库的状态。

重跑 migrate 之后和线上数据库 schema 不一致的一定是手动修改过数据库,那么把schema.rb加到文件跟踪也不会根本性解决问题。因为migrate文件会影响数据库结构,除了rails db:schema:load外,schema.rb并不会反影响到数据库结构。

如上成立的话,还是感觉schema.rb在版本跟踪中没有意义。

1

schema.rb 有冲突不处理,ignore 掉冲突就消失了吗?

设想有一个 table:

create_table :users do |t|
  t.string :name

  t.timestamps
end

这时分出两个分支,同时对 :name 进行修改:

# branch-1
rename_column :users, :name, :display_name
# branch-2
rename_column :users, :name, :full_name

如果有 schema.rb,在合并的时候会当即发现有冲突,两个分支都对 schema.rb 的同一行进行了修改,需要立即处理冲突。

如果没有 schema.rb,冲突会延后到执行 migration 的时候。如果有 CI 它会报异常,如果没有 CI,源码没有冲突,那就要拉下来 migration 的时候才发现冲突,此时代码已经被 merge 了。

schema.rb 能让冲突更早发现,更容易处理。

1

我找到 discourse 为什么不 check-in schema.rb 的讨论 https://meta.discourse.org/t/schema-rb-vs-migrations/14983

他们似乎基于这两个理由

1. schema.rb 不能用 db 的本地特性

这时候应该用 structure.sql 。

2. schema.rb/structure.sql 依赖于开发的本地数据库,可能会 check-in 不合版本的修改

开发环境和生产环境应该一致,用 docker 来维护。

至于开发者提交了坏的 structure.sql,应该提交者自己 review 和上游开发者 review,而不是大家都没有一个完整版的 schema,同时期待线上不会出问题。

25193

找到了一些额外的文章:

我再重新梳理下我的思考:

  • schema.rb是一个自动生成的文件,rails db:migrate等命名执行后,此文件会重新生成。
  • schema.rb表述本地当前的数据库结构,migration外手动修改数据库后,执行rails db:migrate会反应到schema.rb上
  • schema.rb究竟应该被谁版本控制?我的观点是migrate文件,而不是版本控制工具。只有migrate文件才能表述程序的迭代。上面提到的那个点,很容易破坏schema.rb,信任这样的schema.rb然后向下迭代是危险的。而空库migrate后的schema才是唯一表述迭代后数据库该有的结构。
  • 大多观点在2011年就已经加入,schema.rb中的注释是07年加入的。我不认为在当前的计算机配置下,rails db:migrate会比'rails db:schema:load慢多少,所以我不认为这一点它可以快速迁移数据库到你期望的状态` 还依然会是优点。另外rails项目一般也不庞大~
  • 多人协作时,大部分都负责不同的模块,同时修改某个表导致的冲突不多。git的不智能,导致的version冲突的问题,让其发生并且去花费时间解决,并没有意义。
  • 合并到主分支,一定是顺序执行的。假如同时修改一个表,那么最后一个提交到主分支的人在提交前就应该发现到了migrate冲突并解决。理论上存在可能性,同时删除了一个字段,但是概率很低。并且现在的开发理念中,都会有各种测试手段与流程。取舍之间,我选择舍弃会造成冲突的schema.rb跟踪,换来存在小概率migrate定义冲突情况的发生,并通过测试等手段解决掉这个副作用问题。
  • 它加入到版本控制确实也不会对团队造成太大的生产力浪费,但是频次还是比较高的。基于上面的一些观点,我还是觉得加入到版本控制没有大的优点,那么把它移出版本跟踪,对团队还是会减少一些此文件冲突烦恼的。
  • 再次强调下,我认为schema.rb应该被migrate文件控制,而不是版本工具。
1
25193lehug 回复

去掉 scheme 后,明显劣势是没有地方看当前数据库有什么表和字段了。当然,可以学 discourse 那样把 schema 导到 model 注释里,等于丢掉自带的东西引入一个依赖。

11楼 已删除
11562

你一直在重复这个 别人也做了说明 你一直在重复schema能重建 并且容易冲突的问题,完全听不进去别人的意见. 并且在实际项目中 如果只用migration文件很容易出现忘记提交问题 这时候如果时间过长 是没办法直接migrate成功的 需要一份schema来恢复一个基准数据库 再进行migration

25193

谢谢提醒。重新认识了下,我是有了答案,希望找到一个有说服力的反方观点,感觉还是没找到~

1

schema.rb 带来的好处:

  1. 更快的 db setup。
  2. 一目了然的数据库模式。
  3. 及早发现冲突。
  4. 符合约定,易于上手。

没有 schema.rb 带来的问题:

  1. db:migrate 越来越慢,影响开发和 CI。
  2. 需要在本地跑一遍 migration 才能看数据库模式。
  3. 发现冲突的时机延后到 CI 或更晚。
  4. 违反约定,让人迷惑。

如果这不能说服楼主,我感觉也没什么办法阻止楼主干傻事了。

25193
1Rei 回复

谢谢,这样优缺点已经很明确了。希望这个也能给别的愿意思考与质疑的人一些参考。

14560
Specify whether schema dump should happen at the end of the
db:migrate rake task. This is true by default, which is useful for the
development environment. This should ideally be false in the production
environment where dumping schema is rarely needed.
mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true

补充一个ActiveRecord::Core中的注释, 既然在production环境应该是false,所以我认为楼主的决定没有错。

1680

schema.rb 如果冲突,直接重跑 migration 不就可以解决冲突了吗?

25193
1680justin 回复

是的,重跑下就解决了。有一个场景:我们借助coding.net,多人提交合并请求,都携带了schema.rb,那么前一个合并后,后面的全部都会提示合并冲突。另外多人协作,大多数情况下,我不需要关心别人的schema改动,那么这种冲突解决就对我们来说浪费时间。

30794

因为有人忘记提交schema.rb. 现在每次提交都要git checkout db/schema.rb, 然后手动把自己的修改粘贴到schema上去。心好累。

25193
30794ThxFly 回复

为什么需要手动维护这个文件?

30794
25193lehug 回复

因为master的schema滞后于真实schema。某次提交有迁移但没schema就会发生这种情况。人都比较懒,没人修复这次过失

25193
30794ThxFly 回复

schema文件会在rails db:migrate后重写的。只要不基于master执行rails db:schema:load那么master滞后的问题可以忽略。既然你们团队对schema.rb文件也不重视,很多人为了避免冲突而选择不提交它,那么变成一个团队内的约定,都不提交,也未尝不可。你单独为了一个老旧的约定,而做一些对团队其他人意义不大的事情,我觉得没必要~

8

不check schema.rb的前提是你们要能保证migration一直可以运行。但据我观察,超过两年的项目很难保证这一点。另外,migration很快的前提是,你们要保证migration 文件里不写data migration。

25193
8hooopo 回复

我现在有个观点: 1:要保证migrate的连续性,保证其可以顺序执行。2:定期或者大版本更新后,清理一次migrate文件目录,将多个migrate文件合并到一个,为migrate文件夹瘦身(需要注意不能创建新的migrate,而是用老的已有的version,否则会视为新的migrate而清表重建)。 这样是不是一种更好的代码维护方式?

8
25193lehug 回复

确实可行 但瘦身可比合并schema。rb冲突复杂多了,并且危险大。

其实check schema最大的优点就是不会出现:你本地work,其他人或其他环境不work。

1

SVN 也不是不能用。

25193
8hooopo 回复

空库执行下migrate,然后把schema.rb覆盖到最后一个version中,然后把其他的migrate删掉,这样就合并了,还是比较轻松的😀

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册