如何在Rails/ActiveRecord中锁定父记录?

3

如果一个父记录关联着多个子记录,在父记录上使用行锁是确保一致性的显而易见的方法。然而,在ActiveRecord中,我似乎找不到一个清晰简洁的方法来实现这一点。


例如,我们有两个模型:OrderOrderProduct

class Order < ActiveRecord::Base
  has_many :order_products
  ...
end

class OrderProduct < ActiveRecord::Base
  belongs_to :order
  ...
end

更新OrderProduct会影响到Order的整体状态,因此我们希望确保在任何时候只有一个事务在更新Order

如果我们尝试在编辑OrderProduct时实现这一点,在ruby中最清晰的方法是:

def edit
  product = OrderProduct.find params[:id]
  Order.transaction do
    product.order.lock!
    # Make sure no changes have occurred while we were waiting for the lock
    product.reload

    # Do stuff...
    product.order.some_method
  end
end

然而,这种方法在 SQL 查询中效率较低,会产生以下结果:
SELECT "order_products".* FROM "order_products" WHERE "order_products"."id" = $1 LIMIT 1 [["id", "2"]]
SELECT "orders".* FROM "orders" WHERE "orders"."id" = 2 LIMIT 1
SELECT "orders".* FROM "orders" WHERE "orders"."id" = $1 LIMIT 1 FOR UPDATE  [["id", 2]]
SELECT "order_products".* FROM "order_products" WHERE "order_products"."id" = $1 LIMIT 1 [["id", 2]]
SELECT "orders".* FROM "orders" WHERE "orders"."id" = 2 LIMIT 1

我们可以通过将查询语句更改为以下内容,来减少查询次数:

def edit
  product = OrderProduct.find params[:id]
  Order.transaction do
    order = Order.find product.order_id, lock: true
    # Make sure no changes have occurred while we were waiting for the lock
    product.reload
    # Cache the association
    product.order = order

    # Do stuff...
    product.order.some_method
  end
end

哪一个可以生成更好的SQL语句:
SELECT "order_products".* FROM "order_products" WHERE "order_products"."id" = $1 LIMIT 1 [["id", "2"]]
SELECT "orders".* FROM "orders" WHERE "orders"."id" = 2 LIMIT 1 FOR UPDATE  [["id", 2]]
SELECT "order_products".* FROM "order_products" WHERE "order_products"."id" = $1 LIMIT 1 [["id", 2]]

然而,这段代码比较混乱。

使用ActiveRecord有更简洁的方法吗?仅仅为了缓存关联,调用product.order = order似乎有些危险。

2个回答

0

1
我知道文档中提到的方法,但它们似乎在关联方面效果不佳(请参见上面的SQL)。我已经在问题中添加了一些进一步的代码示例。 - Felix

0
.lock.load

你可能正在寻找的是这个。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接