Rails在唯一索引和validates_uniqueness_of中有什么区别

时间:2014-04-20 20:08:17

标签: ruby-on-rails postgresql indexing

首先,任何人都可以解释唯一索引在数据库中的工作原理吗?

假设我有一个带有name column的用户模型,我在其上添加了unique index,但在模型中(user.rb)我只有presence验证器name 1}} field。

所以现在当我尝试创建两个同名用户时,我得到了PGError

  

重复键值违反了唯一约束" index_users_on_name"

所以我认为unique index的工作方式与uniqueness validator相同(?)

如果是,那么外键呢?

假设我的Post模型与belongs_to :user User has_many :posts关联。 并且user_id中的外键posts table具有唯一索引。然后,多个帖子不能具有相同的user_id

有人可以解释unique index的工作原理吗?

我使用Ruby 2.0.0使用Rails 4。

3 个答案:

答案 0 :(得分:16)

以下是unique index和validates_uniqueness_of

之间的区别

这是一个补丁,可以使ActiveRecord识别数据库生成的唯一约束违规错误。例如,它在不声明validates_uniqueness_of:

的情况下进行以下工作
create_table "users" do |t|
  t.string   "email",   :null => false
end
add_index "users", ["email"], :unique => true

class User < ActiveRecord::Base
end

User.create!(:email => 'abc@abc.com')
u = User.create(:email => 'abc@abc.com')
u.errors[:email]
=> "has already been taken"

好处是速度,易用性和完整性 -

<强>速度

使用这种方法,您无需进行数据库查找以在保存时检查唯一性(当错过索引时有时会非常慢 - https://rails.lighthouseapp.com/projects/8994/tickets/2503-validate ...)。如果您真的关心验证唯一性,那么无论如何都必须使用数据库约束,因此无论如何数据库都将验证唯一性,这种方法会删除额外的查询。检查索引两次对于DB来说不是问题(它是第二次缓存的),但是从应用程序中保存数据库往返是一个很大的胜利。

易于使用

鉴于你必须拥有真正唯一性的数据库约束,这种方法将让所有内容在数据库约束到位后自动发生。如果您愿意,仍然可以使用validates_uniqueness_of。

<强>完整性

validates_uniqueness_of一直是一个黑客攻击 - 它无法正确处理竞争条件并导致必须使用某种冗余错误处理逻辑处理的异常。 (请参阅http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMe..中的“并发性和完整性”部分。)

validates_uniqueness_of 不足以确保值的唯一性。原因是在生产中,多个工人流程可能导致竞争条件:

  1. 两个并发请求尝试创建具有相同名称的用户(和 我们希望用户名是唯一的)

  2. 两个工作进程在服务器上接受请求 现在将并行处理它们

  3. 两个请求都会扫描users表并看到名称是 可用

  4. 两个请求都通过验证并创建一个貌似的用户 可用名称

  5. 为了更清楚地理解,请查看

    如果为列创建唯一索引,则意味着您可以保证该表不会有多个具有该列相同值的行。仅在模型中使用validates_uniqueness_of验证不足以强制执行唯一性,因为可能有并发用户尝试创建相同的数据。

    想象一下,两个用户尝试使用您在用户模型中添加validates_uniqueness_of:email的同一电子邮件注册帐户。如果他们同时点击“注册”按钮,Rails会在用户表中查找该电子邮件并回复一切正常,并且可以将记录保存到表中。然后,Rails会使用相同的电子邮件将两条记录保存到用户表中,现在您有一个非常糟糕的问题需要处理。

    为避免这种情况,您还需要在数据库级别创建唯一约束:

    class CreateUsers < ActiveRecord::Migration
      def change
        create_table :users do |t|
          t.string :email
          ...
        end
    
        add_index :users, :email, unique: true
      end
    end
    

    因此,通过创建index_users_on_email唯一索引,您将获得两个非常好的好处。数据完整性和良好性能,因为唯一索引往往非常快。

    如果在posts表的user_id中输入unique:true,则不允许输入具有相同user_id的重复记录。

答案 1 :(得分:2)

至于唯一性,

  

唯一性验证属性的值在之前是唯一的   对象被保存。它不会创建唯一性约束   数据库,所以它可能发生两个不同的数据库连接   为您想要的列创建两个具有相同值的记录   独一无二。为避免这种情况,您必须在两者上创建唯一索引   数据库中的列。

此外,如果您只是在模型级别validates_uniqueness_of,那么您将被限制从rails侧插入重复记录但不在数据库级别。 通过dbconsole进行SQL注入查询会毫无问题地插入重复的记录。

当你说你在“posts”表中创建了一个带有“user_id”索引的外键时,默认情况下rails只会在其上创建index而不是unique index。如果你有1-M关系,那么在你的情况下,唯一索引就没有意义了。

如果您的帖子表中有unique: true“user_id” 然后没有办法让具有相同“user_id”的重复记录通过

答案 2 :(得分:2)

Db唯一索引,我引用此SO question是:

  

数据库中的唯一索引也是该列的索引   强制执行约束,你不能有两个相等的值   两列不同的列

虽然 ROR唯一性验证应该从应用程序级别执行相同操作,这意味着以下方案可能很少但很容易发生:

  • 用户A提交表单
  • Rails检查数据库是否存在用户A-未找到的现有ID
  • 用户B提交表单
  • Rails检查数据库中是否存在用户B-未找到的现有ID
  • Rails保存用户A记录
  • Rails保存用户B记录

一个月前发生在我身上并建议使用this SO question中的数据库唯一索引来解决它

顺便说一下,这个解决方法在Rails中很好地 documented

  

解决此问题的最佳方法是添加唯一索引   数据库表使用   ActiveRecord的:: ConnectionAdapters :: SchemaStatements#add_index。在里面   发生竞争条件的罕见情况,数据库将保证   该领域的独特性