MYSQL事务及MVCC
一个不可再分的最小工作单元,通常对应一个完整的义务流程。事务是用来管理SQL的DML语句(updata、delete、insert)。
事务的四大特征ACID
- 原子性(Atomicity): 指一个事务的所有操作要么全部成功,要么全部失败。undo log是用来回滚数据的,用于保障未提交事务的原子性。
- 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
- 隔离性(Isolation,又称独立性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,以防止多个事务的操作导致数据的不一致。包括四种隔离级别。
- 持久性(Durability):指数据在任何条件下都可以写入到磁盘上。redo log是用来恢复数据的,用于保障,已提交事务的持久化特性(记录了已经提交的操作)
四种隔离级别
- 读未提交 read uncommitted
事物A和事物B,事物A未提交的数据,事物B可以读取到。这种隔离级别会导致“脏读”。脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据 - 读已提交 read committed
事物A和事物B,事物A提交的数据,事物B才能读取到。 这种隔离级别会导致“不可重复读取”(在事务A没有修改数据的情况下,事务A两次读取的数据不一样)。例如:事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。 - 可重复读 repeatable read
事务A和事务B,事务A提交之后的数据,事务B读取不到。这种隔离级别可以避免“不可重复读取”,达到可重复读取,但会导致幻读。不可重复读是读取了其他事务更改的数据,针对update操作。幻读是读取了其他事务新增的数据,针对insert与delete操作 - 串行化 serializable
事务A和事务B,事务A在操作数据库时,事务B只能排队等待。这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发。一般不会使用。
MVCC(MultiVersion Concurrency Control)多版本并发控制机制
针对四钟隔离级别存在的问题(脏读、不可重复度、幻读),可以采用加锁(读锁(共享锁)、写锁(排他锁)、间隙锁)来解决,但这会影响性能。因此,采用MVCC来实现读写的并发操作(通常实现读已提交和可重复读)。InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间(事务ID(trx_id)),一个保存了行的过期时间(回滚指针), 当然存储的并不是实际的时间值,而是系统版本号。MVCC是通过undo log和readView来共同实现的并发控制。
-
undo log
用于存储尚未提交的事务,即某行数据的多个版本。每个版本之间通过roll_pointer(回滚指针)连接。 -
readView :用于保存未提交的事务ID(m_ids)和已创建事务的最大事务ID (max_trx_id)。readView的作用是用于判断版本链中的哪一个版本可用,版本链包括undo log和最新的修改记录,如下图所示。
总共4条规则来判断记录能否读取。creator_trx_id: 创建readView 的事务id、max_trx_id:以创建事务的最大ID值、min_trx_id: m_ids中最小的事务ID。 -
四条规则如下:
- trx_id==creator_trx_id:可以访问这个版本,代表自己访问自己;
- trx_id < min_trx_id: 可以访问这个版本,代表访问已经提交的事务;
- trx_id > max_trx_id: 不可以访问这个版本,代表访问的事务是执行select之后创建的,不能进行访问;
- min_trx_id <= trx_id <= max_trx_id: 如果 trx_id 在m_ids中(代表访问的事务没有提交),则不能访问这个版本,反之可以。
- MVCC如何实现 read committed
每一条select 都会有一个readView。在MVCC中,一个事务开始后,每遇到一个select就会创建一个readView, 当两个select之间有新的事务提交后,通过最新的readView可以读取到已提交的数据,从而实现读已提交。 - MVCC如何实现 reaptable read
一个事务在执行第一条select时,会创建一个readView, 且执行之后的select时不会再创建新的readView且不会更新readView。换句话说,一个事务只在遇到第一个select才创建一个readView,且保持不变,之后的select也是使用该readView。 这样就实现了可重复读。