当前位置: 首页 > news >正文

MySQL笔记---事务

1. 什么是事务

在 MySQL 中,事务(Transaction)一组不可分割的 SQL 操作序列,这些操作要么全部成功执行,要么全部失败回滚,以此保证数据的一致性和完整性。事务是数据库并发控制的基础,广泛用于金融交易、订单处理等需要确保数据准确性的场景。

1.1 事务的 ACID 特性

  • 原子性(Atomicity):事务是一个 “原子” 操作单元,所有 SQL 语句要么全部执行成功要么全部失败(回滚),不存在部分执行的情况。 例:银行转账时,“A 账户扣款” 和 “B 账户到账” 必须同时成功,若任一环节失败,整个操作需回滚到初始状态。
  • 一致性(Consistency):事务执行前后,数据库的状态必须从一个 “一致状态” 切换到另一个 “一致状态”(即数据满足预设的约束,如主键唯一、外键关联等)。 例:转账前 A 和 B 的总余额为 1000 元,转账后总余额仍需为 1000 元,不能因事务执行导致总额变化。
  • 隔离性(Isolation):多个并发事务之间相互隔离,一个事务的操作不会被其他事务干扰,避免因并发导致的数据错误(如脏读、不可重复读等)。 MySQL 通过 “隔离级别” 控制隔离性的严格程度(见下文)。
  • 持久性(Durability):事务一旦提交(COMMIT),其对数据的修改会被永久保存到磁盘,即使数据库崩溃,重启后也能恢复该修改。

1.2 引擎对事务的支持

在MySQL中,只有InnoDB引擎支持事务:

mysql> SHOW engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.01 sec)

2. 事务的操作命令

命令作用
BEGIN 或 START TRANSACTION显式开启一个事务
COMMIT提交事务:将事务中所有修改永久保存到数据库
ROLLBACK回滚事务:放弃事务中所有未提交的修改,恢复到事务开始前的状态
SAVEPOINT 保存点名称在事务中创建一个 “保存点”,后续可回滚到该点(而非整个事务)
ROLLBACK TO 保存点名称回滚到指定保存点(保存点之后的操作被撤销,之前的仍有效)
RELEASE SAVEPOINT 保存点名称删除指定保存点

我们使用如下一张简单的表来作为示例:

CREATE TABLE user(id INT PRIMARY KEY,name VARCHAR(20),balance DECIMAL(6, 2)
);

2.1 直接提交

-- 终端1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)mysql> INSERT INTO user VALUES (4, '赵六', 4561.23);
Query OK, 1 row affected (0.00 sec)mysql> COMMIT;
Query OK, 0 rows affected (0.01 sec)-- 终端2
-- 提交前
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
+----+--------+---------+
3 rows in set (0.00 sec)-- 提交后
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
|  4 | 赵六   | 4561.23 |
+----+--------+---------+
4 rows in set (0.00 sec)

2.2 全部回滚

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)mysql> INSERT INTO user VALUES (5, '田七', 5612.34);
Query OK, 1 row affected (0.00 sec)mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

2.3 部分回滚

-- 终端1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)mysql> INSERT INTO user VALUES (4, '赵六', 4561.23);
Query OK, 1 row affected (0.00 sec)mysql> SAVEPOINT id4;
Query OK, 0 rows affected (0.00 sec)mysql> INSERT INTO user VALUES (5, '田七', 5612.34);
Query OK, 1 row affected (0.00 sec)mysql> ROLLBACK TO id4;
Query OK, 0 rows affected (0.00 sec)mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)-- 终端2
-- 提交前
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
+----+--------+---------+
3 rows in set (0.00 sec)-- 提交后
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
|  4 | 赵六   | 4561.23 |
+----+--------+---------+
4 rows in set (0.00 sec)

3. 事务的自动提交

MySQL 默认开启AUTOCOMMIT=1(自动提交),即单条 SQL 语句会被视为独立事务自动提交

mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

若需显式控制事务,需先执行SET AUTOCOMMIT=0关闭自动提交,或用BEGIN手动开启。

mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

如果我们将AUTOCOMMIT关闭,此时我们执行的单条SQL语句就不会被自动提交,而需要我们显式执行COMMIT

-- 终端2
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
|  4 | 赵六   | 4561.23 |
+----+--------+---------+
4 rows in set (0.00 sec)-- 终端1
mysql> INSERT INTO user VALUES (5, '田七', 5612.34);
Query OK, 1 row affected (0.00 sec)-- 终端2
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
|  4 | 赵六   | 4561.23 |
+----+--------+---------+
4 rows in set (0.00 sec)-- 终端1
mysql> COMMIT;
Query OK, 0 rows affected (0.01 sec)-- 终端2
mysql> SELECT * FROM user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   | 1234.56 |
|  2 | 李四   | 2345.61 |
|  3 | 王五   | 3456.12 |
|  4 | 赵六   | 4561.23 |
|  5 | 田七   | 5612.34 |
+----+--------+---------+
5 rows in set (0.00 sec)

4. 事务的隔离级别

4.1 并发事务可能导致的问题

  1. 脏读:事务 A 读取了事务 B 未提交的修改(若 B 后续回滚,A 读取的数据是 “无效” 的)。
  2. 不可重复读:事务 A 两次读取同一数据,期间事务 B 修改并提交了该数据,导致 A 两次结果不一致。
  3. 幻读:事务 A 两次执行同一查询,期间事务 B 插入 / 删除了符合条件的记录,导致 A 两次结果集不同。
    不可重复读与幻读的区别
    对比维度不可重复读(Non-repeatable Read)幻读(Phantom Read)
    定义同一事务内,两次读取同一行数据时,因其他事务对该行数据修改并提交,导致两次读取结果不一致。同一事务内,两次执行相同范围的查询(如 WHERE 条件)时,因其他事务插入 / 删除了符合条件的新记录,导致两次结果集的行数或内容不一致。
    数据变化类型针对已有数据的更新(UPDATE)(如修改某行的字段值)。针对新数据的插入(INSERT)已有数据的删除(DELETE)(影响符合查询条件的记录数量)。
    产生场景聚焦于单行数据的内容变化。聚焦于符合条件的记录范围变化(新增或减少了行)。

4.2 隔离级别

MySQL 定义了 4 种隔离级别(从上到下,隔离性越强,并发性能越差):

隔离级别解决脏读?解决不可重复读?解决幻读?说明
读未提交(READ UNCOMMITTED)允许读取其他事务未提交的修改,性能最高但安全性最差。
读已提交(READ COMMITTED)只能读取其他事务已提交的修改,避免脏读(Oracle 默认级别)。
可重复读(REPEATABLE READ)✅(InnoDB)事务中多次读取同一数据结果一致,InnoDB 通过 MVCC + 间隙锁解决幻读(MySQL 默认级别)。
串行化(SERIALIZABLE)强制事务串行执行(加表级锁),完全避免并发问题,但性能极低。

4.3 隔离级别的查看与设置

在MySQL中,隔离级别由两个变量进行控制:

  • 会话隔离级别:决定当前会话的隔离级别。
  • 全局隔离级别:用户登录后默认使用全局隔离级别作为会话的隔离级别;
4.3.1 查看隔离级别

(1)MySQL 8.0+

-- 查看当前会话隔离级别
SELECT @@transaction_isolation;
-- 或
SELECT @@session.transaction_isolation;-- 查看全局隔离级别
SELECT @@global.transaction_isolation;

(2)MySQL 5.7及以下

-- 查看当前会话隔离级别
SELECT @@tx_isolation;
-- 或
SELECT @@session.tx_isolation;-- 查看全局隔离级别
SELECT @@global.tx_isolation;
4.3.2 设置隔离级别
-- (1)设置会话隔离级别
SET TRANSACTION ISOLATION LEVEL 隔离级别名称(上表中的英文名称);
-- 例:设置为读已提交
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;-- (2)设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别名称;

5. MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制)是 InnoDB 存储引擎实现高并发读写的核心机制。它通过保存数据的多个历史版本,让读写操作互不阻塞(读不阻塞写,写不阻塞读),同时保证事务隔离性,是解决 “不可重复读” 等并发问题的关键技术。

MVCC 的核心目标是在并发场景下,解决两大矛盾:

  1. 读写冲突:避免 “读操作等待写操作释放锁” 或 “写操作等待读操作释放锁”,提升并发效率。
  2. 隔离性保证:在不加锁的情况下,让不同事务看到对应隔离级别允许的数据版本(如 “可重复读” 级别下,事务内多次读取数据一致)。

例如,在“可重复读”的隔离级别下,即使其他事务对数据进行了修改并提交,我们也希望在当前事务的视角下看到的数据是不变的(看到历史版本的数据)。

这就需要保存并管理历史版本的数据,而InnoDB使用的技术就是MVCC。

5.1 MVCC 的核心组成

MVCC 的实现依赖 3 个关键组件:隐藏列undo 日志Read View

(1)隐藏列(数据行的版本标识)

InnoDB 会为表中的每一行数据添加 3 个隐藏列(默认不显示,用于版本管理):

  • DB_TRX_ID:记录最后一次修改该行数据的事务 ID(6 字节)。 (事务 ID 是自增的,新事务的 ID 比旧事务大,可用于判断事务的先后顺序。)
  • DB_ROLL_PTR:回滚指针(7 字节),指向该行数据的上一个历史版本(存储在 undo 日志中)。
  • DB_ROW_ID行 ID(6 字节),若表没有主键或唯一索引,InnoDB 会用它生成聚簇索引。

(2)undo 日志(历史版本的存储)

当事务修改数据时,InnoDB 不会直接覆盖旧数据,而是将旧数据写入undo 日志(回滚日志),并通过DB_ROLL_PTR将当前行与 undo 日志中的历史版本串联起来,形成一条版本链。

例如:

  1. 事务 1(ID=10)插入一行数据,DB_TRX_ID=10,DB_ROLL_PTR=NULL(无历史版本)。
  2. 事务 2(ID=20)修改该行数据,InnoDB 会将修改前的旧数据写入 undo 日志,然后更新当前行的DB_TRX_ID=20,DB_ROLL_PTR指向 undo 日志中的旧版本(ID=10 的版本)。
  3. 事务 3(ID=30)再次修改,重复上述过程,版本链会不断延长(当前行 → ID=30 的修改 → ID=20 的修改 → ID=10 的初始版本)。

现在我们将历史版本都组织管理起来了,但问题是,对于某个具体的事务,哪些版本是他可见的呢?

(3)Read View(可见性判断规则)

Read View(读视图)是事务在读取数据时生成的一个 “快照”,它定义了当前事务能看到哪些版本的数据(即哪些历史版本对当前事务 “可见”)。

Read View 包含 4 个核心属性:

  1. m_ids:当前活跃(未提交)的事务 ID 列表。
  2. min_trx_id:m_ids中最小的事务 ID(当前活跃事务中最早开始的)。
  3. max_trx_id:下一个将要分配的事务 ID(大于所有已存在的事务 ID)。
  4. creator_trx_id:生成该 Read View 的事务自身的 ID。

5.2 MVCC 的工作原理(可见性判断逻辑)

当事务读取一行数据时,InnoDB 会通过 Read View 的规则,从该数据的版本链中筛选出 “可见” 的版本:

  1. 取版本链中某个版本的DB_TRX_ID(修改该版本的事务 ID),与 Read View 的属性对比。
  2. DB_TRX_ID == creator_trx_id: 说明该版本是当前事务自己修改的,可见
  3. DB_TRX_ID < min_trx_id: 说明修改该版本的事务在当前事务开始前已提交,可见
  4. DB_TRX_ID >= max_trx_id: 说明修改该版本的事务在当前事务生成 Read View 后才开始,不可见
  5. min_trx_id <= DB_TRX_ID < max_trx_id
    1. DB_TRX_ID在m_ids中(该事务仍活跃未提交):不可见
    2. DB_TRX_ID不在m_ids中(该事务已提交):可见

若当前版本不可见,则通过DB_ROLL_PTR回溯到上一个历史版本,重复上述判断,直到找到可见版本或版本链结束(返回空)。

注意:这里的判断逻辑不与某个隔离级别绑定,接下来我们会介绍MVCC与隔离级别的关系。

5.3 MVCC 与隔离级别的关系

MVCC 的行为会根据事务隔离级别调整,核心差异在于Read View 的生成时机

  • 读已提交(READ COMMITTED)每次执行查询时,都会重新生成一个 Read View
    • 因此,同一事务内两次查询可能看到其他事务已提交的修改(解决脏读,但允许不可重复读)。
  • 可重复读(REPEATABLE READ): 仅在事务第一次执行查询时生成 Read View,之后的查询复用该 Read View。
    • 因此,同一事务内多次查询看到的是同一版本的数据,不受其他事务提交的修改影响(解决不可重复读)。

5.4 MVCC 的优势

  1. 读写不阻塞:读操作无需加锁(快照读),写操作仅锁定当前行,避免了传统锁机制中 “读等写、写等读” 的性能损耗。
  2. 隔离性保证:通过版本链和 Read View,高效实现了 “读已提交” 和 “可重复读” 隔离级别,平衡了一致性和并发性能。
  3. 支持事务回滚:undo 日志不仅用于存储历史版本,也是事务ROLLBACK时恢复数据的依据。
http://www.dtcms.com/a/461913.html

相关文章:

  • 火车采集wordpress百色seo关键词优化公司
  • CVPR 2025 | 频率动态卷积FDConv,标准卷积的完美替代,即插即用,高效涨点!
  • 外贸企业用什么企业邮箱?2025 全球畅邮 TOP3,海外客户沟通无障碍
  • 做网站要注意些什么要求html制作个人简历
  • 第6篇 OpenCV RotatedRect如何判断矩形的角度
  • 响水做网站杭州网站设计手机
  • java面试-0135-InputStream不能重复读取原因及解决?√
  • C++之类的继承与派生
  • Yudao单体项目 springboot Admin安全验证开启
  • 电子商务网站建设风格网站建设技术的实现
  • 【Frida Android】基础篇2:Frida基础操作模式详解
  • 应用于ElasticSearch的C++ API——elasticlient
  • MyISAM存储引擎的特点
  • 伺服滑差补偿方案
  • 无锡网站建设排名安徽网站开发建设
  • 【C++】探秘string的底层实现
  • 建设卡开通网银网站学做网站 空间 域名
  • 基于Simulink的太阳能单极性移相控制光伏并网逆变器
  • 受欢迎的锦州网站建设wordpress取消默认图片
  • CUDA-Q Quake 规范详解:量子中间表示的技术深度解析
  • 包头教育平台网站建设吉化北建公司官网
  • LeetCode 3494.酿造药水需要的最少总时间:模拟(贪心)——一看就懂的描述
  • 做企业网站那家好网站后台图片上传大小
  • 把List<T>构建一颗树封装工具类
  • GISBox v2.0.0:新增功能、问题修复、性能优化三维度,强化GIS服务核心能力
  • Qt界面布局利器:QStackedWidget详细用法解析
  • ClickHouse 配置优化与问题解决
  • 宁波网站建设果核个人网页制作教程简单
  • 白塔网站建设iis .htaccess wordpress
  • 【计算机视觉】基于复杂环境下的车牌识别