MySQL事物,MVCC机制

事物

事务(Transaction)是一组作为单个逻辑工作单元执行的SQL语句,它具有ACID特性:

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败,不能部分成功或部分失败
  • 一致性(Consistency):事务执行前后,数据库的状态保持一致
  • 隔离性(Isolation):多个事务并发执行时,每个事务的执行结果不受其他事务的影响
  • 持久性(Durability):事务一旦提交,对数据库的修改将永久保存

事务隔离级别

并发控制时会出现问题

  • 脏读:一个事务读取另一个事务未提交的数据
  • 不可重复读:同一事务内多次读取数据时,数据发生变化
  • 幻读:事务之间的数据插入或删除导致查询结果不一致

MySQL的隔离级别可以解决上述问题,支持四种隔离级别

  1. 读未提交(Read Uncommitted)

    • 允许脏读、不可重复读和幻读
    • 事务可以读取其他事务未提交的数据
  2. 读已提交(Read Committed)

    • 解决了脏读问题,但仍然可能发生不可重复读和幻读
    • 事务只能读取其他事务已提交的数据
  3. 可重复读(Repeatable Read)

    • 解决了脏读和不可重复读问题,但仍然可能发生幻读
    • 事务会对读取的数据加锁,保证该数据在事务内不会被修改
  4. 串行化(Serializable)

    • 解决了脏读、不可重复读和幻读问题
    • 对同一行记录,写操作会加锁,读操作会加共享锁,从而避免幻读
sql 复制代码
-- 查看当前事务隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
-- 设置事务隔离级别
-- READ-UNCOMMITTED(最低级别)
-- READ-COMMITTED
-- REPEATABLE-READ(MySQL 的默认隔离级别)
-- SERIALIZABLE(最高级别)
SET GLOBAL transaction_isolation = 'REPEATABLE-READ';
-- 开启事务
START TRANSACTION;
-- 提交事务
COMMIT;

MVCC

在MySQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,但不能解决幻读问题。如果想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。

主要解决了在高并发环境下的数据库读写冲突问题,它允许事务处理更加高效,同时提高了数据库的整体性能和可靠性

什么是快照读和当前读?
  1. 快照读读取的数据是快照数据,不加锁的简单的SELECT操作都属于快照读
  2. 当前读读取的数据是当前最新的数据,需要加锁的SELECT操作都属于当前读

快照读就是普通的读操作,而当前读包括了加锁的读取和DML操作

什么是 MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制),通过数据行的多个版本管理来实现数据库的并发控制,它的思想就是保存数据的历史版本

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC实现原理

MVCC 的实现主要依赖于:隐藏字段、undo log、Read View。

在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改

原文

隐藏字段

每条行记录除了自定义之外,InnoDB 还维护了以下隐藏字段

sql 复制代码
CREATE TABLE users (
   id INT,
   name VARCHAR(50)
   -- 以下是InnoDB自动维护的隐藏字段
   -- DB_TRX_ID     -- 事务ID:记录最后一次修改该行的事务ID
   -- DB_ROLL_PTR   -- 回滚指针:指向上一个版本的记录
   -- DB_ROW_ID     -- 如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引
);
undo log

undo log 是用于事物回滚;当读取记录时,该记录被其他事物占用,则需要通过undo log来找到之前的数据版本,实现非锁定读。undo log主要分为两类:

  • insert undo log:事务对insert操作的undo loginsert操作的记录只对事务本身可见,对其他事务不可见,事务提交后,该undo log可以直接删除
  • update undo log:事务对update操作的undo log,事务提交后,该undo log会被purge线程删除

不同事物或者相同事物对同一行记录的update或者delete操作,会使该记录行的undo log成为一条链表,链首就是最新的记录,链尾就是最早的旧记录

Read View

Read View 记录了当前事务能看到的版本,这些版本的数据基于事务开始时的快照。

Read View的结构,Read View主要是用来做可见行判断,主要字段有

  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_id 为 m_low_limit_id。小于这个 ID 的数据版本均可见
  • m_ids:Read View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID
RC 和 RR 隔离级别下 MVCC 的差异

在事务隔离级别 RC 和 RR (InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同

  • 在 RC 隔离级别下,每次select查询会生成并获取最新的Read View(m_ids 列表)
  • 在 RR 隔离级别下,同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View
MVCC 解决不可重复读问题

举个例子:

事物1 事物2 事物3
T1 START TRANSACTION;
T2 START TRANSACTION; START TRANSACTION;
T3 update users set name='Job' where id=1;
T4 select * from users where id=1; select * from users where id=1;
T5 update users set name='Tom' where id=1;
T6 select * from users where id=1; select * from users where id=1;
T7 commit; update users set name='Bill' where id=1;
T8 select * from users where id=1;
T9 update users set name='Andrew' where id=1;
T10 commit; select * from users where id=1;
T11 commit;

在RR隔离级别下

  • T2 时刻

  • T4 时刻

  • T6 时刻

  • T8 时刻
  • T10 时刻

在RC隔离级别下

  • T2 时刻
  • T4 时刻
  • T6 时刻
  • T8 时刻
  • T10 时刻

// todo 按照 read view 分析 ...

MVCC➕Next-key-Lock 防止幻读
  1. 执行普通 select,此时会以 MVCC 快照读的方式读取数据

    RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 "幻读"

  2. 执行 select...for update/lock in share mode、insert、update、delete 等当前读

    当前读读取的是最新的数据,并且会对读取到的数据加锁,防止其他事务修改或删除该数据。在执行 insert、update、delete 操作时,会以当前读的方式读取数据,并加锁,防止其它事务在查询范围内插入数据,从而实现防止幻读

相关推荐
JavaGuide3 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
怒放吧德德5 小时前
MySQL篇:MySQL主从集群同步延迟问题
后端·mysql·面试
数据智能老司机6 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
Eip不易也不e7 小时前
教程之同时安装两个版本的 mysql
mysql
数据智能老司机7 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
松果猿7 小时前
空间数据库学习(二)—— PostgreSQL数据库的备份转储和导入恢复
数据库
Kagol7 小时前
macOS 和 Windows 操作系统下如何安装和启动 MySQL / Redis 数据库
redis·后端·mysql
无名之逆7 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s9123601017 小时前
rust 同时处理多个异步任务
java·数据库·rust
数据智能老司机8 小时前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构