MySQL/InnoDB 的事务隔离级别和锁

事务的隔离级别

读分为快照读和当前读。用 MVCC 可解决快照读的脏读、幻读问题,不需要上锁。

  • Read Uncommited
    可以读取未提交记录。此隔离级别,不会使用,忽略。

  • Read Committed (RC)
    针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。

  • Repeatable Read (RR)
    针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。

  • Serializable
    从 MVCC 并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
    Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。

具体可看

避免死锁

既然上锁,一不注意就可能出现死锁。为了避免死锁,一般有三种手段

  1. 按相同的顺序加锁,一次锁定需要的资源
  2. 加锁时限
  3. 死锁检测

具体到应用层面我们可以:

  1. 以固定的顺序访问表和行。将两个事务的 sql 顺序调整为一致
  2. 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小
  3. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率
  4. 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为 gap 锁造成的死锁
  5. 为表添加合理的索引。如果不走索引将会为表的每一行记录上锁,死锁的概率大大增大

这主要体现的是手段 1 的思想。在 InnoDB 中有死锁检测机制。InnoDB 可以主动探知到死锁,并回滚了某一苦苦等待的事务。那么它是怎么探知到死锁的呢?

  1. 直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在 InnoDB 中,参数 innodb_lock_wait_timeout 用来设置超时时间
  2. wait-for graph 原理

具体可看

InnoDB 的死锁检测机制相当于最后一道防线。也正因为 InnoDB 需要做死锁检测, 而且死锁检测算法的时间复杂度和并发事务是指数的关系(没有求证过,在一个演讲中看到这个说法),所以并发很高的时候,这个机制会占用很高的 CPU。

推荐阅读更多精彩内容