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 操作时,会以当前读的方式读取数据,并加锁,防止其它事务在查询范围内插入数据,从而实现防止幻读

相关推荐
夜光小兔纸12 分钟前
Oracle 普通用户连接hang住处理方法
运维·数据库·oracle
兩尛2 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库
web2u2 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
Elastic 中国社区官方博客3 小时前
使用 Elasticsearch 导航检索增强生成图表
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
小金的学习笔记3 小时前
RedisTemplate和Redisson的使用和区别
数据库·redis·缓存
新知图书3 小时前
MySQL用户授权、收回权限与查看权限
数据库·mysql·安全
文城5213 小时前
Mysql存储过程(学习自用)
数据库·学习·mysql
沉默的煎蛋3 小时前
MyBatis 注解开发详解
java·数据库·mysql·算法·mybatis
呼啦啦啦啦啦啦啦啦3 小时前
【Redis】事务
数据库·redis·缓存
HaoHao_0103 小时前
AWS Serverless Application Repository
服务器·数据库·云计算·aws·云服务器