Rails 把旧的数据库中的数据 load 到新数据库 (新数据库表字段有差异)

chenyunli · 2016年10月25日 · 最后由 pengedy 回复于 2016年10月27日 · 5078 次阅读

把旧的数据库中的数据 load 到新数据库 (新数据库表字段有差异)

注:我们的情景:以前是 php 的项目改成 rails 的项目,数据库也打算重新建一个。数据库都是 postgresql ,大体数据结构类似,但是 有表字段名字不同,字段类型不同,我们现在就是把原来的数据 搞到我们新的数据库中。然后应用能 work。对数据库不熟,所以没有选择数据库脚本做。

  1. 安装 gem 'yaml_db'
    照例: Gemfile 中 加入 gem 'yaml_db'bundle

  2. 在旧的数据库环境下(database.yml 连接对应的数据库),运行 rake db:data:dump,会生成文件 db/data.yml。这个里面会把数据导出成数组格式。 如下:

    users:
      columns:
      - id
      - email
      - status
      - nickname
      - created_at
      - updated_at
      records: 
      - - 1
        - a@a.com
        - 1
        - ss
        - '2016-10-25 11:35:41'
        - '2016-10-25 14:26:11'
    
  3. 安装 gem 'bulk_insert' 照例: Gemfile 中 加入 gem 'bulk_insert'bundle

  4. 在新的数据库环境下(修改 database.yml 连接对应的数据库),写 task,读取 data.yml 文件,并写入新的数据库。 rake -T 可以查看所有 rake 命令,找到自己的 rake 命令 并执行 (本例:rake new_db:new_db_load)。

    task 代码:

    文件目录:lib/tasks/new_db_migration.rake

    namespace :new_db do
      desc 'migrate old db data to new db'
      task :new_db_load => :environment do
    
        old_db = YAML.load_file('db/data.yml')
    
        User.bulk_insert(set_size: 100) do |worker|
          old_db['users']['records'].each do |record|
            worker.add(
                id: record[0], email: record[1], status: (record[2] == 1), nick_name: record[3], created_at: record[4], updated_at: record[5]
            )
          end
        end
    
        puts 'user finish'
    
        puts 'done!'
      end
    end
    

    上面我做的变更是 status ,原来是 0 或者 1,现在改成 true 或 false,通过 status: (record[2] == 1) 操作来符合新的数据结构。其余的变更都可以类似处理。

我有一个疑问,以及两个建议。

疑问:为什么不通过创建一个新的 Rails DB Migration 来处理字段的更名,或者数据的更新?

建议 1:yaml_db 对数据规模比较小的情况,如果数据体量较大,可能会生成巨大的 yaml 文件,因为 yaml 格式不支持流式处理,所以读取的时候必须一次性读入到内存,解析非常慢且消耗资源。

建议 2:仍然建议直接通过数据库提供的常规方式来操作数据库,如果新旧两个数据库在同一台机器上,并且更新的时候还需要保存一份旧数据库的副本的话,可以使用 SQL 直接实现,比如下面这条 SQL 相当于在 PostgreSQL 中创建一份数据库副本,然后就可以在新的副本数据库中直接使用 SQL 命令修改字段信息,这样效率最高。

CREATE DATABASE new_db WITH TEMPLATE original_db;

#1 楼 @lgn21st 使用建议 2 的话,我们有一个 status 类型是 integer,原来是 0 或者 1,现在我们要变成 true 或者 false,如何在改变数据库表类型的同时把数据也转变过来?(我们现在的数据库数据还比较小,所以没有用 sql 方式)

印象里楼主可是花了 5 万培训出来的啊……看了这帖子感觉钱花的不太值呀🤐

楼主,我处理过不止一次的类似情况,简单谈一谈我的感受。

我先介绍一下我处理过的情况:

  • 从 MySQL 中向 PostgreSQL 中导入数据,表结构一致。
  • 在 PostgreSQL 中从库 A 向库 B 中导入数据,表结构不一致。

首先要明白,数据迁移这件事情,是数据库之间的事,跟业务框架没有丝毫关系。所以我是不赞成使用业务框架里 ORM来完成这种操作的,尽管这样能够完成任务,但局限性太强、效率很低。

所以,1 楼 @lgn21st 的两个建议都非常有道理。

面对较小规模的数据,最好的方式是写 SQL 脚本,当然也要看实际情况,两个库同机或异地有不同的处理方式。 面对较大规模的数据,可以考虑使用开源的ETL工具/框架,毕竟在”大数据“概念横空出世之前,ETL 一直把持着 BI 领域,也算是大批量数据导入/导出/格式转换的金刚钻了。

另外,4 楼 @coderliu 一般 Web 框架的培训班里都不会讲这种数据迁移的东西吧。

