perf: 消除出库列表和还库操作的 N+1 查询,改用批量 IN + joinedload

This commit is contained in:
DXC
2026-05-19 09:49:30 +08:00
parent d60e1c5188
commit 48651ffd01
2 changed files with 111 additions and 51 deletions

View File

@ -114,12 +114,12 @@ class TransService:
@staticmethod
def process_return(data, operator_name):
"""
还库逻辑(支持部分归还)
1. 校验本次归还数量不能大于待还数量
2. 恢复可用库存(按本次归还数量)
3. 更新库位 (如果有变动)
4. 记录库管签字
5. 更新归还数量和状态(部分归还/全部归还)
还库逻辑(支持部分归还)- 已优化,消除 N+1 和长事务死锁风险
四步走策略:
1. 收集所有 borrow_id
2. 批量锁定借用记录
3. 收集库存ID并批量锁定库存
4. 内存中完成业务逻辑
"""
items = data.get('items', [])
signature = data.get('signature_path') # 库管签字
@ -130,15 +130,60 @@ class TransService:
model_map = {'stock_buy': StockBuy, 'stock_semi': StockSemi, 'stock_product': StockProduct}
try:
# ==========================================
# ★ 优化步骤 1收集所有 borrow_id
# ==========================================
borrow_ids = []
item_map = {} # 存储原始 item 数据key=borrow_id
for item in items:
borrow_id = item.get('id')
# 前端传入的本次归还数量
return_qty = float(item.get('return_qty', 0))
# 前端如果没有填 return_location应该在提交前处理好或者这里做 fallback
# 这里假设前端传来的 return_location 就是最终要保存的库位
final_location = item.get('return_location')
if borrow_id:
borrow_ids.append(borrow_id)
item_map[borrow_id] = {
'return_qty': float(item.get('return_qty', 0)),
'final_location': item.get('return_location')
}
record = TransBorrow.query.with_for_update().get(borrow_id)
if not borrow_ids:
raise ValueError("没有有效的归还记录")
# ==========================================
# ★ 优化步骤 2批量锁定借用记录
# ==========================================
borrow_records = TransBorrow.query.with_for_update().filter(
TransBorrow.id.in_(borrow_ids)
).all()
borrow_map = {r.id: r for r in borrow_records}
# ==========================================
# ★ 优化步骤 3收集库存ID并批量锁定库存
# ==========================================
stock_ids_by_table = {'stock_buy': set(), 'stock_semi': set(), 'stock_product': set()}
for borrow_id, record in borrow_map.items():
if record.source_table in stock_ids_by_table and record.stock_id:
stock_ids_by_table[record.source_table].add(record.stock_id)
stock_map = {} # 格式: { ('stock_buy', 101): stock_obj }
for table_name, ids in stock_ids_by_table.items():
if not ids:
continue
ModelClass = model_map[table_name]
stocks = ModelClass.query.with_for_update().filter(
ModelClass.id.in_(ids)
).all()
for stock in stocks:
stock_map[(table_name, stock.id)] = stock
# ==========================================
# ★ 优化步骤 4内存中完成业务逻辑
# ==========================================
for borrow_id, item_data in item_map.items():
return_qty = item_data['return_qty']
final_location = item_data['final_location']
record = borrow_map.get(borrow_id)
if not record:
continue
@ -153,22 +198,19 @@ class TransService:
if return_qty > pending_qty:
raise ValueError(f"本次归还数量({return_qty})不能大于待还数量({pending_qty})")
ModelClass = model_map.get(record.source_table)
if ModelClass:
stock = ModelClass.query.with_for_update().get(record.stock_id)
if stock:
# 1. 恢复可用库存(按本次归还数量)
stock.available_quantity = float(stock.available_quantity) + return_qty
# 更新库存
stock = stock_map.get((record.source_table, record.stock_id))
if stock:
# 恢复可用库存
stock.available_quantity = float(stock.available_quantity) + return_qty
# 更新库位
if final_location:
stock.warehouse_location = final_location
# 2. 更新库位 (如果提供了有效值)
if final_location:
stock.warehouse_location = final_location
# 3. 更新归还数量
# 更新归还数量和状态
new_returned_qty = returned_qty + return_qty
record.returned_quantity = new_returned_qty
# 4. 更新状态
if new_returned_qty >= total_qty:
record.is_returned = True
record.status = 'returned'