mysql中多事务并发执行时的锁机制与性能优化

MySQL行锁会在WHERE条件无法使用索引(如隐式转换、函数操作、LIKE ‘%abc’)时退化为全表扫描并大量加锁,导致性能崩溃;并非真正“升级”为表锁,而是锁数量激增。

mysql中多事务并发执行时的锁机制与性能优化

MySQL 的行锁在什么情况下会升级为表锁

InnoDB 默认加的是行级锁,但不是所有查询都能走索引——一旦 WHERE 条件无法使用索引(比如隐式类型转换、函数包裹字段、LIKE '%abc'),InnoDB 就可能退化为扫描全表,进而对所有扫描过的记录加锁,甚至触发锁升级(实际是锁数量爆炸导致性能崩塌,而非真正“升级”)。

  • 检查执行计划:
    EXPLAIN SELECT * FROM orders WHERE status = 'pending';

    确保 typeref 或更优,key 显示用了哪个索引

  • 避免在索引列上做运算:WHERE YEAR(create_time) = 2024 → 改成 WHERE create_time >= '2024-01-01' AND create_time
  • 字符串比较注意字符集和排序规则:不同 COLLATION 可能导致索引失效,用 SHOW CREATE TABLE t 确认字段定义

UPDATE 语句没走索引时的锁行为有多危险

一条没命中索引的 UPDATE 在高并发下极易引发锁等待雪崩。它不只是慢,而是会持续持有大量记录的 X 锁,阻塞其他事务对这些记录的读(需要 S 锁)和写(需要 X 锁),甚至波及无关行——因为 Gap Lock 会锁住索引间隙。

  • 典型错误:UPDATE users SET balance = balance - 100 WHERE phone = 13800138000; —— 如果 phoneVARCHAR 类型,而传入数字,触发隐式转换,索引失效
  • 验证方式:开启锁监控:
    SELECT * FROM performance_schema.data_locks;

    观察 LOCK_DATALOCK_MODE 字段

  • 线上紧急缓解:临时加索引(需评估 DDL 阻塞影响),或改用主键分批更新:
    UPDATE users SET balance = balance - 100 WHERE id IN (1001,1002,1003);

如何用 SELECT … FOR UPDATE 安全地实现扣减库存

直接 UPDATE stock SET count = count - 1 WHERE id = 123 AND count > 0 有竞态风险:两个事务同时读到 count=1,都执行成功,变成 -1。必须显式加锁并检查结果。

  • 正确顺序:先查再锁,且用唯一索引(如主键或 sku_id):
    SELECT count FROM stock WHERE sku_id = 'ABC123' FOR UPDATE;
  • 应用层判断返回值是否 ≥ 1,再执行 UPDATE;不要依赖 ROW_COUNT() 做最终校验,因为锁已释放
  • 避免在 FOR UPDATE 查询里 JOIN 其他大表,否则锁范围扩大;必要时拆成两步,用 SELECT ... FOR UPDATE 查主键,再用主键更新
  • 超时控制:设置 innodb_lock_wait_timeout(默认 50 秒),并在应用层捕获 Lock wait timeout exceeded 错误重试或降级

READ COMMITTED 和 REPEATABLE READ 隔离级别对锁的影响差异

很多人以为 RC 能减少锁,其实只在“不加锁读”上宽松,写锁行为几乎一致。真正的区别在于 Gap Lock 是否启用——RR 下普通 SELECT 不加锁,但 UPDATE/DELETE 会加 Next-Key Lock(Record + Gap),而 RC 下只锁匹配到的记录,不锁间隙。

  • RC 更适合高并发更新场景(如秒杀),能显著降低死锁概率,但要接受“幻读”——不过业务上往往可接受(比如多插入几单不影响核心逻辑)
  • RR 是 MySQL 默认,安全性高,但若业务大量执行范围条件更新(如 UPDATE logs SET status=1 WHERE created_at > '2024-01-01'),Gap Lock 会锁住整个时间范围,极易阻塞
  • 切换前务必测试:修改会话级隔离级别仅影响当前连接:
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

锁机制本身不复杂,难的是在索引失效、隔离级别、事务粒度、应用重试逻辑之间找到平衡点。最容易被忽略的是:你以为只锁了一行,其实 InnoDB 锁了一片;你以为改了隔离级别就安全了,其实没配好索引照样卡死。

https://www.php.cn/faq/1991772.html

发表回复

Your email address will not be published. Required fields are marked *