ActiveRecord touch对多个工作人员是并发安全的吗?

时间:2018-04-03 21:52:22

标签: ruby-on-rails activerecord concurrency locking

想象一下,我有两个工作人员(W1,W2)并行运行相同的工作(sidekiq不保证作业只运行一次,因此可能会发生)。

作业:

car.touch(:read_at)
cars = Car.where.not(read_at: nil).order(read_at: :asc).limit(1)
send_user_email if cars.include?(car)

问题的非并发版本:

  • 19:00:01:W1触及read_at
  • 19:00:02:W1查询汽车,使用car获取最旧的not read_at.nil?
  • 19:00:03:W1向用户发送电子邮件。
  • 19:00:04:W2触及read_at
  • 19:00:05:W2查询汽车,使用car获取最旧的not read_at.nil?
  • 19:00:06:W2返回,没有发送电子邮件。
  • 用户收到1封电子邮件。

问题的并发版本:

  • 19:00:01:W1触及read_at。 AR使用read_at=19:00:01向DB发送写入。 W1与DB有连接问题,写入尚未执行。
  • 19:00:02:W2触及read_at。 AR使用read_at=19:00:02向DB发送写入。 W2连接良好,执行DB写操作。
  • 19:00:03:W2查询汽车,使用car获取最旧的not read_at.nil?
  • 19:00:04:W2向用户发送电子邮件,W1写入仍然没有被执行。
  • 19:00:05:对DB执行W1写入。
  • 19:00:06:W1查询汽车,使用car获取最旧的not read_at.nil?。现在来自W1的汽车是最老的,写入是在W2之后执行的,但由于命令是read_at=19:00:01,因此来自W1的汽车将比来自W2的汽车早read_at=19:00:02
  • 19:00:07:W1向用户发送电子邮件。
  • 用户收到2封电子邮件。

这种情况(并发版本)是否可能发生? 如果是这样,AR是否有理由不使用DBs函数来更改touch的时间戳(例如,postgresql NOW())。

使用NOW()会使W1 read_at始终比W read_at更新。

1 个答案:

答案 0 :(得分:0)

为什么Rails不使用数据库本机函数的一个很好的理由(如果不是主要原因)是您的数据库对Rails应用程序正在使用的时区一无所知。

一旦你能够configure a timezone到你的数据库服务器之外的其他东西,每当你调用car.touch(:read_at)时,Rails需要告诉你的数据库究竟会在那里保留什么。

要记住的另一件事是你的工人应该是idempotent。换句话说,您应该让您的代码了解这些可能的竞争条件并保护自己免受这些竞争条件的影响。您可能已经想到的一个解决方案是为数据库事务添加适当的锁。