MVCC

查看隔离级别

1
2
3
4
5
6
7
select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+-----------------+
| REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+

默认为RR(可重复读)

修改隔离级别

1
2
3
4
5
6
7
8
MariaDB sia@localhost:dev> set global transaction isolation level read committed;
Query OK, 0 rows affected
Time: 0.001s

MariaDB sia@localhost:dev> set session transaction isolation level read committed;
Query OK, 0 rows affected
Time: 0.001s

现在都应该是RC

READ-UNCOMMITTED

读未提交

事务一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MariaDB sia@localhost:dev> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+------------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+------------------+
| READ-UNCOMMITTED | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set
Time: 0.005s
MariaDB sia@localhost:dev> select * from `user`
MariaDB sia@localhost:dev> begin
Query OK, 0 rows affected
Time: 0.001s
MariaDB sia@localhost:dev> select * from `user`;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 1 | xiaoming | 0 |
+----+----------+---------+
1 row in set
Time: 0.005s
MariaDB sia@localhost:dev> update `user` set balance=100 where id=1;
Query OK, 1 row affected
Time: 0.001s
MariaDB sia@localhost:dev>

事务二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MariaDB sia@localhost:dev> begin
Query OK, 0 rows affected
Time: 0.001s
MariaDB sia@localhost:dev> select * from `user`;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 1 | xiaoming | 0 |
+----+----------+---------+
1 row in set
Time: 0.005s
MariaDB sia@localhost:dev> select * from `user`;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 1 | xiaoming | 100 |
+----+----------+---------+
1 row in set
Time: 0.004s
MariaDB sia@localhost:dev>

很显然 事务二读取到了事务一未提交的数据

READ-COMMITTED

左边为事务一右边为事务二 事务二读取到了事务一提交的内容

可能导致脏读

image-20240117025315528

REPEATABLE-READ

image-20240117152758761

SERIALIZABLE

串行化

每个select 都会加上s锁 每个update insert delete 都会加上x 锁

image-20240117154709205

其实际执行流程类似这样

image-20240117163616490

事务问题:

脏读

如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。

不可重复读

在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。

幻读

在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

不可重复读与幻读的最本质的区别是

不可重复读是对于一条内容多次读取到的内容不一样,幻读是多次读取到的数据的条数不一样

MVCC 实现原理

当前读:会加上锁 next-key lock 行锁和gap锁的集合

1
2
3
4
5
select * lock in share mode; ---s锁
select * for update ---x锁
update
delete
insert

快照读:

1
2
select 
---基于mvcc 控制

readview 决定返回那一个版本链的内容 对于读已提交的来说,是每个语句都生成一个readview 对于可重复读来说每个事务只生成一个readview

事务开启

1
2
begin/start transaction --- 只有在执行了第一条命令后才算是开启了事务
start transaction with consistent snapshot ---马上开启了事务

Read View 结构

img

  • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务
  • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id

对于每条记录来说,每个里面都会有两个固定的隐藏字段,要是没有主建的话,也会生成一个虚拟的主键:

隐藏字段:

1
2
trx_id , roll_pointer
---事务id 回滚指针

*undo log*维护了多个版本链

每一个记录里记录着修改该记录的事务的id,和一个回滚指针,可以根据回滚指针,回滚记录,或者借助readview 执行并发控制。