1. 定义
MVCC是多版本并发控制(Multi - Version Concurrency Control)的缩写。它是InnoDB存储引擎实现高并发控制的一种机制。在数据库系统中,多个事务可能会同时对数据进行读写操作,而MVCC通过为数据行保存多个版本来解决并发事务之间的冲突问题,使得数据库在保证数据一致性的同时,能够支持更多的并发操作。MVCC的核心思想是:为每一行数据维护多个版本,使得读写操作可以并发执行而不互相阻塞。
2. 事务和事务ID
在InnoDB中,事务是MVCC的核心。每个事务在开始时都会被分配一个唯一的事务ID(Transaction ID,简称TXID)。事务ID是单调递增的,用于标识事务的先后顺序。
-
事务ID的作用:
-
事务ID用于标记数据版本的创建者和可见性。
-
事务ID用于判断事务之间的先后顺序,从而实现并发控制。
-
3. 数据版本的存储结构
InnoDB使用行版本控制来实现MVCC。每行数据在物理存储上可能有多个版本,这些版本通过隐藏的列和指针进行管理。
-
隐藏列:
-
DB_TRX_ID(事务ID):记录最后一次对该行进行修改的事务的ID。这个ID是递增的,每次事务开始时都会分配一个唯一的事务ID。当一个事务对数据行进行修改时,InnoDB会将这个事务的ID记录在DB_TRX_ID列中。
-
DB_ROLL_PTR(回滚指针):回滚指针指向该行数据的旧版本。旧版本数据存储在InnoDB的回滚段(Undo Log)中。Undo Log是InnoDB用来存储数据行旧版本的地方,它记录了数据行在各个事务修改前的状态。
-
DB_ROW_ID:行ID,用于唯一标识一行数据。
-
-
版本链:
-
每行数据的多个版本通过DB_ROLL_PTR链接在一起,形成一个版本链。
-
最新的版本存储在表空间中,旧版本存储在回滚段(Undo Log)中。
-
-
ReadView:
决定当前事务能看到哪些版本的数据,包含:-
m_ids
:当前活跃(未提交)事务ID列表。 -
min_trx_id
:m_ids中的最小值。 -
max_trx_id
:下一个将分配的事务ID。 -
creator_trx_id
:创建该ReadView的事务ID。
-
4. MVCC的可见性规则
InnoDB通过事务ID和版本链来判断数据版本的可见性。具体规则如下:
4.1 读操作的可见性规则
-
一致性读(Consistent Read):
-
默认情况下,InnoDB使用一致性读,即读取数据时会看到事务开始时的数据库快照。
-
事务只能看到在它开始之前已经提交的版本,或者它自己插入的版本。
-
事务不能看到在它开始之后其他事务插入或更新的版本。
-
-
活跃事务是指在当前事务开始时仍然在运行的事务。换句话说,这些事务可能尚未提交或回滚。
示例:假设当前数据库中有以下事务:-
事务ID为
102
的事务也正在运行。 -
事务ID为
101
的事务正在运行(尚未提交或回滚)。 -
事务ID为
100
的事务已经提交。 -
现在,一个新的事务(事务ID为
103
)开始执行。对于事务103
来说: -
活跃事务:事务ID为
101
和102
的事务,因为它们在事务103
开始时仍在运行。 -
非活跃事务:事务ID为
100
的事务,因为它在事务103
开始之前已经提交。
-
-
快照读的判断逻辑:
-
数据版本:记录了每条记录在不同时间点的状态。
-
活跃事务:记录了在当前事务开始时仍在运行的事务。
-
如果一个数据版本的
DB_TRX_ID
小于当前事务的最小活跃事务ID:-
这个版本是由一个已经提交的事务生成的,对当前事务可见。
-
-
如果一个数据版本的
DB_TRX_ID
大于当前事务的最大活跃事务ID:-
这个版本是由一个在当前事务开始之后才开始的事务生成的,对当前事务不可见。
-
-
如果一个数据版本的
DB_TRX_ID
在当前事务的活跃事务列表中:-
这个版本是由一个仍在运行的事务生成的,对当前事务不可见。需要回溯到旧版本,继续判断,直到找到一个符合上述条件的版本。
-
-
4.2 写操作的可见性规则
-
当前读(Current Read):
-
写操作(如INSERT、UPDATE、DELETE)会获取行锁,并直接操作最新的数据版本。
-
写操作会生成新的数据版本,并更新
DB_TRX_ID
为当前事务ID。 -
如果是更新操作,旧版本会被移动到回滚段中,并通过
DB_ROLL_PTR
链接。
-
5. 事务的隔离级别与MVCC
InnoDB支持多种事务隔离级别,不同隔离级别对MVCC的实现方式和可见性规则有所不同。
4.1 读已提交(Read Committed)
-
特点:
-
事务只能看到在它开始之前已经提交的版本。
-
每次读取数据时,InnoDB都会重新构建一个快照。
-
不允许读取未提交的版本,避免了“脏读”问题。
-
-
MVCC实现:
-
对于每个SELECT操作,InnoDB会根据当前事务的读视图,找到最新的已提交版本。
-
如果数据被其他事务锁定,当前事务会等待锁释放。
-
5.2 可重复读(Repeatable Read)
-
特点:
-
事务在执行期间看到的数据库快照是一致的,即使其他事务在并发修改数据。
-
避免了“不可重复读”问题。
-
-
MVCC实现:
-
事务开始时,InnoDB会创建一个快照视图,后续的读操作都基于这个快照。
-
事务只能看到在它开始之前已经提交的版本,或者它自己插入的版本。
-
如果其他事务在并发修改数据,当前事务不会看到这些修改,除非它自己也进行了修改。
-
5.3 串行化(Serializable)
-
特点:
-
最严格的隔离级别,事务之间完全隔离。
-
避免了所有并发问题,但性能开销最大。
-
-
MVCC实现:
-
除了使用MVCC的版本控制外,还会通过加锁机制(如表锁或行锁)来确保事务之间的串行执行。
-
读操作也会加锁,防止其他事务修改数据。
-
6. 回滚段(Undo Log)的作用
回滚段是InnoDB实现MVCC的关键组件之一。它用于存储数据的旧版本,以便支持一致性读和事务回滚。
-
存储旧版本:
-
当事务更新数据时,旧版本会被移动到回滚段中,并通过
DB_ROLL_PTR
链接。 -
回滚段中的旧版本数据用于支持一致性读,事务可以根据需要回溯到旧版本。
-
-
事务回滚:
-
如果事务需要回滚,InnoDB会通过回滚段中的旧版本数据恢复到事务开始之前的状态。
-
-
垃圾回收:
-
当没有事务再需要回滚段中的旧版本数据时,InnoDB会进行垃圾回收,释放回滚段的空间。
-
7. 总结
InnoDB的MVCC机制是一种高效的并发控制机制,它通过为数据行保存多个版本,实现了读操作和写操作之间的高并发性,减少了锁竞争,并保证了事务的一致性读。然而,MVCC机制也存在一些局限性,如版本链过长、垃圾回收问题和快照隔离级别下的幻读问题。尽管如此,InnoDB的MVCC机制仍然是现代数据库系统中一种重要的并发控制机制,广泛应用于各种高并发的场景中。