快照读和当前读

快照读

快照读是指读取数据时不是读取最新版本的数据,而是基于历史版本读取的一个快照信息(mysql读取undo log历史版本) ,

快照读可以使普通的SELECT 读取数据时不用对表数据进行加锁

当前读

当前读是读取的数据库最新的数据,当前读和快照读不同,因为要读取最新的数据而且要保证事务的隔离性,所以当前读是需要对数据进行加锁的

(update、delete、insert、select ....lock in share mode、select for update)

 

MVCC如何解决幻读?

快照读下,通过Read view的方式解决了幻读,本文主要介绍快照读下的的幻读解决方式

当前读下,需要使用间隙锁来解决幻读,但本质上当前读就是为了查最新数据

如 select * from xxx where id > 1 for update

 

Read view是什么?

InnoDB的快照读并非创建数据的完整备份,而是创建了一份事务id相关的数据快照,结合undo_log来实现整体数据的快照读

read view主要储存了下列4项数据的快照

trx_ids: 当前系统活跃(未提交)事务版本号集合。

low_limit_id: 创建当前read view 时当前系统最大事务版本号+1。

为什么不是当前事务版本号+1:事务开始时刻为当前事务版本号+1,但读已提交级别的后续查询会创建新的readview,所以实际储存的是最大事务版本号+1

up_limit_id: 创建当前read view 时“系统正处于活跃事务最小版本号”

creator_trx_id: 创建当前read view的事务版本号;

 

InnoDB如何实现可重复读和读已提交?

1)数据库已存记录

trx_id

数据

undolog

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

2)trx_id=2 修改数据id=1的数据 name=cjd11

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

 

读已提交场景

3)trx_id=3 开启查询,创建readview

trx_ids

low_limit_id

up_limit_id

creator_trx_id

2,3

2

4

3

 

此时数据库数据为

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

 

trx_id = 2

>= low_limit_id(处于活跃的最小版本号) 且

< up_limit_id(当前系统最大事务版本号+1)且

!=creator_trx_id(当前事务版本号) 但

in trx_ids(正在活跃的事务版本号内)

 

故此数据不可见,根据undolog指针找到上一条记录trx_id=0 < low_limit_id,故数据可见

故该查询最终得到的数据为

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

 

4)trx_id=2 新增记录id =3; trx_id=4新增记录id=4

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

2

id = 3; name = cjd3

 

 

 

 

 

4

id = 4; name = cjd4

 

 

 

 

 

 

5)trx_id=3 开启查询,创建readview

trx_ids

low_limit_id

up_limit_id

creator_trx_id

2,3,4

2

5

3

新增的id=3与id=4,因为事务版本号处于活跃,故都对该次查询不可见

故该查询最终得到的数据为

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

2

id = 3; name = cjd3

 

 

 

 

 

4

id = 4; name = cjd4

 

 

 

 

 

 

6)trx_id=4 修改id=4数据为name=cjd41,并提交事务,trx_id=3创建数据name=cjd5

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

2

id = 3; name = cjd3

 

 

 

 

 

4

id = 4; name = cjd41

0xxxxxxx

4

id = 4; name = cjd4

 

3

id = 5; name = cjd5

 

 

 

 

 

 

7)trx_id=3 开启查询,创建readview

trx_ids

low_limit_id

up_limit_id

creator_trx_id

2,3

2

5

3

 

事务4提交后,数据name=cjd41 对事务3可见,name=cjd5事务号为创建者自身也可见,故最终查询得到的数据为

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

2

id = 3; name = cjd3

 

 

 

 

 

4

id = 4; name = cjd41

0xxxxxxx

4

id = 4; name = cjd4

 

3

id = 5; name = cjd5

 

 

 

 

 

 

可重复读场景

3)可重复读场景下,无论第几次查询均使用事务开启时创建的read view,但可以读到后续自身事务创建的数据

trx_ids

low_limit_id

up_limit_id

creator_trx_id

2,3

2

4

3

故可重复读得到的查询结果为

trx_id

数据

undolog

 

trx_id

数据

undolog

2

id = 1; name = cjd11

0xxxxxxx

0

id = 1; name = cjd1

 

0

id = 2; name = cjd2

 

 

 

 

 

2

id = 3; name = cjd3

 

 

 

 

 

4

id = 4; name = cjd41

0xxxxxxx

4

id = 4; name = cjd4

 

3

id = 5; name = cjd5

 

 

 

 

 

 

读已提交每次查询创建新的read view,可重复读使用的第一次创建的read view

这也是为什么可重复读能解决幻读,而读已提交不能解决幻读的原因