并发事务的控制方式有哪些?
Mysql中并发事务的控制就两种 锁 和 MVCC 。锁可以视作悲观控制方式、MVCC可以视作乐观控制方式。
锁
锁主要是通过显示控制而不是调度,主要是通过读写锁来实现并发控制
共享锁(S锁):又称读锁,允许事务在读取记录的时候共享锁;
排他锁(X锁):又称写锁,不允许在修改记录时共享锁;
读写锁可以做到读读并行,但读写、写写并不能并行;
锁又根据细粒度不同分为表级锁和行级锁。
MVCC
MVCC是多版本并发控制方法,即对一份数据会储存多个版本,通过事务的可见性来保证事务都能看到应该看到的版本。通常会有一个全局版本分配器为每一行分配版本,版本号是唯一的。
InnoDB对MVCC的实现
MVCC的实现依赖于:隐藏字段、Read View、undo log。在内部实现中,InnoDB通过数据行的DB_TRX_ID和Read_view来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。对于每个事务,用户只能看到这个事务创建Readview之前已经提交的事务。
隐藏字段
在内部,InnoDB为每行数据添加了三个隐藏字段:
- DB_TRX_ID:表示最后一次插入或更新该行的事务id
- DB_ROLL_PTR:回滚指针,指向该行的undo log,若该行未被更新,则为空
- DB_ROW_ID:如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引
ReadView
class ReadView {
/* ... */
private:
trx_id_t m_low_limit_id; /* 大于等于这个 ID 的事务均不可见 */
trx_id_t m_up_limit_id; /* 小于这个 ID 的事务均可见 */
trx_id_t m_creator_trx_id; /* 创建该 Read View 的事务ID */
trx_id_t m_low_limit_no; /* 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge */
ids_t m_ids; /* 创建 Read View 时的活跃事务列表 */
m_closed; /* 标记 Read View 是否 close */
}
m_low_limit_id:目前出现过的最大事务ID+1,即下一个将被分配的事务ID。大于等于这个ID的数据版本均不可见;
m_up_limit_id:活跃事务的最小ID,小于这个ID的版本均可见,如果活跃事务列表为空,m_up_limit_id = m_low_limit_id;
m_ids:Read View创建时其他未提交的活跃事务ID,不包含当前事务,和过去已经提交过的事务;
m_creator_trx_id:创建该 Read View 的事务ID;
undo-log
undo-log有两个作用:
- 当事务回滚时,回到最初修改前的yangz
- MVCC,当读取数据时,若当前数据被其他事务占领,或者当前版本对该事务不可见,可以通过undo-log读取之前的版本数据。
insert undo log 与update undo log
insert undo log是在inset操作中产生的undo log,因为insert操作只有当前事务本身可见,对其他事务不可见,顾该undo log可以在事务提交后删除;
update undo log是在update与delete中产生的undo log,该操作需要提供MVCC机制,不能在事务提交后就进行删除,而是放入undo log列表,等待purge线程最后进行删除。
数据可见性算法
在selec语句执行之前,会创建一个快照,快照中保存了当前数据库中未提交的活跃事务;
当用户在这个事务中要读取某个记录行的时候,InnoDB 会将该记录行的 DB_TRX_ID 与 Read View 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件
- 如果DB_TRX_ID < m_up_limit_id,该行记录可见
- 如果DB_TRX_ID >= m_low_limit_id,不可见,跳转5
- m_ids为空,表面当前事务创建快照之前,该事务已提交,可见
- 如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表面快照创建时该行事务处于活跃或者已提交状态,所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)
- 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
- 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
5.在该记录行的 DB_ROLL_PTR 指针所指向的 undo log 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空