#5 楼 @pengedy 我也是刚入门不久,我上面那么说是因为这个需求完全可以通过写个 Migration 来实现,楼主却用了这么复杂的方法来实现,加上对首页那两个广告贴挂了好久的怨念,忍不住吐槽了一下。

#6 楼 @coderliu 培训班的事情再议吧,不要总给人家贴上这样的标签。毕竟人家能找到工作,也就是获得了社会公司认可的。 这里讨论技术就好。

Rails 倒数据的好处是能走各种回调,应用级别的校验,缺点是慢,我们业务不是很复杂的情况下,每秒差不多 60 条左右。

如果不需要走业务验证或者回调,还是推荐直接用数据库工具解决

有技术讨论挺好的,谁都不是一出来就很牛 X

同意一楼。LZ 可以用这种方法操作,就会知道时间消耗少多了

#2 楼 @chenyunli 你问的这个问题是一个好问题,这方面如果是简单的类型转换,比如在 MySQL 中,用的就是 tinyint 来存储 boolean,这样直接就修改数据库 DDL 都不用做数据迁移。

复杂的情况下,可能要根据具体情况来处理,比如通过 migration 脚本分四步

  1. 先在原来的表上建立一个新的字段
  2. 把旧的字段 transfer 到新的字段上(就是你原文中提到的 int to boolean)
  3. 删除旧字段
  4. 新字段改名

更复杂的情况,就如 #5 楼 @pengedy 提到的,用专用 ETL 工具来处理,这方面论坛里面讨论的不多。

#4 楼 @coderliu 讨论可以尽量不要带立场,这里不歧视新人,培训的事情过去了,我们应该多鼓励和帮助 @chenyunli

#10 楼 @lgn21st 我们的情景是 以前是 php 的项目改成 rails 的项目,数据库也打算重新建一个。数据库都是 postgresql ,大体数据结构类似,但是 有表字段名字不同,字段类型不同,我们现在就是把原来的数据 搞到我们新的数据库中。然后应用能 work。

#10 楼 @lgn21st 我们现在又遇到了一个问题。用 migration 跑的时候,原来的数据 id 不连续,现在 create 的话,id 都不能被赋值成原来的 id 了,这样的话表关系就乱了。

#5 楼 @pengedy 你说的很对呢,但是我对数据库操作不太会。所以就这样做的。希望你多多指教。有没有更详细的做法说明?我们的数据现在不多,所以,数据库脚本怎么写,分几步?怎么做?

#12 楼 @chenyunli copy 一份数据库进行 alter 操作可以解决 id 问题,每条记录重新创建不是很好的方案

#14 楼 @yangdong77 写的真详细,谢谢!还有个问题:你这个这样做的话,id 那个问题可以解决么?我现在又来个问题是用 migration 跑的时候,原来的数据 id 不连续,现在 create 的话,id 都不能被赋值成原来的 id 了,这样的话表关系就乱了。

#14 楼 @yangdong77 看了好几遍,好像看懂了些,非常感谢!如果这个 id 问题不存在,就完全可以用你这个方式了。我们数据量不大。很适用呢。

#15 楼 @embbnux migration 版本怎么办呢?我们的新项目有新创建一份 migration

#18 楼 @chenyunli 如果之前的项目没有 migration 体系的话,就在 rails 新项目写一个总的符合新数据库结构的 migration, 在新的数据库上跑完 migrate , 再把修改好的导入。或者你看一下 rails 的 migration 原理,就是在数据库里多了一张 schema_migrations 的表,里面记录下版本号,自己手动添加这张表也可以

#12 楼 @chenyunli 我不是很确定你遇到的 id 的问题具体什么情况,但是 id 自增是由数据库自身来保证的,并不是由 ActiveRecord 接管,所以在 PHP 和 Rails 中,id 的自增行为应该是一致的。

另外你遇到的是在 legacy 数据库上跑一个 Rails,上面 #19 楼@embbnux 讲的策略是正确的,具体操作只能根据情况小心规划,一步一步调整。

#13 楼 @chenyunli 先只考虑冷数据的迁移,也就是这些数据当下没有跑在业务系统里。这样的数据迁移任务可以分成三个子任务:

  1. 数据提取:要确定进行数据转换的范围,是整个库还是若干个表。(Extract)
  2. 数据转换:这一步比较繁琐,主要是将原有的数据格式以某种方法转换成新的数据格式,比如多表查询、表拆分等,最终形成符合目标数据格式的数据集。(Transform)
  3. 数据装载:将上一步生成的数据集装载进新的数据库中。(Load)

这简直就是 ETL 的思路。

实际的做法,比较建议在旧库中先折腾出来一套新库(建表、查询并插入数据)之后、把新库 dump 出来加载到目标数据库里。

也许是我陷在 ETL 较深,毕竟不是 DBA,如果有更好的思路还请各位提出。

如果是线上系统,可以参考基于内容的数据迁移计划和方案这篇文章。

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