【问题标题】:Add UUID Primary Key in Rails and MySQL在 Rails 和 MySQL 中添加 UUID 主键
【发布时间】:2015-07-04 08:53:00
【问题描述】:

我有一张没有 ID 的桌子。现在我知道这是一个错误的设计,我想向该表添加一个主键 ID。这是脚本:

def up
  add_column :players, :id, :primary_key, first: true

  change_table :players do |t|
    t.change :id, :uuid
  end
end

def down
  remove_column :players, :id
end

问题是脚本为 UUID 列生成整数值。如何正确添加主键 UUID 到表中?

编辑: 它已经在生产中。 我可以添加 ID 列,但它将填充整数,而不是我预期的 UUID。

【问题讨论】:

  • 如果你说它是错误的设计,你应该努力修复你的设计!
  • 它已经在生产中了。我可以添加 ID 列,但它将填充整数,而不是我预期的 UUID。

标签: mysql ruby-on-rails


【解决方案1】:

在数据库环境中,以列 id 作为主键的表将是 每一行的唯一标识符。它通常是具有自动增量的整数值(但不是必需的),通常使用 32 位或 64 位整数。表中没有 2 行具有相同的 id。

在 Rails 中,UUID 是一个全局 唯一标识符。它可用于识别用户、会话或任何内容。并且所有类别的 id 都是相同的。它是使用随机生成器创建的,因此为了减少 2 个不同的创建具有相同 UUID 的机会,它是一个 128 位的值。

有多种方法可以使用 UUID 作为主键,但这可能是一个糟糕的设计理念,因为数据库引擎必须根据它来查找行,而且查找行的工作量很大,因此查找行可能会更加困难。通常,数据库会将 UUID 转换为内部表行 ID。

我猜你需要一个主键和一个单独的 UUID 列,但这取决于上下文。

【讨论】:

    【解决方案2】:

    这是完全可行的,在许多情况下它是一个不错的设计选择。在分片、多主写入或需要移动到任何多数据库系统时,创建全局唯一 ID 会使事情变得更加容易。当然,缺点是它占用的空间是 bigint 的两倍,而且比自动增量更复杂。

    首先,切勿将 UUID 存储为 VARCHAR。好的,现在不碍事了,下面是一些代码:

    这个 gem 使用 Mysql 8.0+ 的 UUID_TO_BIN 和 BIN_TO_UUID 来存储二进制和检索 UUID 字符。 https://github.com/nedap/mysql-binuuid-rails

    gem 'mysql-binuuid-rails' 
    

    在模型中,使用 Rails 5+ 中的 Attributes API。请务必记住设置所有外键来解码 UUID,即attribute :user_id, MySQLBinUUID::Type.new

    class Account < ApplicationRecord
      attribute :id, MySQLBinUUID::Type.new
      before_create { self.id = ApplicationRecord.generate_uuid }
    end
    
    

    本次迁移指定不创建默认主键,然后手动指定主键。触发器很好,因为如果您在没有 ActiveRecord 的情况下运行任何创建记录的查询,它仍然会正确生成和填充主键。

    class CreateAccounts < ActiveRecord::Migration[6.0]
      def change
        create_table :accounts, id: false do |t|
          t.binary :id, limit: 16, primary_key: true
          t.string :email
    
          t.timestamps
        end
    
    
        reversible do |dir|
          dir.up do
            execute <<~SQL
              CREATE TRIGGER before_insert_set_account_uuid
                BEFORE INSERT ON accounts
                FOR EACH ROW
                BEGIN
                  IF new.id IS NULL THEN
                    SET new.id = UUID_TO_BIN(UUID(), 1);
                  END IF;
                END
            SQL
            execute "UPDATE accounts SET id = UUID_TO_BIN(UUID());"
          end
          dir.down do
            execute <<~SQL
              DROP TRIGGER before_insert_set_account_uuid;
            SQL
          end
        end
      end
    end
    

    这是使用 MySQL 的方法来创建 UUID。请注意,这意味着两次往返数据库,一次获取新的 UUID,一次保存记录。最好利用 UUIDTools 等在本地生成它。

    class ApplicationRecord < ActiveRecord::Base
      self.abstract_class = true
    
      def self.generate_uuid
        ActiveRecord::Base.connection.execute("select UUID();").first[0]
      end
    end
    

    【讨论】:

      猜你喜欢
      • 2019-12-12
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 2012-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多