事务的隔离级别
读分为快照读和当前读。用 MVCC 可解决快照读的脏读、幻读问题,不需要上锁。
Read Uncommited
可以读取未提交记录。此隔离级别,不会使用,忽略。Read Committed (RC)
针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。Repeatable Read (RR)
针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。Serializable
从 MVCC 并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。
避免死锁
既然上锁,一不注意就可能出现死锁。为了避免死锁,一般有三种手段
- 按相同的顺序加锁,一次锁定需要的资源
- 加锁时限
- 死锁检测
具体到应用层面我们可以:
- 以固定的顺序访问表和行。将两个事务的 sql 顺序调整为一致
- 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率
- 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为 gap 锁造成的死锁
- 为表添加合理的索引。如果不走索引将会为表的每一行记录上锁,死锁的概率大大增大
这主要体现的是手段 1 的思想。在 InnoDB 中有死锁检测机制。InnoDB 可以主动探知到死锁,并回滚了某一苦苦等待的事务。那么它是怎么探知到死锁的呢?
- 直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在 InnoDB 中,参数 innodb_lock_wait_timeout 用来设置超时时间
- wait-for graph 原理
InnoDB 的死锁检测机制相当于最后一道防线。也正因为 InnoDB 需要做死锁检测, 而且死锁检测算法的时间复杂度和并发事务是指数的关系(没有求证过,在一个演讲中看到这个说法),所以并发很高的时候,这个机制会占用很高的 CPU。