Ale*_*eue 3 postgresql activerecord race-condition select-insert ruby-on-rails-3
我有以下代码:
rating = user.recipe_ratings.where(:recipe_id => recipe.id).where(:delivery_id => delivery.id).first_or_create
Run Code Online (Sandbox Code Playgroud)
然而不知何故,我们偶尔会遇到PG::Error: ERROR: duplicate key value violates unique constraint错误。我想不出任何应该发生的原因,因为重点first_or_create是防止这些情况发生。
这只是一个疯狂的竞争条件吗?我怎样才能在没有一系列令人发狂的begin...rescue块的情况下解决这个问题?
这似乎源于“SELECT 或 INSERT”情况的典型竞争条件。
Ruby 在其实现中似乎更注重性能而非安全性。引用《Ruby on Rails 指南》:
该
first_or_create方法检查first是否返回nil。如果它确实返回nil,则create调用。...
该方法生成的 SQL 如下所示:
Run Code Online (Sandbox Code Playgroud)SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 BEGIN INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57') COMMIT
如果这是实际的实现(?),它似乎完全开放竞争条件。另一个交易可以很容易地SELECT在第一个交易的SELECT和 之间进行INSERT。然后尝试它自己的INSERT,这会引发您报告的错误,因为第一个事务同时插入了该行。
通过数据修改 CTE,竞争条件的时间范围可以大大缩短。即使是安全版本也不会花费那么多。但我想他们有他们的理由。
比较这个安全的实现: