Mysql并发读写解决方案分析--MVCC

数据库并发场景

所有系统的并发场景都是三种,对于数据库而言为:

读-读:不存在任何问题,也不需要并发控制。

读-写:有线程安全问题,可能会造成事务隔离性问题,也就是脏读,不可重复读,幻读。

写-写:有线程安全问题,会存在更新丢失问题:第一类更新丢失(回滚丢失),第二类更新丢失(覆盖丢失)。

事务隔离级别

隔离级别:串行化SERIALIZABLE、可重复读(REPEATABLE READ)、读提交(READ COMMITTED)、读未提交(READ UNCOMMITTED)

可重复读(REPEATABLE READ):当前正在执行事务产生的数据变化不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。

读提交(READ COMMITTED):处于 READ COMMITTED 级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。

读未提交(READ UNCOMMITTED):处于这个隔离级的事务可以读到其他事务还没有提交的数据。如果这个事务使用其他事务不提交的变化作为计算的基础,然后那些未提交的变化被它们的父事务撤销,就会导致大量的数据变化。

查看隔离级别:

386f5fc4662d112fdf42a853635a8e7f.png

读未提交(READ UNCOMMITTED)

用一个例子来演示读未提交的隔离级别。设置当前会话的隔离级别,并准备一张staffs表:

set session transaction isolation level READ UNCOMMITTED;

a713a8b38a3c7c5b4aad3749e636af1c.png

1、启动事务A:

BEGIN;
select * from staffs where id =1;

9a5c67e9cdd18262360692ce0edc9808.png

2、启动事务B:

BEGIN;
select * from staffs where id =1;
update staffs set age=11 where id=1;

事务B执行完update动作后不commit提交,在事务A中继续查询id=1的值,如下所示:

1fbf53a0f9ef54b9026f0d4865ac2b5f.png

两次读到的数据不一致,事务A已经读到了别的事务未提交的修改,这个就是脏读。读未提交还会产生不可重复读。

读提交(READ COMMITTED)

在读提交的隔离级别下,当事务B修改完数据尚未提交时,事务A是无法读取到修改的数据的。当事务B提交事务后,事务A才可以看到修改的结果。

读可重复(REPEATABLE READ)

当事务B提交后,事务A查询的结果依然是事务A开启时读到的数据,这就是所谓的可重复读,也就是说事务开启时读到的数据,在事务提交前,是一致的,不会因为外面事务的修改提交而改变开启事务前读到的值。

可重复读会有幻读现象,比如事务A读取某唯一索引不存在,但是插入时显示异常失败,原因是其他事务插入的数据对事务A不可见。可以使用select ..for update;间隙锁解决幻读问题。

串行化

串行化不允许多个事务并行操作同一行数据,开启事务A未提交前事务B会阻塞。

视图

不同的隔离级别主要是为了提高数据库的并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

不同隔离级别在select读时表现不同,主要是生成Read View(读视图)的策略不同。

Read View是事务进行快照读操作的时候生产的读视图,在当前事务执行快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。事务ID默认单调递增。查询不会新增事务ID,只有DML才会导致事务ID增加。

当前读

select lock in share mode(共享锁),select for update;update,insert,delete(排他锁);这些操作都是一种当前读,当前读读取的记录都是目前数据库中最新的版本。读取时还要保证其它并发事务不能修改当前记录,所以会对读取数据加锁。

快照读

像不加锁的select操作就是快照读,即不加锁的非阻塞读。串行级别下的快照读会加锁,退化成当前读。

数据库隐式字段

数据库每行记录除了业务字段外,还有数据库隐式定义的几个字段:

DB_ROW_ID:隐藏自增ID,如果表没有定义主键,会使用该ID。
DB_TRX_ID:最近修改的事务ID,记录创建这条记录以及最后一次修改该记录的事务的ID。
DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本(undo log日志)。
DELETED_BIT:delete操作跟truncate/drop不一样,仅使用该flag标记逻辑删除。

Undo log记录形式

对于insert操作,undo日志仅记录新数据的主键,回滚时直接删除;

对于delete/update操作,undo日志需要记录旧数据整行内容,回滚时直接恢复;

MVCC多版本并发控制就是基于undo log实现,undo log简化理解为多个事务对某行数据写操作的记录链,类似写时复制。示例:

1、初始事务往 persion表中插入了一条新记录,记录如下,name = a1,age = 11;隐式主键 = 1,事务ID和回滚指针此时都为 NULL;

227c595fd1ea84a0064dbc48fbeea169.png

2、事务1对该行的name作出修改,改为a2;

2.1 在 事务1 修改该行记录数据的同时,数据库会先对该行加排他锁(InnoDB引擎会自动对DML语言影响的记录上写锁|独占锁)。

2.2 上锁完毕后,将该行数据拷贝到 undo log 中,作为旧记录,即在 undo log 中有当前行的拷贝副本。

2.3 拷贝完毕后,修改该行的 name 为 a2,并且修改隐藏字段的事务ID为当前 事务1的ID,这里事务ID默认是从1开始递增,回滚指针指向拷贝到 undo log 的副本记录,即表示我的上一个版本就是他。

2.4 事务提交后,释放锁。

6ffc53d6cc3417f83cdca57455c69e0a.png

3、又来一个事务2修改persion表的同一行记录,将age项修改为 22岁;在事务2修改该行数据之前,数据库继续给他上排他锁。

上锁完毕之后,把该行数据拷贝到 undo log 中,作为旧记录,发现操作的这行记录已经有undo log 的记录了,那么最新的旧数据作为链表的表头,插在这行记录的 undo log 日志的最前面。

修改该行age为22岁,并且修改隐藏字段的事务ID为当前事务2的ID,那就是2,回滚指针指向刚刚拷贝到 undo log 的副本记录。

事务提交,释放锁。

f4a7b3863a0c198aa2766de4f363e9ea.png

undo log 的链首就是最新的旧记录,尾部是最旧的记录。数据库的purg线程会定期清理掉已提交的事务记录,使undo log重复使用。

Read View读视图

Read View 有三个全局参数:

trx_list:未提交事务 ID 列表,用来维护 Read View 生成时刻系统正处于活跃状态的事务ID。

up_limit_id:记录 trx_list 事务ID列表中 最小的ID,也就是最初修改该记录的事务。

low_limit_id:Read View生成时刻数据库尚未分配的下一个事务ID,也就是当前最大事务ID + 1。

以RR级别和RC级别来分析,开启事务的时候开启一个 Read View,假定当前事务ID = 10,trx_list为(4,8, 10),因为当前事务10正在执行,所以自己也活跃,所以此时 up_limit_id=4,low_limit_id=11。

如果读到一个数据的 事务ID =3,小于 活跃列表的最小值(up_limit_id=4),可见;如果读到一个数据的 事务ID = 12,大于low_limit_id,不可见,需要根据回滚指针从undo log中找到小于up_limit_id的版本,或者不在活跃事务列表trx_list中的版本。

RR级别和RC级别的区别是:

RR级别开启事务的时候开启一个Read View,并且整个事务过程中一直使用这个视图;RC级别在每次读取时都使用最新的视图,因此会读到已提交事务的修改信息。

-写冲突

目前数据库基本不会出现第一类更新丢失,快照读是第二类更新丢失的一个主要原因,需要将一致性非锁定读替换为一致性锁定读,使用悲观锁/乐观锁解决。

总结:

MVCC可以解决多版本读写冲突加锁,而写写冲突还是需要锁来保证。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值