summery

  1. Mysql 默认使用 RR, 在这个状态下面,其他事务线程开启修改中间的数据不能被看到,看到的是 undo log 里面上一个版本的 read views 这个主要是确保对应的最终一致性,中间数据可以快速读取,不需要像 serilizable 这样导致强一致性读取速度减慢,同时也避免了 RU 的脏读问题,和 RC 的不可重复读问题
  2. Mysql 中间有两种读操作,一种是快照读 直接 select 是查看当前事务开始之前的状态 使用 MVCC 实现对应的版本 undo log,第二种就是当前读 select ... for update / update / delete 获取的就是最新的读取数据同时加锁
  3. 锁等待只是对应的锁的修改等待前一个事务释放,死锁是 当前线程持有锁同时请求另一个锁,另一个线程持有另一个锁请求当前线程的锁,这样导致循环出现,就是构成了死锁
  4. 没有索引的where 条件就是扫全表,给全表上锁,一切 当前读的where 条件必须检查是否添加索引

index & lock

  1. 唯一索引 + 唯一条件 只会锁一条记录 || 普通索引 + 范围查询 锁住扫描范

> 原因是 唯一索引数据库中间最多只有一个数据 || 但是范围查询中间有多少个数据,同时无法确定是否这个区间会被插入和添加多少个条件数据,之后会导致直接锁整个范围,如果不加索引全表扫描,并发起来之后就会导致对应的 RT 飙高

  1. 只锁住一条记录的锁是 Record Lock || 锁住一个扫描范围

生产应对方案

1. 上线前强制规范(第一道防线)

"我们团队强制要求:所有DML语句(Update/Delete)的WHERE条件,必须先用Explain查看执行计划,确保type是ref或range,且rows扫描行数控制在1000以内。如果type=ALL(全表扫描),禁止上线。"

2. 线上监控(第二道防线)

"我们在performance_schema和慢查询日志里开启了lock_time(锁等待时长)监控。一旦发现锁等待超过1秒,立即触发告警,DBA会强制kill该会话。"

3. 索引降级策略(第三道防线)

"如果确实存在范围查询且扫描行数较多,我们会使用force index强制走特定索引,或者将范围查询拆分为分批循环(例如每次查1000条去更新),避免单次大事务锁住所有数据。"

4. 终极兜底(架构层面)

"对于核心业务表(如订单、库存),我们会采用读写分离,将查询流量引到从库;对于Update操作,直接通过分布式ID+消息队列异步落库,避免在高峰期产生大事务锁。"

生产方案

  1. 如果业务能够通过订单号唯一索引或主键定位订单,我会优先使用唯一索引等值条件,让 InnoDB 精确锁定目标记录,减少无关事务之间的锁冲突。
  2. 对高并发场景中的 UPDATE、DELETE、SELECT FOR UPDATE,必须通过 EXPLAIN 检查是否命中了合适索引,避免扫描并锁住大量无关记录。
  3. 不是“没有索引就不允许执行”,而是:没有合适索引的当前读具有很高的并发风险,上线前必须重点检查。

开发准则

  1. 不是“有索引就行”,而是要命中合适索引
  2. 唯一索引的等值查询通常锁范围最小
  3. 普通索引 + 范围查询 还是会出现 锁范围

面试回答

MySQL InnoDB 的并发控制主要靠 MVCC 和锁两套机制。普通 SELECT 通常是快照读,通过 Read View 读取事务可见版本,不加锁,所以不会阻塞其他事务修改。 但是 UPDATE、DELETE、SELECT FOR UPDATE 属于当前读,需要读取最新已提交版本,并且会加锁来保证并发修改的安全。 在 RR 隔离级别下,普通 SELECT 通常复用同一个 Read View,所以同一事务里可能一直看到旧值;但当前读仍然会看到最新版本。 锁的范围和索引访问路径有关,唯一索引等值查询通常只锁一条记录,范围查询或普通索引可能触发 next-key lock,没有合适索引可能导致扫描和锁住更多记录。 如果两个事务更新同一行,后来的事务会锁等待;如果多个事务互相等待对方持有的锁,就会形成死锁。工程上要缩短事务、保证访问顺序一致、让 SQL 命中索引,并在应用层做好幂等和重试。

隔离级别决定可见性,MVCC 负责快照读,锁负责当前读,索引决定锁范围。