我一直在关注这个教程http://ruby.railstutorial.org/chapters/modeling-users?version=3.2#top,我发现它很棒,但是它提到了一些我没有得到的唯一性属性。 到目前为止,这是mu用户文件:
class User < ActiveRecord::Base
#these attributes can be modified by the users
attr_accessible :name, :email;
#validation testing
validates :name, presence: true, length: { maximum: 50 }
#regular expression (there is an official one)
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
#and add it..
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
它说:
“使用验证:唯一性不保证唯一性。
Alice意外点击“提交”两次,快速连续发送两个请求。 发生以下序列:请求1在内存中创建通过验证的用户,请求2执行相同的操作,请求1的用户被保存,请求2的用户被保存。 结果:尽管具有唯一性验证,但两条用户记录具有完全相同的电子邮件地址。“
我尝试使用控制台(和User.create方法)使用相同的电子邮件地址创建2个用户,并且唯一性似乎工作正常,因为只有第一个进入sqlite3。那么什么可能导致错误或唯一性失败?
答案 0 :(得分:4)
我相信教程试图提出的是,唯一性验证是由应用程序层强制执行的,而不是由数据库强制执行的。如果您的系统可以同时处理多个请求,则:唯一性验证会受到所谓的竞争条件的影响。
唯一性的作用方式是(1)在数据库中查询匹配的记录。如果未找到匹配的记录,则验证通过并且(2)将记录保存到数据库。在步骤(1)和(2)之间,理论上可能(尽管不太可能)另一个请求突然进入并向数据库添加匹配记录。
通过在数据库中强制执行唯一性,可以避免此问题。如何做到这取决于您使用的数据库; Rails不会帮助你这样做。
答案 1 :(得分:1)
如果有两个RoR实例与同一个SQL服务器通信,则可能存在以下情形:
process A reads, no email like 'foo@bar'
process B reads, no email like 'Foo@BAR'
process A writes new User with 'foo@bar'
process B writes new User with 'Foo@BAR'
Ruby一次不支持多个Thread
,因此您需要两个单独的RoR流程。
如果您在SQL中编写唯一性强制执行,或使用DataMapper
而不是ActiveRecord
,则可以解决此问题。
答案 2 :(得分:0)
当两个并行进程尝试保存冲突对象时,约束失败
Process 1 Process 2
1. Create Object in Memory
2. Create Object in Memory
3. Check DB -> valid
4. Check DB -> valid
现在两个进程都认为他们已经验证了他们的唯一性约束并继续保存对象。
5. save
6. save
现在的问题是,两个进程在保存对象之前都会检查唯一性。因此,两个流程都会看到检查时没有冲突。冲突仅在步骤5之后才开始存在。现在的问题是,在步骤6和过程6中没有检查它因此保存了一个不遵循唯一约束的无效对象。
要缓解这种情况,您可以在数据库上创建唯一索引。这肯定会确保您无法将无效数据插入数据库,因为数据库以原子方式检查约束,因此不再发生竞争条件。
现在你可以只依赖那个独特的索引了。但是,如果发生错误,您将注意到不会收到错误消息。所以在实践中你应该同时使用两者。因此,对于大多数情况,您可以依赖于良好集成的rails唯一约束。对于罕见的竞争条件,您可以依靠唯一索引来确保您的数据100%保存。