如何迁移复杂的Rails数据库以使用UUID主键Postgresql

时间:2013-06-21 02:52:19

标签: ruby-on-rails postgresql migration uuid

我有一个数据库我想转换为使用UUID作为postgresql中的主键。

我有大约30个具有深层多级关联的表。是否有一种“简单”的方法将所有当前ID转换为UUID?

从这个:https://coderwall.com/p/n_0awq,我可以看到我可以在迁移中改变表格。我在想这样的事情:

for client in Client.all
  # Retrieve children
  underwritings = client.underwritings
  # Change primary key
  execute 'ALTER TABLE clients ALTER COLUMN id TYPE uuid;'
  execute 'ALTER TABLE clients ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
  # Get new id - is this already generated?
  client_id = client.id
  for underwriting in underwritings
    locations = underwriting.locations
    other_record = underwriting.other_records...

    execute 'ALTER TABLE underwritings ALTER COLUMN id TYPE uuid;'
    execute 'ALTER TABLE underwritings ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
    underwriting.client_id = client_id
    underwriting.saved
    underwriting_id = underwriting.id

    for location in locations
      buildings = location.buildings
      execute 'ALTER TABLE locations ALTER COLUMN id TYPE uuid;'
      execute 'ALTER TABLE locations ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
      location.undewriting_id = underwriting_id
      location.save
      location_id = location.id

      for building in buildings
      ...
      end
    end
    for other_record in other_records
      ...
    end
    ... 
    ...
  end
end

问题:

  • 这会有用吗?
  • 有更简单的方法吗?
  • 只要在更改主键之前检索子记录,是否可以正确检索子记录?
  • 一旦调用alter table,是否已经生成了新的主键?

非常感谢您提供帮助或提示。

3 个答案:

答案 0 :(得分:9)

我觉得这些很乏味。可以对PostgreSQL使用直接查询来转换表与现有数据。

主键:

    ALTER TABLE students
        ALTER COLUMN id DROP DEFAULT,
        ALTER COLUMN id SET DATA TYPE UUID USING (uuid(lpad(replace(text(id),'-',''), 32, '0'))),
        ALTER COLUMN id SET DEFAULT uuid_generate_v4()

其他参考资料:

    ALTER TABLE students
        ALTER COLUMN city_id SET DATA TYPE UUID USING (uuid(lpad(replace(text(city_id),'-',''), 32, '0')))

上面左边用零填充整数值并转换为UUID。这种方法不需要id映射,如果需要,可以检索旧id。

由于没有数据复制,这种方法非常快。

要处理这些更复杂的多态关联案例,请使用https://github.com/kreatio-sw/webdack-uuid_migration。此gem为ActiveRecord :: Migration添加了额外的帮助程序,以简化这些迁移。

答案 1 :(得分:4)

我认为通过Rails尝试做这样的事情只会使问题复杂化。我完全忽略了Rails方面的事情,只是在SQL中做。

您的第一步是获取数据库的完整备份。然后将该备份还原到另一个数据库中:

  1. 确保备份有效。
  2. 给你一个现实的围栏,你可以在没有后果的情况下犯错误。
  3. 首先,您需要通过添加真正的外键来匹配所有Rails关联来清理数据。你的一些FK很有可能会失败,如果他们这样做,你将不得不清理破损的参考文献。

    现在您已拥有干净的数据rename all your tables,以便为新的UUID版本腾出空间。对于表t,我们将重命名的表称为t_tmp。对于每个t_tmp,创建另一个表来保存从旧integer id到新UUID id的映射,如下所示:

    create table t_id_map (
        old_id integer not null,
        new_id uuid not null default uuid_generate_v1()
    )
    

    然后填充它:

    insert into t_id_map (old_id)
    select id from t_tmp
    

    当你在这里时,你可能希望索引t_id_map.old_id

    这为我们提供了带有integer ID的旧表和每个t_tmp的查找表,用于将旧id映射到新的integer

    现在使用UUID创建新表,替换保留serial s的所有旧idinsert into ... select ... from列;我也会在这一点上添加真正的外键;你应该对你的数据产生偏执:破碎的代码是暂时的,破碎的数据通常是永远的。

    此时填充新表非常简单:只需使用t_id_map构造并加入相应的id表,将旧的t_tmp映射到新的t_id_map。一旦数据被映射和复制,您将需要进行一些健全性检查以确保一切仍然有意义。然后,您可以放弃{{1}}和{{1}}表,继续您的生活。

    在数据库的副本上练习该过程,编写脚本,然后离开。

    您当然希望在执行此项工作时关闭访问数据库的所有应用程序。

答案 2 :(得分:1)

不想添加外键,并希望使用rails迁移。无论如何,如果其他人想要这样做,我就会这样做(例如2张桌子,总共做了32张):

  def change
    execute 'CREATE EXTENSION "uuid-ossp";'
    execute <<-SQL
      ALTER TABLE buildings ADD COLUMN guid uuid DEFAULT uuid_generate_v1() NOT NULL;
      ALTER TABLE buildings ALTER COLUMN guid SET DEFAULT uuid_generate_v1();
      ALTER TABLE buildings ADD COLUMN location_guid uuid;

      ALTER TABLE clients ADD COLUMN guid uuid DEFAULT uuid_generate_v1() NOT NULL;
      ALTER TABLE clients ALTER COLUMN guid SET DEFAULT uuid_generate_v1();
      ALTER TABLE clients ADD COLUMN agency_guid uuid;
      ALTER TABLE clients ADD COLUMN account_executive_guid uuid;
      ALTER TABLE clients ADD COLUMN account_representative_guid uuid;
    SQL

    for record in Building.all
      location = record.location
      record.location_guid = location.guid

      record.save
    end

    for record in Client.all
      agency = record.agency
      record.agency_guid = agency.guid

      account_executive = record.account_executive
      record.account_executive_guid = account_executive.guid unless account_executive.blank?

      account_representative = record.account_representative
      record.account_representative_guid = account_representative.guid unless account_representative.blank?

      record.save
    end

    execute <<-SQL
      ALTER TABLE buildings DROP CONSTRAINT buildings_pkey;
      ALTER TABLE buildings DROP COLUMN id;
      ALTER TABLE buildings RENAME COLUMN guid TO id;
      ALTER TABLE buildings ADD PRIMARY KEY (id);
      ALTER TABLE buildings DROP COLUMN location_id;
      ALTER TABLE buildings RENAME COLUMN location_guid TO location_id;

      ALTER TABLE clients DROP CONSTRAINT clients_pkey;
      ALTER TABLE clients DROP COLUMN id;
      ALTER TABLE clients RENAME COLUMN guid TO id;
      ALTER TABLE clients ADD PRIMARY KEY (id);
      ALTER TABLE clients DROP COLUMN agency_id;
      ALTER TABLE clients RENAME COLUMN agency_guid TO agency_id;
      ALTER TABLE clients DROP COLUMN account_executive_id;
      ALTER TABLE clients RENAME COLUMN account_executive_guid TO account_executive_id;
      ALTER TABLE clients DROP COLUMN account_representative_id;
      ALTER TABLE clients RENAME COLUMN account_representative_guid TO account_representative_id;
    SQL
  end