Mysql事务的实现
Contents
ACID
- 原子性
- 一致性
- 隔离性
- 持久性
原子性
- 基于日志的REDO/UNDO机制
- undo log (回滚日志):用作回滚
- 提供回滚操作,一般是逻辑日志
- 主要有两个作用:回滚、MVCC;
- 为了实现事务原子性,要么全部完成commit,要么全部回滚rollback,Innodb用于实现mvcc(多版本并发控制)
- innodb为每行记录都实现了三个隐藏字段:
隐藏di
,6字节的事务di
,7字节的回滚指针
- 每次update操作,都会进行以下过程:用排它锁锁定该行、记录redo log、把改行修改前的值copy到undo log、修改当前行的内容(事务id就是当前事务的id,回滚指针指向undo log中的上一个版本的行数据)
- 再有其他update操作会重复上述过程,从而在undo log 里形成一条由回滚指针连接起来的链表
- innodb中存在purge线程,会定时清理那些比现在最老的活动事务id还小的undo log,从而保证undo log 不会无限增长;
- 回滚时需要根据回滚指针找出当前事务修改前的版本
- insert操作没有上一个版本,所以不需要往undo log里复制内容
- delete并不是真正的删除,只是打了一个删除的标记,真正的删除是purge线程完成的
- 一旦事务完成,所有修改必须被记录下,保证数据一致性(后面redo log解释)
隔离性
- 主要通过以下两种手段实现
- 多版本并发控制(MVCC)
- 可以在提高隔离级别的情况下,有效提高数据库并发能力;如果没有MVCC,为了提高隔离级别只能在读操作上加共享锁,所以导致在其他事务为数据加上排他锁的情况下无法读取数据(排他锁和共享锁是互斥的)
- InnoDB对MVCC的实现方式是,在需要时,通过回滚日志构造出历史版本快照!
- InnoDB通过为每行数据后添加两个额外的隐藏值来实现MVCC,这两个值分别用来记录这行数据何时被创建,以及何时过期(被删除),实际操作中记录的并不是时间,而是事务版本号,每开启一个新事务,事务的版本号就会递增。
- 锁
- 共享锁(S锁):若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 排他锁(X锁):若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
- 多版本并发控制(MVCC)
- 四种隔离级别
- 未提交读
- 读不加锁,写加排他锁,直至事务结束。
- 脏读:因为读不加锁,所以可以读取到其他事务还没有提交的数据,如果其他事务执行了回滚操作,此时读取的数据就是脏数据
- 已提交读
- 读不加锁(MVCC),写加排他锁,直至事务结束。
- 通过MVCC机制获取最新快照,如果事务还没提交,这里的最新快照指的是事务开始前的数据,所以是不会读到其他未提交事务的中间数据的,从而解决了脏读。
- 不可重复读:一次事务内的两次读取数据不一致
- 原因是在两次读取之间另外一个事务对数据进行了修改并commit。由于在这种隔离级别下,每次 select 都会通过 MVCC 获取当前数据的最新快照,所以会导致不可重复读。
- 可重复读
- 读不加锁(MVCC),写加排他锁,直至事务结束。
- 通过调整MVCC快照的生成时机,即一个事务只会在第一次 select 时生成数据快照,后续的 select 都是在这个快照之上进行的,所以无论其他事务是否提交,事务中的两次读取都是相同的,从而解决了不可重复读!
- 幻读:幻读有两种理解方式
- 两次读取不一致,读取到了其他已提交事务的新增数据(不可重复读指的是读到了修改或删除数据);
但是,这种幻读的理解方式在mysql 的 innodb 搜索引擎下是不存在的,因为已经通过多版本并发控制(MVCC)解决了这种意义上的幻读!
- 另外一种理解是,同一个事务内,前一次读操作和后一次写操作数据不一致,比如前一次读操作读到的是0,后一次insert操作却因为其他事务在两次操作之间更改数据并提交,数据不为0 从而导致 insert失败或对insert造成其他影响!。
- 发生这种情况的原因是MVCC只对读有效,对写无效!
- 两次读取不一致,读取到了其他已提交事务的新增数据(不可重复读指的是读到了修改或删除数据);
- 可串行化
- 读加共享锁,写加排他锁!
- 不会有任何问题!
- 未提交读
持久性
- 通过redo log实现
- redo log (重做日志):用来恢复提交后的物理页数据(持久化)
- 是物理日志,记录的是数据库中每个页的修改
- 包括两部分:1)内存中的日志缓冲(redo log buffer);2)磁盘上的重做日志
- 通过
force log at commit
机制实现事务的持久性; - InnoDB存储引擎层的日志。
一致性
- 一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 原子性和一致性的区别:即便原子性保持了,也可能因为隔离性的原因导致原子操作前后两次的状态不一致。
- 前面三个特性实现之后,一致性自然也实现了。
补充,MVCC在可重复读隔离级别下的增删改查
- SELECT时,读取创建版本号 <= 当前事务版本号,删除版本号为空或 > 当前事务版本号。
- INSERT时,保存当前事务版本号为行的创建版本号。
- DELETE时,保存当前事务版本号为行的删除版本号。
- UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。
reference
Author 段新朋
LastMod 2020-07-28