在Rails和MySQL中添加UUID主键

时间:2015-04-25 19:29:39

标签: mysql ruby-on-rails

我有一张没有身份证的桌子。既然我知道这是一个错误的设计,我想在该表中添加一个主键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。

2 个答案:

答案 0 :(得分:1)

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

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

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

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

答案 1 :(得分:0)

这是完全可行的,在许多情况下,这是一个不错的设计选择。创建分片,进行多主机写入或需要移至任何多数据库系统时,创建全局唯一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