想象一下,我有两个工作人员(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)
问题的非并发版本:
read_at
。car
获取最旧的not read_at.nil?
。read_at
。car
获取最旧的not read_at.nil?
。问题的并发版本:
read_at
。 AR使用read_at=19:00:01
向DB发送写入。 W1与DB有连接问题,写入尚未执行。read_at
。 AR使用read_at=19:00:02
向DB发送写入。 W2连接良好,执行DB写操作。car
获取最旧的not read_at.nil?
。car
获取最旧的not read_at.nil?
。现在来自W1的汽车是最老的,写入是在W2之后执行的,但由于命令是read_at=19:00:01
,因此来自W1的汽车将比来自W2的汽车早read_at=19:00:02
。这种情况(并发版本)是否可能发生?
如果是这样,AR是否有理由不使用DBs函数来更改touch
的时间戳(例如,postgresql NOW()
)。
使用NOW()
会使W1 read_at
始终比W read_at
更新。
答案 0 :(得分:0)
为什么Rails不使用数据库本机函数的一个很好的理由(如果不是主要原因)是您的数据库对Rails应用程序正在使用的时区一无所知。
一旦你能够configure a timezone到你的数据库服务器之外的其他东西,每当你调用car.touch(:read_at)
时,Rails需要告诉你的数据库究竟会在那里保留什么。
要记住的另一件事是你的工人应该是idempotent。换句话说,您应该让您的代码了解这些可能的竞争条件并保护自己免受这些竞争条件的影响。您可能已经想到的一个解决方案是为数据库事务添加适当的锁。