一,定义
MVCC,全称为 Multi-Version Concurrency Control,是一种数据库管理系统中用于实现并发控制的技术。MVCC允许多个事务同时访问数据库,并且在保持数据一致性的同时避免了常见的并发问题,如脏读、不可重复读和幻读。
MVCC的核心思想是为每个事务创建一个独立的版本(或快照)来访问数据库,而不是像传统的锁定机制一样直接锁定数据。每个事务在执行时只能看到在其启动时间点之前已经存在的数据版本。这种方式可以提高并发性,因为不同事务之间不会互相阻塞,只有在写入冲突的情况下才会导致事务等待。
MVCC的实现通常包括以下几个关键元素:
-
版本号:每个数据行都有一个关联的版本号或时间戳,用于标识数据的版本。
-
事务ID:每个事务都有一个唯一的事务ID,用于标识事务的启动时间点。
-
回滚指针:指向上一个版本
MVCC的优点包括高并发性、不阻塞读操作、避免了写锁冲突,以及可实现高度隔离性。因此,MVCC在许多现代数据库管理系统中得到广泛应用,如 PostgreSQL、MySQL(InnoDB存储引擎)、Oracle 等。它是一种有效的并发控制机制,使得多个事务可以同时进行,提高了数据库系统的性能和可伸缩性。
二,MVCC如何判断事务可见?
读已提交隔离级别下,每一次select操作都会生成一个readview(造成不可重复读)
可重复读隔离级别下,生成readview是以一个事务为单位的(事物级别的readview生成)
-
对于读操作:
- 当事务开始执行时,记录当前事务的启动时间戳或事务ID。
- 在执行读操作时,只能看到在当前事务启动时间戳之前已经提交的事务所产生的数据版本。
- 任何在当前事务启动时间戳之后提交的事务所产生的数据对当前事务都不可见。
- 如果某个数据版本的时间戳早于当前事务的启动时间戳,那么该数据版本对当前事务是可见的。
-
对于写操作:
- 当事务执行写操作时,会创建一个新的数据版本,并且该数据版本的时间戳等于当前事务的启动时间戳。
- 新数据版本只有在当前事务提交后才对其他事务可见。
-
对于并发事务:
- 不同事务之间可以并发执行,因为它们都有自己的事务启动时间戳。
- 不会发生读-写冲突,因为读操作只会看到在自己启动时间戳之前提交的数据版本,而写操作只会影响当前事务的可见性。
三,MVCC的trx_id是什么?
在MVCC(Multi-Version Concurrency Control)中,trx_id
(也称为事务ID)是用于标识和区分不同事务的唯一标识符。每个事务都会被分配一个独一无二的trx_id
,它在数据库中的作用如下:
-
唯一标识事务:
trx_id
用于唯一标识每个事务。每个事务都有一个不同的trx_id
,用于区分不同的事务。 -
标记事务开始:
trx_id
通常在事务开始时分配。当事务开始时,数据库会为该事务生成一个trx_id
,并将其与该事务关联起来。 -
用于版本控制:在MVCC中,每个数据版本都与生成该版本的事务的
trx_id
相关联。这使得数据库能够根据事务的trx_id
来判断哪些数据版本对于哪个事务是可见的。 -
事务提交和回滚:
trx_id
也用于标记事务的提交和回滚。当事务提交时,其trx_id
被标记为已提交,而当事务回滚时,其trx_id
被标记为已回滚。 -
并发控制:
trx_id
用于管理事务之间的并发。不同事务的trx_id
允许它们并发执行,同时保持数据的一致性和隔离性。
四,如何根据trx_id判断当前readview是否可见
-
获取当前事务的
trx_id
:首先,你需要获取当前事务的trx_id
。这通常是通过数据库连接或事务对象来获取的。 -
获取m_ids:表示在生成readview时当前系统活跃的事务id列表(未提交事务集合)
-
获取
readview
的min_trx_id
和max_trx_id
:readview
是用来表示当前事务可以看到哪些数据版本的一个关键组件。readview
通常包含了两个重要的trx_id
:min_trx_id
和max_trx_id
。min_trx_id
是当前系统活跃读写事务中最小的事务id,是m_ids里面的最小值(未提交事务中id最小值)
,而max_trx_id
是生成该readview时系统应该分配给下一个事务的trx_id
。你需要从readview
中获取这两个值。 -
判断
trx_id
是否在可见范围内:一旦获取了当前事务的trx_id
、readview
的min_trx_id
和max_trx_id
,你可以使用以下规则来判断当前的readview
是否可见:- 如果当前事务的
trx_id
<min_trx_id
,则该事务对于当前readview
是可见的,因为它早于min_trx_id
产生,代表该事务已经提交 - 如果当前事务的
trx_id>=
min_trx_id
且<=max_trx_id
,若该事务trx_id不在m_ids中可以访问此版本,反之则不行(事务未提交) - 如果当前事务的
trx_id
>max_trx_id
,则该事务对于当前readview
是不可见的,因为它的trx_id晚于max_trx_id
产生。 - 如果trx_id==creator_id,则可以访问此版本
- 如果当前事务的
原则: 不允许访问到未提交的事务,否则MVCC就没有作用了
五,为什么MVCC无法完全防止幻读
MVCC通过提供每个事务一个一致性的快照来防止不可重复读,确保事务可以看到一致的数据视图。然而,即使使用了MVCC,幻读问题仍然可能发生,原因如下:
-
MVCC与范围查询:当事务执行范围查询时,MVCC确保该事务看到快照中的数据状态。如果在此事务运行期间,另一个事务插入或删除了范围内的数据并提交,那么即使第一个事务的读取是基于快照的,它也可能在后续的查询中看到新的或缺少的数据(即幻影数据),因为这些更改发生在快照之外。
-
锁定策略:虽然MVCC能够减少读写冲突,但它本身不包括对插入操作的范围锁定。因此,即使事务在使用MVCC时对数据范围进行读取,其他事务仍然可以在这个范围内插入新行,这可能会导致幻读。
MVCC极大地提高了数据库的并发性能,减少了读写冲突,但它主要解决的是不可重复读的问题,而不是幻读。要解决幻读问题,通常需要结合其他机制,如Serializable隔离级别下的范围锁定,来防止事务间的幻读现象。在Serializable隔离级别下,事务将对选定的数据范围加锁,防止其他事务在该范围内进行插入、删除或修改操作,从而避免了幻读。