mysql中事务处理的隔离性与并发问题分析

1次阅读

mysql默认隔离级别是repeatable read,因innodb通过mvcc+间隙锁兼顾不可重复读与幻读防控,保障一致性快照稳定;但幻读未被完全杜绝,仅限已执行select条件范围内。

mysql中事务处理的隔离性与并发问题分析

MySQL 默认隔离级别为什么是 REPEATABLE READ 而不是 READ COMMITTED

因为 InnoDB 在 REPEATABLE READ 下通过多版本并发控制(MVCC)+ 间隙锁(Gap Lock)能同时解决「不可重复读」和「幻读」问题,而多数业务场景更看重一致性快照的稳定性。但要注意:这不等于完全杜绝幻读——仅针对当前事务中已执行过的 SELECT 语句,后续新条件的 SELECT 仍可能看到新插入行(除非加 SELECT ... for UPDATE 或显式锁表)。

常见误解是认为 REPEATABLE READ 能彻底屏蔽幻读,实际它靠的是“一致性读视图在事务开启时就固定”,所以同一事务内多次 SELECT 结果一致;但其他事务插入并提交的新记录,在本事务下次 SELECT 时仍可见(除非用了范围条件且触发了间隙锁)。

SELECT ... FOR UPDATE 在什么情况下会锁住整张表

当查询条件无法使用索引时,InnoDB 会退化为表级锁(本质是给每一行加 X 锁,但因无索引扫描全表,效果等同于锁表)。比如:

SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;

status 字段没有索引,这条语句将对 orders 表中所有行加行锁,导致高并发下严重阻塞。

  • 确认是否走索引:用 EXPLAIN 查看 type 是否为 ALLindex
  • 避免隐式类型转换:如 WHERE user_id = '123'user_idint),会导致索引失效
  • 注意 OR 条件:部分 OR 组合会让优化器放弃索引合并,改走全表扫描

如何判断一个事务是否被死锁并自动回滚

MySQL 检测到死锁后,会主动选择一个事务作为牺牲者(victim),将其回滚,并返回错误:Error 1213 (40001): Deadlock found when trying to get lock; try restarting transaction。这个过程由 InnoDB 自动完成,无需人工干预。

但要注意以下几点:

  • 死锁检测默认开启(innodb_deadlock_detect = ON),关闭后虽降低开销,但可能导致事务长时间等待
  • 被选为 victim 的事务不一定是最小或最短的——InnoDB 基于事务持有锁的数量与权重综合判断
  • 应用层必须捕获该错误并重试,否则业务逻辑会中断(尤其在支付、库存扣减等场景)
  • 可通过 SHOW ENGINE INNODB STATUSG 查看最近一次死锁详情,包括每个事务持有的锁、等待的锁、SQL 语句等

并发更新同一行时,UPDATE ... WHERESELECT ... FOR UPDATE 的行为差异

二者都会上写锁(X 锁),但时机和粒度不同:

  • UPDATE ... WHERE id = 1:先定位行,再加 X 锁,然后修改。如果 WHERE 条件匹配多行,会逐行加锁;若无匹配行,则不加任何锁
  • SELECT ... FOR UPDATE:先加锁再读取,即使后续不做修改,锁也会持续到事务结束。它更适合“读-改-写”分离明确的场景(如先查余额,再判断是否足够,最后扣减)
  • 性能影响:单纯更新建议直接用 UPDATE,减少一次网络往返;需要前置校验逻辑时,用 SELECT ... FOR UPDATE 更安全,避免检查与更新之间的窗口期被篡改
  • 注意 UPDATE 的原子性:即使两个事务同时执行 UPDATE t SET x = x + 1 WHERE id = 1,InnoDB 保证最终结果是 +2,但中间会串行化执行,第二个事务需等待第一个释放锁

真正容易被忽略的是:隔离级别只约束普通 SELECT,而所有带锁读(FOR UPDATELOCK IN SHARE MODE)和写操作(INSERT/UPDATE/delete)始终使用当前最新版本数据,不受 MVCC 快照影响。这意味着你在 REPEATABLE READ 事务里执行了两次 SELECT ... FOR UPDATE,第二次看到的可能是其他事务刚提交的新值——这不是 bug,是设计如此。

text=ZqhQzanResources