MVCC的理解

脏读、幻读、不可重复读

脏读:读取未提交(未提交的数据被回滚)的数据

幻读:一个事务相同查询条件,前后查询的数据量不一致问题(指新插入的行)。

不可重复读:同一事务前后多次读取,数据内容不一致。事务为可重复读可解决。

为什么要有MVCC?

解决了在可重复读、读提交的事务隔离级别下读写的事务并发。

在没有MVCC存在的情况下如何保证不会出现脏读?

隔离级别

隔离级别 脏读 幻读 不可重复读 概念
READ UNCOMMITTED 事务能够看到其他事物没有提交的修改
READ COMMITTED × 事务能看到其他事物提交后的修改,前后两次读取可能会出现数据不一致
REPEATABLE READ(默认隔离级别) × √ (INNODB不可能) × 事务在多次读取的数据一致
SERIALIZABLE × × × 事务串行

实现原理

使用三个隐藏字段、undo log 和 read view一起实现

三个字段

ROW_ID 6字节,数据表没有主键,将自动生成一个row_id;

TRX_ID 6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id

ROLL_PTR 7字节,回滚指针,指向这条记录上一个版本,用于配合undolog,指向上一个旧版本


当前记录

name age ROW_ID TRX_ID ROLL_PTR
Jone 18 1 3 0x10000001

undo log

回滚日志,用于记录数据被修改前的信息,用于保障未提交事务的原子性。主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

name age ROW_ID TRX_ID ROLL_PTR
Jone 17 1 2 0x10000000
name age ROW_ID TRX_ID ROLL_PTR
Jone 16 1 1 null

Read View

可见性判断,事务进行快照读操作的时候产生的读视图,在执行读的时候生成当前数据系统的快照,记录并维护系统当前活跃事务的id(事务id是递增的),把它当做条件去判断当前事务能够看到哪个版本的数据。有可能读取最新的数据,也有可能读取当前航记录的undo log中的某个版本数据。

属性:

trx_list: 数值列表,用来维护ReadView生成时刻系统正在活跃的事务ID;

up_limit_id: 记录trx_list列表中事务最小的ID;

low_limit_id: ReadView生成时刻,系统即将分配的下一个事务ID

规则:

  1. TRX_ID < up_limit_id ⇒ 当前事务能看到TRX_ID的记录

    TRX_ID >= up_limit_id ⇒ 看条件2

  2. TRX_ID ≥ low_limit_id⇒ 代表TRX_ID所在的记录在ReadView生成后才出现的,当前事务不可能见

    TRX_ID < low_limit_id ⇒ 看条件3

  3. 判断TRX_ID是否在活跃事务中,如果在代表ReadView生成时,这个事务是活跃状态,修改的数据当前事务看不到。如果不在代表这个事务在ReadView生成之前就已经提交,那么是可见的。

举个例子

数据:

name age ROW_ID TRX_ID ROLL_PTR
Jone 20 1 5 0x10000000

undo log

name age ROW_ID TRX_ID ROLL_PTR
Jone 19 1 1 null

可重复读的事务隔离级别下:

事务1 事务2 事务3 事务4 事务5
BEGIN; BEGIN; BEGIN; BEGIN; BEGIN;
快照读 UPDATE;
COMMIT;
... 快照读 快照读 ...

此时事务2的Read View

up_limit_id trx_list low_limit_id
1 1,2,3,4,5 6
up_limit_id trx_list low_limit_id
1 1,2,3,4,5 6

此时事务3的Read View

up_limit_id trx_list low_limit_id
1 1,2,3,4 6

读提交的事务隔离级别下:

事务1 事务2 事务3 事务4 事务5
BEGIN; BEGIN; BEGIN; BEGIN; BEGIN;
快照读 UPDATE;
COMMIT;
... 快照读 快照读 ...
-- 查看当前事务id
SELECT TRX_ID FROM information_schema.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
-- 设置当前对话事务隔离级别
set session TRANSACTION ISOLATION level REPEATABLE read;
set session TRANSACTION ISOLATION level read committed;

可重复隔离级别,事务执行情况

T1 T2
begin;
select; == null
begin;
insert;
commit;
select; ==null
commit;
T1 T2
begin;
begin;
insert;
commit;
select; == insert
commit;

说明可重复读隔离性下,第一次select获取到read view后,第二次不会再次获取read view

读已提交隔离级别,事务执行情况

T1 T2
begin;
select; == null
begin;
insert;
commit;
select; ==insert
commit;
T1 T2
begin;
begin;
insert;
commit;
select; ==insert
commit;

说明读已提交隔离性下,第一次select获取到read view后,第二次再次获取read view。可以获取到其他事务提交的数据。

关于spring应用下的一些问题

mybatis-plus通过配置关闭缓存

# 解决两次相同的select,后面一次select不会查询数据库问题;
# 原因在于mybatis有一级缓存,再一次sqlsession里,如果执行相同的select语句,mybatis不会重新插叙那数据库。
mybatis-plus:
    configuration:
        local-cache-scope: statement

Spring通过清除SqlSession缓存

@Resource
private SqlSessionFactory sqlSessionFactory;

SqlSessionUtils.getSqlSession(sqlSessionFactory).clearCache();

扩展阅读

ACID

原子性:undo log保证

一致性: 由其他三者一起保证

隔离性: MVCC + undo log

持久性:redo log 保证

posted @ 2022-03-31 11:20  WeaveOwn  阅读(74)  评论(0编辑  收藏  举报