MySQL 多版本并发控制 —— MVCC

前言

  • MVCC指的是快照读。
  • MySQL中仅在RC读已提交级别、RR可重复读级别才会使用MVCC机制。
  • RC级别中,MVCC机制是会在每次select语句执行前,都会生成一个ReadView
  • RR级别中,一个事务只会在首次执行select语句时生成快照,后续所有的select操作都会基于这个ReadView来判断,这样也就解决了RC级别中存在的不可重复问题。

隔离级别

  • 读未提交(READ UNCOMMITTED):这是事务的最低隔离级别,事务中的读取操作可以看到其他未提交事务的变动。这种隔离级别可能导致脏读不可重复读幻读问题。

  • 读提交(READ COMMITTED):这是大多数数据库系统的默认隔离级别。事务中的读取操作只能看到其他事务已经提交的变动。这种隔离级别解决了脏读问题,但可能会出现不可重复读幻读问题。

  • 可重复读(REPEATABLE READ):这是MySQL的默认隔离级别。事务中多次读取同一行数据,而不会看到其他事务对这一行数据的修改。这种隔离级别解决了脏读不可重复读问题,但仍然可能出现幻读问题。

  • 可串行化(SERIALIZABLE):这是事务的最高隔离级别。在事务执行期间,使用该隔离级别可以保证事务串行执行,避免了脏读不可重复读幻读问题。但是这种隔离级别效率低下,因为事务通常需要等待前一个事务完成,才能继续执行。

读方式

  • 当前读是指在事务执行过程中,直接读取数据,而不是等待事务提交。这种读取方式会阻塞其他事务的写入操作,因此在并发性能方面可能较差。

    • 排他锁(也称为写锁):使用 SELECT ... FOR UPDATE 语句来对选定的数据行添加排他锁。例如:SELECT * FROM table_name WHERE condition_column = 'condition_value' FOR UPDATE; 将返回满足条件的数据行,并对这些行添加排他锁。其他事务需要对这些行进行修改或删除时,必须等待排他锁被释放。

    • 共享锁(也称为读锁):使用 SELECT ... LOCK IN SHARE MODE 语句来对选定的数据行添加共享锁。例如:SELECT * FROM table_name WHERE condition_column = 'condition_value' LOCK IN SHARE MODE; 将返回满足条件的数据行,并对这些行添加共享锁。其他事务可以继续读取这些行,但是不能修改或删除。其他事务必须等待共享锁被释放。

  • 快照读是指在某个时间点上,一个事务读取另一个已经提交的事务的数据。在大多数情况下,读取操作不会阻塞其他事务的写入操作,这样可以提高数据库的并发性能。

    • SELECT * FROM table_name WHERE condition_column = 'condition_value';

隐藏字段

  • DB_ROW_ID:这是一个隐藏的列,它存储了行的唯一标识符。每一行都有一个唯一的ID,这个ID是在创建行时自动生成的。DB_ROW_ID列通常在InnoDB表的物理结构中存在,但在SELECT、INSERT、UPDATE等SQL语句中是隐藏的,不能直接访问或修改。
  • DB_Deleted_Bit:这是一个内部标记位,用于表示该行是否被标记为删除。当一行被删除时,该标记位将被设置为1,表示该行已被删除。这个字段仅在内部使用,对用户来说是隐藏的。
  • DB_TRX_ID:这是一个隐藏的列,它存储了当前事务的ID。当一行被修改或删除时,该事务的ID将被存储在该列中。通过这个字段,InnoDB引擎可以追踪事务的修改操作,并在需要时进行回滚操作。
  • DB_ROLL_PTR:这是一个隐藏的指针,它指向回滚日志中的位置,用于在需要回滚时恢复数据。当事务需要回滚时,InnoDB引擎将使用DB_ROLL_PTR来找到对应的回滚日志,并根据日志中的信息将数据恢复到事务开始之前的状态。

undo-log

sql 复制代码
SELECT * FROM `users` WHERE user_id = 1;
+---------+-----------+----------+
| user_id | user_name | user_age |
+---------+-----------+----------+
|       1 | xyc       | 18       |
+---------+-----------+----------+

UPDATE `users` SET user_name = "neil" WHERE user_id = 1;
UPDATE `users` SET user_age = 20 WHERE user_id = 1;

比如上述这段SQL隶属于trx_id=1的事务,undo-log日志中存储的数据为:

不同的旧版本数据,会以roll_ptr回滚指针作为链接点,然后将所有的旧版本数据组成一个单向链表。最新的旧版本数据,都会插入到链表头中,而不是追加到链表尾部。

