在实际项目,团队对于是否把 schema.rb 加入到版本控制中做了一些讨论。想延伸下,听听各位的意见。
目前我们把其加入到了版本控制,带来了一些问题: 经常冲突,然后解决冲突,存在解决冲突不佳导致的副作用
。
同时发现不加入版本控制,交给 migrate 文件自行构建,暂未发现副作用。并且大部分 github 项目都将 schema.rb 加入到了.gitignore。
但是 rails guide 或者 rails 风格指引中都提到建议加到版本控制
6.3 数据库模式转储和源码版本控制
数据库模式转储是数据库模式的可信来源,因此强烈建议将其纳入源码版本控制。
db/schema.rb 文件包含数据库的当前版本号,这样可以确保在合并两个包含数据库模式文件的分支时会发生冲突。一旦出现这种情况,就需要手动解决冲突,保留版本较高的那个数据库模式文件。
其中提到了一个好处是
当需要部署 Rails 应用的新实例时,不必把所有迁移重新运行一遍,直接加载当前数据库的模式文件要简单和快速得多。
但是我们实际项目中有 200+ 的表,空库执行 rails db:migrate 耗时也不超过一分钟(甚至更少),所以我觉得此好处可忽略。
你们将其加入到版本控制了吗?不加入版本控制后,你们遇到了什么副作用?
这个问题。。。我觉得 schema.rb 是无辜的。。。 你们应该解决协同开发的问题,并不是 schema.rb 的问题。。。因为如果版本控制出问题的话。。。不止 schema.rb 会冲突啊。。。
多人同时修改一个表的结构时,schema 中很容易出现冲突,尤其是 version 那。不把其 加入版本控制,而是通过 migrate 文件重建 schema.rb,这样就不会存在冲突的情况。另外拉去新代码携带新的 migrate 文件后,也必然会执行 db:migrate,这个动作必定会重建 schema.rb。那么我为何需要跟踪它呢?
不加入版本控制,而是拉取新的 migrate 文件后,必然会执行rails db:migrate
,执行后本地的 schema 文件会重建的呀。那么我不加入版本控制,也是可以实现获取数据库结构的目的的。
另外为何 homeland 项目的.gitignore 里面有 /db/schema.rb
?
我就见过一个项目重跑 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 最佳实践的项目。
我自测了一下,数据库里面直接删除一个字段,然后执行一个空的 migrate,接着 schema.rb 会更新为当前数据库的结构。而不和 migrate 文件定义的一致。似乎 schema.rb 只是rails db:migrate
执行后,rails 根据数据库结构重建的结果,只反应本地数据库的状态。
重跑 migrate 之后和线上数据库 schema 不一致
的一定是手动修改过数据库,那么把 schema.rb 加到文件跟踪也不会根本性解决问题。因为 migrate 文件会影响数据库结构,除了rails db:schema:load
外,schema.rb 并不会反影响到数据库结构。
如上成立的话,还是感觉 schema.rb 在版本跟踪中没有意义。
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 能让冲突更早发现,更容易处理。
我找到 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,同时期待线上不会出问题。
找到了一些额外的文章:
我再重新梳理下我的思考:
rails db:migrate
等命名执行后,此文件会重新生成。rails db:migrate
会反应到 schema.rb 上rails db:migrate
会比'rails db:schema:load慢多少,所以我不认为这一点
它可以快速迁移数据库到你期望的状态` 还依然会是优点。另外 rails 项目一般也不庞大~去掉 scheme 后,明显劣势是没有地方看当前数据库有什么表和字段了。当然,可以学 discourse 那样把 schema 导到 model 注释里,等于丢掉自带的东西引入一个依赖。
你一直在重复这个 别人也做了说明 你一直在重复 schema 能重建 并且容易冲突的问题,完全听不进去别人的意见. 并且在实际项目中 如果只用 migration 文件很容易出现忘记提交问题 这时候如果时间过长 是没办法直接 migrate 成功的 需要一份 schema 来恢复一个基准数据库 再进行 migration
schema.rb
带来的好处:
没有 schema.rb
带来的问题:
如果这不能说服楼主,我感觉也没什么办法阻止楼主干傻事了。
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,所以我认为楼主的决定没有错。
是的,重跑下就解决了。有一个场景:我们借助 coding.net,多人提交合并请求,都携带了 schema.rb,那么前一个合并后,后面的全部都会提示合并冲突。另外多人协作,大多数情况下,我不需要关心别人的 schema 改动,那么这种冲突解决就对我们来说浪费时间。
因为有人忘记提交 schema.rb. 现在每次提交都要 git checkout db/schema.rb, 然后手动把自己的修改粘贴到 schema 上去。心好累。
因为 master 的 schema 滞后于真实 schema。某次提交有迁移但没 schema 就会发生这种情况。人都比较懒,没人修复这次过失
schema 文件会在 rails db:migrate 后重写的。只要不基于 master 执行 rails db:schema:load 那么 master 滞后的问题可以忽略。既然你们团队对 schema.rb 文件也不重视,很多人为了避免冲突而选择不提交它,那么变成一个团队内的约定,都不提交,也未尝不可。你单独为了一个老旧的约定,而做一些对团队其他人意义不大的事情,我觉得没必要~
不 check schema.rb 的前提是你们要能保证 migration 一直可以运行。但据我观察,超过两年的项目很难保证这一点。另外,migration 很快的前提是,你们要保证 migration 文件里不写 data migration。
我现在有个观点:1:要保证 migrate 的连续性,保证其可以顺序执行。2:定期或者大版本更新后,清理一次 migrate 文件目录,将多个 migrate 文件合并到一个,为 migrate 文件夹瘦身(需要注意不能创建新的 migrate,而是用老的已有的 version,否则会视为新的 migrate 而清表重建)。 这样是不是一种更好的代码维护方式?
确实可行 但瘦身可比合并 schema。rb 冲突复杂多了,并且危险大。
其实 check schema 最大的优点就是不会出现:你本地 work,其他人或其他环境不 work。
空库执行下 migrate,然后把 schema.rb 覆盖到最后一个 version 中,然后把其他的 migrate 删掉,这样就合并了,还是比较轻松的
但是 schema 本来就是 db dump 出来的,永远反映的是 db 现在的状态,假如开发环境别的分支有 migration,在当前分支 commit 之后这个 wip 的 migration 就会合到 master 上....
我认为 schema 的正确性是没法保证的,如果常常要从头 init 整个环境,我觉得合并 migration 才是唯一能用的方案。