如果一个父记录关联着多个子记录,在父记录上使用行锁是确保一致性的显而易见的方法。然而,在ActiveRecord中,我似乎找不到一个清晰简洁的方法来实现这一点。
例如,我们有两个模型:Order
和 OrderProduct
。
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
似乎有些危险。