当前位置: 首页 > news >正文

MySQL高并发下 SELECT ... FOR UPDATE 性能差的庖丁解牛

MySQL 高并发下SELECT ... FOR UPDATE性能差,本质是行锁竞争 + 事务持有时间过长导致的锁等待与吞吐下降。它并非“不好用”,而是在错误场景下被滥用


一、核心原理:FOR UPDATE如何工作?

▶ 1.加锁机制
  • InnoDB 行锁
    • SELECT ... FOR UPDATE会对结果集所有行排他锁(X Lock)
    • 其他事务无法读取(若未开启 MVCC 快照读)或修改这些行
  • 锁范围
    • 若无索引 →全表扫描 + 锁所有行(灾难!)
    • 若有索引 →仅锁命中行
▶ 2.事务生命周期
MySQL事务2事务1MySQL事务2事务1BEGINSELECT * FROM seats WHERE id=100 FOR UPDATE返回数据 + 加锁SELECT * FROM seats WHERE id=100 FOR UPDATE阻塞(等待 T1 释放锁)COMMIT返回数据

💡核心认知
FOR UPDATE的性能 = 锁粒度 × 事务时长 × 并发度


二、性能瓶颈:三大致命问题

▶ 1.锁粒度过大
  • 场景
    -- 无索引字段查询SELECT*FROMordersWHEREuser_id=123FORUPDATE;
  • 后果
    • 全表扫描 → 锁住所有行(即使只返回 1 行)
    • 并发度 ≈ 1(其他事务全部阻塞)
▶ 2.事务持有时间过长
  • 场景
    $pdo->beginTransaction();$seat=$pdo->query("SELECT * FROM seats WHERE id=100 FOR UPDATE")->fetch();// 调用第三方支付 API(耗时 2 秒!)$paymentResult=callPaymentAPI($seat);if($paymentResult){$pdo->exec("UPDATE seats SET status=2 WHERE id=100");}$pdo->commit();
  • 后果
    • 行锁持有 2 秒 → 其他请求排队等待
    • 吞吐量从 1000 QPS 降至 50 QPS
▶ 3.死锁风险
  • 场景
    • 事务 A 锁 seat 100 → 尝试锁 seat 101
    • 事务 B 锁 seat 101 → 尝试锁 seat 100
  • 后果
    • MySQL 检测到死锁 → 回滚其中一个事务 → 重试成本高

三、工程优化:四层解决方案

▶ 方案 1:缩小锁粒度(最有效)
  • 必须为 WHERE 字段加索引
    -- 添加索引ALTERTABLEseatsADDINDEXidx_train_status(train_id,status);-- 优化查询SELECTidFROMseatsWHEREtrain_id=100ANDstatus=0LIMIT1FORUPDATE;-- 仅锁 1 行
▶ 方案 2:缩短事务时长
  • 两阶段提交
    // 阶段1:锁定座位(短事务)$pdo->beginTransaction();$stmt=$pdo->prepare("SELECT id FROM seats WHERE train_id=? AND status=0 LIMIT 1 FOR UPDATE");$stmt->execute([$trainId]);$seatId=$stmt->fetchColumn();if($seatId){$pdo->exec("UPDATE seats SET status=1 WHERE id=?",[$seatId]);// 标记为已锁定}$pdo->commit();// 阶段2:异步处理支付(无锁)if($seatId){dispatchPaymentJob($seatId);// 放入队列}
▶ 方案 3:无锁设计(终极方案)
  • 预分配座位池
    -- 余票计数表CREATETABLEtrain_inventory(train_idINTPRIMARYKEY,availableINTNOTNULL);
  • 原子扣减
    // 乐观锁扣减库存$stmt=$pdo->prepare(" UPDATE train_inventory SET available = available - 1 WHERE train_id = ? AND available > 0 ");if($stmt->execute([$trainId])&&$stmt->rowCount()>0){// 分配具体座位(无锁)assignSeat($trainId);}
