如何限制行并防止预订系统的竞争条件

How can I limit rows and prevent race conditions for a reservation system

我正在构建产品预订系统。假设我有一个带有名称和数量字段 ("my-tshirt", 2) 的产品 table。我还有一个预订 table,它将产品 ID 映射到用户 ID。如何确保 table 只有两个保留条目?我怎样才能防止任何竞争条件?我需要为此担心吗?我读过航空公司和酒店的人说分配 15 分钟 session,但我更愿意实时进行,一旦您点击预订按钮,我们就可以立即(即 0.1-5.0 秒)告诉你有没有预定。

我正在使用 Tornado、SQLAlchemy、Postgres,并且还在使用 Redis 进行各种操作。

要回答您的问题,是的,您确实需要担心竞争条件。我建议处理问题的方法是使用 FOR UPDATE select 语句。这将允许您找到您要查找的行,锁定它并更新它,而无需在您修改它的时候另一个线程进入并查询或修改它。这样的事情应该可以解决问题:

try:
    productToUpdate = session.query(Product).filter(Product.name == "my-tshirt", Product.quantity > 0).with_for_update().one() #assume Product.name is unique and name exists
except sqlalchemy.orm.exc.NoResultFound as e:
    return "No reservations available" #Handle no reservation case here
productToUpdate.quantity -= 1
newReservation = Reservation()
newReservation.product = productToUpdate #assume relationship setup
newReservation.user = userForReservation #assume relationship setup
session.commit()

编辑:

如果您不想更新产品数量和保留单独的预订数量,有两种处理方式。

首先,正如您在问题中提出的那样,是从预订 table 中获取预订总和,并将其与可用产品数量进行比较。我建议不要这样做,因为为了避免竞争条件,您需要锁定保留 table 以防止插入行,这会影响其他线程,并且可能会根据您的 table 使用情况大幅减慢速度。如果不锁定 table,另一个线程可能会在您查询总数之后,在您创建另一个条目之前插入预订。

第二种方法,也是我的建议,是在您的产品 table 中添加一列用于预订计数,并在您的预订 table 的同时更新该列。这仍然有可能在阅读后将预订插入到 table 中,但是如果您的应用程序在未锁定产品 table 的情况下没有插入到预订 table 中,您可以确保您的数据准确无误。

try:
    productToUpdate = session.query(Product).filter(Product.name == "my-tshirt", Product.quantity - Product.reservations >= quantityToReserve).with_for_update().one() #assume Product.name is unique and name exists
except sqlalchemy.orm.exc.NoResultFound as e:
    return "No reservations available" #Handle no reservation case here
productToUpdate.reservations += quantityToReserve
#can put in another query and logic here to see if you're updating a reservation, or creating a new one
newReservation = Reservation(reservationQuantity = quantityToReserve)
newReservation.product = productToUpdate #assume relationship setup
newReservation.user = userForReservation #assume relationship setup
session.commit()

有关 postgres 的 FOR UPDATE 的一些阅读是 here

关于 with_for_update() 的一些阅读是 here