mysql并发下如何控制请求顺序_mysql队列化思路

9次阅读

高并发下防超卖需用 SELECT…FOR UPDATE 行锁,必须命中索引;无强序需求可用唯一约束 +ON DUPLICATE KEY UPDATE;严格 FIFO 应交由 Redis 队列处理,避免依赖 AUTO_INCREMENT 或时间戳排序。

mysql 并发下如何控制请求顺序_mysql 队列化思路

SELECT …… FOR UPDATE 实现行级排队

在高并发更新同一行数据时(比如库存扣减),直接 UPDATE 可能导致超卖。必须让请求串行化处理该行。核心是先查再锁,且查询必须命中索引——否则会升级为表锁或锁住不相关记录。

  • SELECT stock FROM goods WHERE id = 123 FOR UPDATE 必须基于主键或唯一索引,否则 MySQL 可能加间隙锁(Gap Lock)锁住更大范围
  • 事务中不能只执行这句就提交,必须紧接着做 UPDATECOMMIT,否则锁会持续到事务结束,拖慢整体吞吐
  • 应用层需设置合理超时,例如 JDBC 的 setQueryTimeout(),避免某请求卡死导致后续全部阻塞

INSERT …… SELECT …… ON DUPLICATE KEY UPDATE 无锁 队列写入

当“排队”目标不是强一致性顺序,而是防止重复处理(如防重下单),可用唯一约束 + 插入冲突回退替代显式加锁。它比 FOR UPDATE 更轻量,但不保证全局执行顺序。

  • 建表时给业务唯一键(如 order_no)加 UNIQUE 索引
  • INSERT INTO queue_log (order_no, status, created_at) VALUES ('NO123', 'pending', NOW()) ON DUPLICATE KEY UPDATE status = VALUES(status)
  • 插入成功说明是第一个请求;冲突则说明已有同订单在处理中,当前请求可直接跳过或轮询状态

用 Redis List + Lua 做外部协调队列

MySQL 本身不提供消息队列能力,纯靠数据库实现严格 FIFO 容易成为瓶颈。更常见的做法是把“排队逻辑”下沉到 Redis,MySQL 只负责最终状态落地。

  • LPUSH queue:order 入队,BRPOP queue:order 0 阻塞取任务,天然保序
  • 关键操作必须原子:用 Lua 脚本封装「取任务 + 标记处理中 + 设置超时」,避免多个 worker 同时取到同一任务
  • MySQL 更新只在 worker 拿到任务后执行,此时已脱离并发争抢,只需保证单次更新正确性即可

为什么 不能依赖 AUTO_INCREMENT 或时间戳排序请求

很多人误以为插入时的自增 ID 或 NOW() 时间就能代表请求到达顺序,实际在并发下完全不可靠。

  • AUTO_INCREMENT 在批量插入、InnoDB 的 innodb_autoinc_lock_mode=2(默认)下可能跳号、乱序分配
  • NOW() 精度只有秒级(5.6 及以前)或微秒级(5.7+),但同一微秒内仍可能有多个请求,且写入落盘顺序≠执行顺序
  • 主从复制、连接池复用、事务延迟提交都会进一步打乱“看起来的时间顺序”
-- 错误示例:以为按 id 升序就是请求顺序 SELECT * FROM order_log ORDER BY id ASC; -- 实际可能漏掉未提交事务的记录,或被 gap lock 影响可见性

真正需要顺序控制的地方,必须显式引入锁、队列或版本号机制,而不是依赖数据库的隐式行为。

星耀云
版权声明:本站原创文章,由 星耀云 2025-12-31发表,共计1322字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources