这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记
ACID
事务:逻辑上的一组操作,要么全部执行完成,要么都不执行
- Atomicity,原子性,事务具有原子性,是最小的执行单位
- Consistency,一致性,执行前后系统从一个正确的状态转移到另一正确的状态(状态应该是应用层定义的,所以实际事务通过AID,确保了C)
- Isolation,隔离性,并发访问数据库时,一个事务不被别的事务干扰。从结果上来看,并发的事务是按某个顺序先后执行的
- Durability,持久性,事务提交后对数据的改变是永久的,数据库故障也不会产生影响
InnoDB引擎使用redo log保障持久性,undo log保障原子性,锁保障隔离性
并发事务潜在问题
- Dirty Read:事务A正在修改X,事务B读到了事务A提交前的数据X
- Lost to Modify:事务A正在修改X,事务B的读到了事务A提交前的数据X并进行了修改,根据完成的顺序,A或B的修改丢失
- Unrepeatable Read:事务A需要多次读取X,在过程中事务B修改了X,导致事务A前后读到的数据不一样
- Phantom Read:事务A需要多次读取一个查询,在过程中事务B增删了一些数据,导致事务A前后读到的数据量不一样
事务隔离级别
- READ-UNCOMMITED,读取未提交,允许读取未提交的数据
- READ-COMMITED,读取已提交,一个事务总是读取最近一次已提交的版本
- REPEATABLE-READ,可重复读,一个事务总是读取到事务开始前最后一次提交的版本。默认
- SERIALIZABLE,可串行化,完全的ACID
锁
简介
加锁机制
- 乐观锁,假设用户的并发事务不会相互产生影响,在提交更新前才检查有没有其他事务修改了数据,如果其他事务有更新,正在提交的事务会进行回滚。实现有CAS或版本控制
- 悲观锁,对本事务外修改持保守态度,需要数据库的锁机制
兼容性
- S锁,可共享锁,读锁
- X锁,不可共享锁,写锁
锁粒度
- 表锁,通常在DDL时使用
- 行锁,只锁定设计更新的行,如果MySQL无法直接确定影响的行,会锁定所有行,如果能够过滤经过Server层过滤后释放无关的行锁
锁模式
-
行锁
-
GAP锁;GAP,即B树索引的空隙,索引可能的地方,例如index值5、10之间的gap为(5, 10)。通过GAP锁防止可能的插入,避免幻读。对于精确值,GAP锁加到左右区间。RR级别才有
-
Next-Key锁:行锁+GAP锁
-
意向锁(表级)。由于行锁与表锁粒度的不同,当一行被事务A锁定时,新的事务B想要加表锁,如果加锁成功,那么就能修改行锁锁定的内容,这显然不合理,而请求表锁时逐行判断是否已有行锁效率太低。所以引入意向锁。
- A想要加行锁时,就需要先对表加对应意向锁
- 这样B就可以直接判断表的意向锁状态
- 插入意向锁
两阶段锁协议
增长阶段事务只能获得锁,释放阶段事务只能释放锁,也就是说事务只有要做的事全部结束了才能释放锁
MVCC 多版本并发重制
- 每一次写操作都会创建一个新版本的数据
- 读操作会从有限个版本的数据中挑选一个最合适的结果直接返回
MySQL与MVCC
通过快照读可能读到历史数据,使得通常的select操作不需要加S锁,是一种类似于CAS的乐观锁设计
特殊的读操作,需要处理最新的数据,属于当前读,加锁
select * from table where ? lock in share mode // 不会阻止
select * from table where ? for update // 会阻止其他事务的读锁
MVCC实现
额外的行字段
在数据库的每一行中,添加额外的三个字段
DB_TRX_ID更新该行的最后一个事务idDB_ROLL_PTR指针,指向改行对应的undologDB_ROW_ID单调递增的行ID,AUTO_INCREMENT
当前读 & 快照读
所有事务拥有一个全局自增id,新事务创建时,事务系统会吧当前未提交事务ID集合交给他
快照读
当一个事务去读某行数据时,如果DB_TRX_ID不为空,会与当前事务id进行比较
- 如果改行读到的DB_TRX_ID在未提交事务集合中,或者大于当前事务ID,说明这个修改是在当前事务开始之后发生的。那么当前事务就需要通过DB_ROLL_PTR去读之前的值
- 如果不在集合中,或者小于当前事务ID,说明修改发生在事务开始之前,可以直接读
当前读
不通过undo log回溯到之前的数据状态
例如需要根据值进行修改时,肯定需要知道最新的值
MVCC 与 不可重复读、幻读
- 不可重复读,当前事务过程中,读到了另一个提交的修改
- 幻读,当前事务过程中,发现了另一个事务增加或删除了条目
通过快照读,解决了不可重复读的问题
如果当前事务修改到了其他事务插入并提交的数据,会导致DB_TRX_ID置为当前,那么下一次快照读就会读到之前不存在的行,发生幻读。
即使有next-key,也没有在RR下解决幻读。
设表一开始有值1、2、3
设表一开始有值1、2、3
所以,RR下能否避免幻读,与next-key的加锁时机有关