新手问题 并发情况下无法依赖 cache counter 的问题

Awlter1 · June 02, 2019 · Last by Rei replied at June 02, 2019 · 1119 hits
class Shelf
  field :books_count
  has_many :books
end

class Book
  belongs_to :shelf, counter_cache: :books_count
end

假设,一个 shelf 最多有 10 本书,现在书架上已有 9 本,shelf.books_count = 9

现在同时进来两个请求,尝试在 shelfs 上创建一个 book

在 controller 中 create book 时验证 books_count,但是此时两个请求拿到的都是 9,所以两个请求的验证都会通过,会出现书架上出现 11 本书的情况

请问下,这种情况下怎么处理比较好呢?redis?

Reply to Rei

谢 Rei 神,看完悲观锁之后我发现我帖子没描述清楚,现在的问题是检查是否超出限值 books_count 的逻辑和创建 book 的逻辑不是在一起的

检查限值是写在 controller 的 callback 里的,before_action :check_shelf_availability, 而且下面还有 10 几个其他 callback,才走到BooksController#create

check_shelf_availability需要在这个 callback 之前被 call 到,而且里面的逻辑是,如果大于限制,直接return render一个报错的页面

这样好像就没法把这些逻辑写在一个 transaction 里了吧

trigger 或 check constraint 都可以吧

ALTER TABLE book_shelfs ADD CONSTRAINT books_count_check CHECK (
   books_count <= 10
);

Rei 说的是锁 Shell

Shell.find(params[:id]).lock(:true)

其他线程还想读的这个 id 的 shell 的话,就要等锁。

如果你不想锁 Shell 的话,还可以先把 shell 的 book 都创建出来(不给 id),在加 book 的时候,就可以上锁了。

Redis 的分布式锁,也可以,效果应该是一样的(哦,不是,如果操作时间特别长,redis 好,因为上了 exclusive lock,所有读操作都锁了,redis 分布式锁,只锁了加入 shell 这个操作)

·用 select for update 锁记录

Reply to Awlter1
around_action :lock_shelf
before_action :check_shelf_availability

private

def lock_shelf
  @shelf = ...
  @shelf.with_lock do
    yield
  end
end
You need to Sign in before reply, if you don't have an account, please Sign up first.