[教程] Ruby on Rails 的 first_or_initialize 会造成 race condition,解决办法是用 upsert
把 first_or_initialize 改成 upsert 就行
今天(2020-3-4)没有心情写长文配图片,简单写几句就行
这篇文章讲什么?
Ruby on Rails 里 first_or_initialize "可能"会出错。
之所以说"可能":这个问题只会在多个 Rails 实例下造成问题,
如果你只是本地开发,只有1个 Rails server。这个问题是测不出来的
对谁有用?
Ruby on Rails 程序员
有什么用?
修 bug
问题描述
程序员使用 first_or_initialize 的本质是:
要么插入,要么更新,我不管,反正你给我存下来。
如果先
xxx.first_or_initialize
然后
xxx.save
因为它是先 select check 是不是存在,然后去 insert。
所以有 race condition 问题。
如果你的数据库设置了唯一索引(比如这里我们用的是 PostgreSQL)
那么会报错。
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_apply_evaluations_on_unique_columns"
DETAIL: Key (application_id, stage_key, user_id)=(1259, interview, 57) already exists.
具体报错信息别管,大概扫一眼就行,不需要你认真读。
总之意思是数据重复了。索引不能重复。
注:如果你的数据库没有设置唯一索引,当然也不会报这个错误。
如果你的 Model 不验证任何唯一性,
那么数据库会多出两条一样的数据。
问题根源
我们线上用的是 AWS ECS,可以类比成 Docker Swarm 和 Kubernetes。
跑了3份容器。
用户点击2次按钮时,发了2个请求,这两个请求到达了3个容器里面的2个。
同时执行,同时 first_or_initialize。
都 new 了一个 Record。
然后 save 的时候,其中一个成功了,另一个就报错。
解决办法
- 不理。因为数据的确保存了,没有造成其他任何问题。
- 使用 Rails 6.0.0 里面的 upsert。
我们是怎么解决的
因为这个问题并没有阻碍用户正常使用功能。我们决定下次处理
下次的处理方式是:
- 把 Rails 从 6.0.0.rc1 更新到 6.0.0
- 改下代码,把 first_or_initialize 改成 upsert。
全文完
感谢阅读