在电商、零售等业务系统中,库存扣减是高频且核心的操作,当多个用户同时下单购买同一商品时,如果处理不当就可能出现库存被重复扣减的超卖问题。postgresql作为成熟的关系型数据库,提供了多种事务和并发控制机制,可以有效避免这类问题的发生。

超卖问题的产生原因
超卖问题的本质是并发场景下的数据竞争问题。假设当前商品库存为1,两个用户同时发起下单请求,两个请求同时查询到库存为1,都认为可以扣减,随后都执行扣减操作,最终库存会变成-1,出现超卖。这个问题的核心是没有保证库存查询和扣减操作的原子性。
基于事务和行级锁的解决方案
postgresql的行级锁可以在事务中锁定目标行,防止其他事务同时修改该行数据,结合事务的原子性可以保证库存扣减的原子性。
实现步骤
- 开启事务,设置合适的事务隔离级别,一般使用读已提交即可
- 对目标库存行加行级排他锁,查询当前库存数量
- 判断库存是否充足,充足则执行扣减操作,不足则回滚事务
- 提交事务,释放锁
代码示例
以下是使用postgresql行级锁实现库存扣减的示例代码,假设库存表结构为:
-- 库存表结构
CREATE TABLE product_stock (
product_id INT PRIMARY KEY,
stock_num INT NOT NULL CHECK (stock_num >= 0)
);
扣减库存的存储过程如下:
CREATE OR REPLACE PROCEDURE deduct_stock(
p_product_id INT,
p_deduct_num INT,
OUT p_result INT
)
LANGUAGE plpgsql
AS $$
DECLARE
v_current_stock INT;
BEGIN
-- 开启事务,在存储过程中自动包含在事务内
-- 对目标行加行级排他锁,FOR UPDATE会锁定查询到的行
SELECT stock_num INTO v_current_stock FROM product_stock WHERE product_id = p_product_id FOR UPDATE;
-- 判断库存是否充足
IF v_current_stock >= p_deduct_num THEN
UPDATE product_stock SET stock_num = stock_num - p_deduct_num WHERE product_id = p_product_id;
p_result := 1; -- 扣减成功
ELSE
p_result := 0; -- 库存不足
END IF;
-- 正常执行结束会自动提交事务,若出现异常会自动回滚
EXCEPTION
WHEN OTHERS THEN
p_result := -1; -- 执行异常
ROLLBACK;
END;
$$;
调用存储过程扣减库存的代码如下:
-- 调用存储过程,扣减商品ID为1的库存2件
CALL deduct_stock(1, 2, NULL);
-- 查看返回结果
DO $$
DECLARE
res INT;
BEGIN
CALL deduct_stock(1, 2, res);
RAISE NOTICE '扣减结果:%', res;
END;
$$;
基于乐观锁的解决方案
乐观锁假设并发冲突概率较低,不会主动加锁,而是通过版本号或者库存数值比对的方式来判断是否有其他事务修改过数据。
实现原理
在库存表中增加一个版本号字段,每次更新库存时同时更新版本号,并且更新条件中带上之前的版本号,如果更新影响的行数为0,说明版本号已经被其他事务修改,当前扣减失败。
代码示例
首先修改库存表结构,增加版本号字段:
ALTER TABLE product_stock ADD COLUMN version INT NOT NULL DEFAULT 0;
乐观锁扣减库存的SQL如下:
-- 先查询当前库存和版本号
SELECT stock_num, version INTO @current_stock, @current_version FROM product_stock WHERE product_id = 1;
-- 判断库存是否充足
IF @current_stock >= 2 THEN
-- 执行更新,条件中带上版本号,同时版本号加1
UPDATE product_stock
SET stock_num = stock_num - 2, version = version + 1
WHERE product_id = 1 AND version = @current_version;
-- 判断更新是否成功
IF ROW_COUNT() > 0 THEN
RAISE NOTICE '扣减成功';
ELSE
RAISE NOTICE '扣减失败,并发冲突';
END IF;
ELSE
RAISE NOTICE '库存不足';
END IF;
两种方案的对比
| 方案类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 行级锁方案 | 并发量较高,冲突概率大的场景 | 实现简单,可靠性高,不会出现超卖 | 高并发下锁竞争会导致性能下降,长事务会长时间持有锁 |
| 乐观锁方案 | 并发量较低,冲突概率小的场景 | 不需要加锁,性能较好,不会阻塞其他事务 | 冲突概率高时会导致大量更新失败,需要重试机制 |
注意事项
- 无论使用哪种方案,都需要在库存表的
stock_num字段上添加非负约束,防止扣减后出现负数 - 行级锁方案要注意控制事务的执行时间,避免长事务占用锁资源
- 乐观锁方案如果更新失败,需要设计合理的重试机制,避免用户下单失败
- 如果业务中存在分库分表的情况,需要结合分布式锁来保证跨节点的库存扣减一致性
库存扣减的核心是保证查询和扣减操作的原子性,postgresql的事务和锁机制已经提供了完善的底层支持,开发者只需要根据业务场景选择合适的方案即可有效避免超卖问题。
postgresql事务锁并发控制库存扣减修改时间:2026-07-03 23:54:35