导读:本期聚焦于小伙伴创作的《mysql在高并发下如何防止超卖?利用悲观锁select_for_update保证原子性可行吗》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《mysql在高并发下如何防止超卖?利用悲观锁select_for_update保证原子性可行吗》有用,将其分享出去将是对创作者最好的鼓励。

高并发场景下的库存扣减是很多业务系统需要面对的典型问题,当大量用户同时发起下单请求时,如果没有做好并发控制,很容易出现库存被过度扣减的超卖情况,影响业务的正常开展。mysql提供的悲观锁机制中的select_for_update语法,可以在一定程度上保证库存操作的原子性,避免超卖问题。

mysql在高并发下如何防止超卖?利用悲观锁select_for_update保证原子性可行吗

超卖问题的产生原因

超卖问题的核心原因是多个并发请求同时读取到相同的库存值,然后各自基于这个旧值进行扣减操作,最终覆盖彼此的更新结果。我们可以通过一个简单的流程来理解:

  • 请求A查询当前库存为10,准备扣减1,计算后库存为9
  • 请求B同时查询当前库存也为10,准备扣减1,计算后库存为9
  • 请求A先将库存更新为9,请求B随后也将库存更新为9
  • 最终库存显示为9,但实际已经售出2件商品,出现超卖

select_for_update悲观锁的工作原理

select_for_update是mysql InnoDB引擎提供的行级锁语法,属于悲观锁的一种实现。它的作用是:在执行查询时,对符合条件的行加上排他锁,其他事务如果要对这些行执行update、delete或者也加for update的查询,都会被阻塞,直到当前事务提交或者回滚释放锁。

需要注意的是,select_for_update只有在事务中才会生效,并且查询条件必须命中索引,才能触发行级锁,否则会升级为表级锁,严重影响并发性能。

使用select_for_update防止超卖的实现步骤

1. 准备测试表结构

首先创建库存表,用于存储商品的库存信息:

-- 创建商品库存表
CREATE TABLE `product_stock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NOT NULL COMMENT '商品ID',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_product_id` (`product_id`) COMMENT '商品ID唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品库存表';

-- 插入测试数据,商品ID为1001的商品库存为10
INSERT INTO `product_stock` (`product_id`, `stock`) VALUES (1001, 10);

2. 核心扣减库存逻辑实现

在事务中使用select_for_update查询库存并加锁,然后判断库存是否充足,再进行扣减操作:

-- 开启事务
START TRANSACTION;

-- 查询商品库存并加悲观锁,条件命中idx_product_id索引,触发行级锁
SELECT `stock` FROM `product_stock` WHERE `product_id` = 1001 FOR UPDATE;

-- 假设查询到的stock为10,判断库存是否大于等于扣减数量
-- 这里可以在应用层判断,也可以在sql中判断,以下是在应用层判断的逻辑示例
-- 如果库存充足,执行扣减操作
UPDATE `product_stock` SET `stock` = `stock` - 1 WHERE `product_id` = 1001 AND `stock` >= 1;

-- 判断更新是否生效,如果受影响行数为1,说明扣减成功
-- 如果受影响行数为0,说明库存不足,回滚事务
-- 提交事务
COMMIT;

3. 应用层代码示例(以Python为例)

结合应用层的代码,完整实现防止超卖的逻辑:

import pymysql

def deduct_stock(product_id, deduct_num):
    # 建立数据库连接
    conn = pymysql.connect(
        host='127.0.0.1',
        user='root',
        password='123456',
        database='test_db',
        charset='utf8mb4'
    )
    cursor = conn.cursor()
    try:
        # 开启事务
        conn.begin()
        # 执行加锁查询
        cursor.execute("SELECT stock FROM product_stock WHERE product_id = %s FOR UPDATE", (product_id,))
        result = cursor.fetchone()
        if not result:
            raise Exception("商品不存在")
        current_stock = result[0]
        if current_stock < deduct_num:
            raise Exception("库存不足")
        # 执行扣减更新
        cursor.execute(
            "UPDATE product_stock SET stock = stock - %s WHERE product_id = %s AND stock >= %s",
            (deduct_num, product_id, deduct_num)
        )
        # 提交事务
        conn.commit()
        return True
    except Exception as e:
        # 异常回滚事务
        conn.rollback()
        print(f"扣减库存失败:{e}")
        return False
    finally:
        cursor.close()
        conn.close()

# 调用示例,扣减商品1001的1个库存
deduct_stock(1001, 1)

使用select_for_update的注意事项

  • 必须开启事务,否则for update锁会立即释放,无法起到并发控制作用
  • 查询条件必须命中索引,避免行锁升级为表锁,影响整体并发性能
  • 锁的持有时间要尽可能短,事务中不要包含无关的业务逻辑,减少阻塞其他请求的时间
  • 如果库存扣减后需要创建订单等其他操作,要评估这些操作的耗时,避免长事务
  • 该方案适合并发量不是特别高的场景,超高并发场景下可以考虑结合redis等缓存组件做前置库存控制

与其他方案的对比

除了select_for_update悲观锁,还可以使用乐观锁、分布式锁等方案防止超卖,以下是简单的对比:

方案实现方式适用场景优缺点
select_for_update悲观锁事务中加行级排他锁中等并发,数据一致性要求高实现简单,一致性有保障;并发高时阻塞多,性能下降
乐观锁(版本号/库存条件判断)更新时带版本号或库存条件并发较高,冲突概率低无阻塞,性能好;冲突多时会大量重试,影响体验
redis分布式锁基于redis的setnx等实现锁高并发,分布式场景性能好,支持分布式;实现复杂,需要处理锁过期等问题

在实际业务中,可以根据自身的并发量级和业务需求选择合适的方案,select_for_update是中小并发场景下实现简单、可靠性高的选择。

mysqlselect_for_update悲观锁超卖原子性修改时间:2026-06-10 13:45:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。