ReadView

ReadView就是一个事务在尝试读取一条数据时,MVCC基于当前MySQL的运行状态生成的快照,也被称之为读视图。当一个事务启动后,首次执行select操作时,MVCC就会生成一个数据库当前的ReadView,通常而言,一个事务与一个ReadView属于一对一的关系,ReadView一般包含四个核心内容:

  • creator_trx_id:代表创建当前这个ReadView的事务ID

  • trx_ids:表示在生成当前ReadView时,系统内活跃的事务ID列表,活跃事务是指还在执行的事务,即未结束(提交/回滚)的事务。

  • up_limit_id:活跃的事务列表中,最小的事务ID

  • low_limit_id:表示在生成当前ReadView时,系统中要给下一个事务分配的ID值,MySQL的事务ID是按序递增的。

例如:

json 复制代码
{
    "creator_trx_id" : "0",
    "trx_ids" : "[1,2,4]",
    "up_limit_id" : "1",
    "low_limit_id" : "5"
}

MVCC机制实现原理

  • 当一个事务尝试改动某条数据时,会将原本表中的旧数据放入undo-log中。
  • 当一个事务尝试查询某条数据时,MVCC会生成一个ReadView快照。
  • 其中undo-log主要实现数据的多版本,ReadView则主要实现多版本的并发控制。
sql 复制代码
-- 事务T1:trx_id=1
UPDATE `users` SET user_name = "neil" WHERE user_id = 1;
UPDATE `users` SET user_sex = 20 WHERE user_id = 1;
sql 复制代码
-- 事务T2:trx_id=2
SELECT * FROM `users` WHERE user_id = 1;

目前存在T1、T2两个并发事务,T1目前在修改user_id=1的这条数据,而T2则准备查询这条数据,那么T2在执行时具体过程如下:

  1. 当事务中出现select语句时,会先根据MySQL的当前情况生成一个ReadView

  2. 判断行数据中的隐藏列trx_idReadView.creator_trx_id是否相同:

    • 相同:代表创建ReadView和修改行数据的事务是同一个,自然可以读取最新版数据。
    • 不相同:代表目前要查询的数据,是被其他事务修改过的,继续往下执行。
  3. 判断隐藏列trx_id是否小于ReadView.up_limit_id最小活跃事务ID:

    • 小于:代表改动行数据的事务在创建快照前就已结束,可以读取最新版本的数据。
    • 不小于:则代表改动行数据的事务还在执行,因此需要继续往下判断。
  4. 判断隐藏列trx_id是否小于ReadView.low_limit_id这个值:

    • 大于或等于:代表改动行数据的事务是生成快照后才开启的,因此不能访问最新版数据。
    • 小于:表示改动行数据的事务IDup_limit_id、low_limit_id之间,需要进一步判断。
  5. 判断隐藏列trx_id是否在trx_ids中:

    • 在:表示改动行数据的事务目前依旧在执行,不能访问最新版数据。
    • 不在:表示改动行数据的事务已经结束,可以访问最新版的数据。

就是首先会去获取表中行数据的隐藏列,然后经过上述一系列判断后,可以得知:目前查询数据的事务到底能不能访问最新版的数据 。如果能,就直接拿到表中的数据并返回,反之,不能则通过roll_ptrundo-log日志中获取链表头的旧版本数据,然后依次遍历整个链表,从而检索到最合适的一条数据并返回。

相关推荐
kyriewen7 分钟前
用户打开飞行模式都能打开你的网站?Service Worker 做离线缓存,PWA 实战
前端·javascript·面试
常常有15 分钟前
AI智能知识库问答系统(基于 FastAPI和Dify)
python·mysql·fastapi
huaiixinsi37 分钟前
Java 后端面试高频题整理(02)
java·开发语言·spring·面试·职场和发展·架构·maven
Waay38 分钟前
MySQL基础高频考点
运维·mysql·adb
Nontee1 小时前
Java 后端面试题目全集
java·开发语言·面试
omenkk72 小时前
【MySQL专题】1.一条更新SQL语句是如何执行的
数据库·sql·mysql
Lehjy2 小时前
【MySQL】库的操作
数据库·mysql·oracle
Languorous.2 小时前
MySQL CRUD实操详解:插入、查询、修改、删除,附可直接运行示例
数据库·mysql
重生之小比特2 小时前
【MySQL 数据库】用户管理与权限控制
android·数据库·mysql
张元清2 小时前
React 里不用 setTimeout 的计时器写法:useTimeout、useInterval、useCountDown 和 useRafFn
前端·javascript·面试