▶ 方案 4:Redis Lua 脚本(超高并发)
  • Lua 脚本保证原子性
    -- check_and_lock.lualocalavailable=redis.call('GET',KEYS[1])iftonumber(available)>0thenredis.call('DECR',KEYS[1])return1endreturn0
  • PHP 调用
    $locked=$redis->eval(file_get_contents('check_and_lock.lua'),["train:100:seats"],1);

四、避坑指南

陷阱破局方案
无索引使用 FOR UPDATE必须为 WHERE 字段加联合索引
事务中调用外部 API用两阶段提交分离锁与业务逻辑
盲目增加超时时间优化锁粒度比调大innodb_lock_wait_timeout更有效

五、终极心法

**“FOR UPDATE 不是枷锁,
而是精度的标尺——

  • 当你缩小粒度
    你在释放并发;
  • 当你缩短持有
    你在提升吞吐;
  • 当你拥抱无锁
    你在铸造韧性。

真正的高并发,
始于对锁的敬畏,
成于对细节的精控。”


结语

从今天起:

  1. 所有FOR UPDATE查询必须有索引
  2. 事务内禁止调用外部 API
  3. 超高并发场景优先考虑 Redis 无锁方案

因为最好的并发控制,
不是加更多锁,
而是精准控制每一比特的竞争。

http://www.jsqmd.com/news/324926/

相关文章:

  • PHP瓶颈的庖丁解牛
  • 2026年口碑好的镀锌电缆桥架/防火电缆桥架厂家最新TOP排行榜
  • YOLO11避坑指南:常见问题与解决方案汇总
  • 【Effective Modern C++】第三章 转向现代C++:15. 尽可能使用constexpr
  • 『NAS』Typora平替,一款所见即所得的MD编辑器-Haptic
  • 2026年口碑好的冷拉型钢轨道钢/冷拉型钢圆钢TOP实力厂家推荐榜
  • MinerU开源大模型部署案例:跨境电商平台商品图中文案OCR+多语言SEO关键词生成
  • Java多线程:synchronized方法独占锁的秘密
  • 2026年评价高的防沉触变剂/防沉剂抗流挂触变剂厂家选购指南与推荐
  • 2026年质量好的精密冷拉异型钢/冷拉异型钢六角钢实力厂家TOP推荐榜
  • 2026年比较好的注塑磁铁/粘结钕铁硼塑磁厂家推荐及选择指南
  • 天津资深大平层设计师|塑造松弛奢适居住感[特殊字符]
  • 2026年热门的卡通布牛津布/磨砂布牛津布行业内口碑厂家排行榜
  • es-api介绍 - 详解
  • 2026年质量好的纳米材料分散剂/高分子分散剂厂家最新热销排行
  • 2024信奥赛C++提高组csp-s复赛真题及题解:决斗
  • 2026年热门的工业空冷器/空冷器管束厂家最新用户好评榜
  • 版权信息要保留?开源项目使用规范
  • 互联网大厂Java面试:从分布式缓存到消息队列的技术场景解析
  • GLM-Image WebUI启动故障排查:加载失败/显存不足/依赖缺失解决方案
  • 2026年比较好的高鱼粉含量鲈鱼饲料/发酵蛋白鲈鱼饲料品牌推荐榜
  • 警告!你的RAG系统正在裸奔!USENIX Security最新论文揭示90%成功率攻击手法,附防御方案
  • 2026年评价高的网带式抛丸机/通过式抛丸机用户好评厂家排行
  • 2026年质量好的热镀锌电缆桥架/定制电缆桥架TOP实力厂家推荐榜
  • 2026年知名的酸洗冷轧带钢/黑退冷轧带钢厂家实力及用户口碑排行榜
  • 2026年口碑好的涂塑支架/支架厂家最新TOP实力排行
  • 2026年口碑好的60孔催化剂/船催化剂厂家最新实力排行
  • 2026年热门的立式排污泵/耐高温排污泵最新TOP品牌厂家排行
  • 2026年知名的化工离心泵/活鱼输送离心泵厂家推荐及采购指南
  • 2026年评价高的闭式循环水冷却塔/工业冷却塔用户好评厂家排行