MySQL事务ACID与隔离级别深度解析

1.1 什么是事务

1.1.1 概念

事务(Transaction)是最小的不可再分的工作单元;一个完整业务由多条 DML(insert、update、delete)语句组成,这些 SQL要么全部执行成功,要么全部不执行,是一组不可分割的数据库操作序列。

重点:MySQL 中只有 InnoDB 引擎支持事务,MyISAM 不支持事务。

复制代码
-- 查看数据库支持的引擎
show engines;

InnoDB 默认引擎,支持事务、行锁、外键。

1.1.2 示例:银行转账业务

张三转账 200 元给李四,需要两条 update 语句:

复制代码
update account set money = money - 200 where id = 1;
update account set money = money + 200 where id = 2;

分析: 两条语句必须同时成功或者同时失败。第一条修改只是在内存记录操作,不会持久化到磁盘;两条全部执行成功后,才同步底层磁盘数据;如果第二条执行失败,清空所有操作记录,全部回滚,这就是事务的作用。

1.1.3 事务处理原则

事务作为一个完整单元执行:

  • 全部成功:执行commit提交,修改永久保存;
  • 任意一步失败:执行rollback回滚,撤销所有修改,回到事务开始之前的状态; 保证数据一致性,不会出现一半成功一半失败的异常数据。

1.2 事务四大特性 ACID

1.2.1 原子性(Atomicity)

事务是不可分割的最小单元,事务内部操作要么全部发生,要么全部不发生。 转账案例:不能出现张三扣钱、李四没加钱的情况。

1.2.2 一致性(Consistency)

事务执行前后,数据库都处于合法一致状态。 转账案例:转账前两人总额 2000,无论事务成功还是失败,总额始终保持 2000,不会凭空多钱或者少钱。

1.2.3 隔离性(Isolation)

多个并发事务互相隔离,每个事务拥有独立的数据空间,一个事务的操作,在未提交前,不应该随意干扰其他事务。

1.2.4 持久性(Durability)

事务一旦提交成功,对数据的修改永久写入磁盘。就算数据库宕机、服务器断电,提交的数据也不会丢失。

1.3 MySQL 事务基础使用

1.3.1 事务三大核心命令

  1. 开启事务:begin / start transaction
  2. 提交事务:commit,持久化修改到磁盘
  3. 回滚事务:rollback,撤销本次事务所有 DML 操作

1.3.2 事务开启方式汇总

方式 命令
显式开启 1 begin + SQL + commit/rollback
显式开启 2 start transaction + SQL + commit/rollback
自动提交关闭 set autocommit=0,后续 DML 属于同一个事务,需要手动 commit
自动提交开启 set autocommit=1,执行 DML 自动提交

1.3.3 autocommit 自动提交参数

MySQL默认 autocommit=1,自动提交,每执行一条 DML 就直接持久化。

复制代码
-- 查询自动提交状态
select @@autocommit;
show variables like 'autocommit';

-- 仅当前会话关闭自动提交
set autocommit=0;
-- 全局关闭自动提交(所有新连接生效)
set global autocommit=0;

注意:事务是基于数据库连接(会话 session),不是基于表。同一个连接中,在 commit/rollback 之前,所有 DML 都会属于同一个事务;开启事务后必须提交或者回滚,否则会长期占用锁资源。

1.3.4 实操示例 1:begin、commit、rollback

复制代码
-- 创建测试库和账户表
create database mydb17_transcation;
use mydb17_transcation;
create table account (id int(10) primary key not null, name varchar (40), money double);
insert into account values(1, '张三', 1000);
insert into account values(2, '李四', 2000);

-- 测试:开启事务修改,不提交,断开连接
begin;
update account set money=3000 where id=2;
select * from account;
exit;

重新登录查询:李四金额依旧是 2000,未提交的事务断开连接自动回滚。

复制代码
-- 提交事务示例
begin;
update account set money=3000 where id=2;
commit;
exit;
-- 重连查询,数据永久生效

-- 回滚事务示例
begin;
delete from account where id=1;
select * from account;
rollback;
select * from account; -- 张三数据恢复

1.3.5 实操示例 2:关闭自动提交

复制代码
select @@autocommit;
set autocommit=0;
insert into account values(3, '王五', 3000);
commit;
select * from account;

1.3.6 回滚点 savepoint

可以在事务内标记回滚点,支持只回滚到指定标记,而不是整个事务全部回滚。

复制代码
savepoint s1;    -- 创建回滚点s1
rollback to s1;  -- 回滚到s1标记处

注意:自动提交开启时,语句执行就提交事务,回滚点会失效,报错SAVEPOINT does not exist。

1.3.7 隐式提交

部分 DDL 语句(create、alter、drop 等)会触发隐式提交,哪怕没有写 commit,事务也会自动提交,无法 rollback。

生产建议:业务事务使用显式commit/rollback,避免隐式提交带来数据风险。

1.4 InnoDB 如何保证 ACID:redo log 与 undo log

1.4.1 WAL 预写日志技术

WAL:Write-Ahead Logging,先写日志,再刷磁盘数据页。 磁盘随机 IO 很慢、顺序 IO 很快,InnoDB 更新数据不会直接修改磁盘数据页,流程:

  1. 数据页加载到 Buffer Pool 缓冲池
  2. 在内存 Buffer Pool 修改数据,生成 redo log 写入 redo log buffer
  3. 事务提交时,先把 redo log 顺序追加写入磁盘 redo log 文件,提交才算成功
  4. 后台线程空闲时,再把脏页(修改过的数据页)随机刷入磁盘数据文件(刷脏)

通俗理解:先记在粉板(redo log),空闲的时候再誊写到账本(数据文件)。

  • redo log 作用:保证持久性 D,宕机后用来恢复已经提交、但是脏页还没刷盘的数据。

1.4.2 redo log 查看命令

复制代码
-- 查看redo日志总容量
show variables like 'innodb_redo_log_capacity';

-- 查看redo运行状态、LSN信息
show global status like '%innodb%redo%';

redo 日志文件存放路径:Data/#innodb_redo,一共 32 个日志文件,循环复用。

1.4.3 CSR 前滚(Crash Safe Recovery)

LSN:日志逻辑序列号,每次写 redo log LSN 递增。 MySQL 重启时对比数据页 LSN 和 redo log 的 LSN:

  • 如果 redo 里面有已经提交、但是还没刷到数据页的修改,自动执行前滚,把数据补齐;
  • 没有提交的事务,通过 undo 回滚撤销。 这个宕机自动恢复流程就是 CSR。

1.4.4 undo 回滚日志

undo log 记录数据修改前的旧版本数据,两大核心作用:

  1. 原子性 A:事务 rollback 的时候,读取 undo 日志恢复修改前的数据;
  2. 隔离性 I、MVCC 多版本:保存历史数据快照,实现可重复读,让读不加锁。

redo log:记录修改之后的数据,用于宕机恢复(前滚); undo log:记录修改之前的数据,用于回滚、快照读。

1.5 事务隔离级别

1.5.1 并发事务三大问题

并发事务不加隔离控制,会出现三类问题:

  1. 脏读 Dirty Read :一个事务读到其他事务未提交的数据,如果对方回滚,读到的数据是脏数据。
  2. 不可重复读 Non-repeatable Read :同一个事务内,多次读取同一条记录,中间被别的事务修改并提交,两次查询结果不一样(针对行数据内容修改)。
  3. 幻读 Phantom Read:同一个事务范围查询,别的事务在范围内新增 / 删除数据并提交,再次查询发现行数变了,像出现幻觉(针对记录条数)。

区分重点: 不可重复读:行内容被 UPDATE 修改; 幻读:范围里面新增 INSERT 或者删除 DELETE 行,查询结果行数变化。

1.5.2 SQL 标准 4 种隔离级别

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 读未提交 ✅ 允许 ✅ 允许 ✅ 允许
READ COMMITTED 读已提交 ❌ 禁止 ✅ 允许 ✅ 允许
REPEATABLE READ 可重复读(MySQL 默认) ❌ 禁止 ❌ 禁止 ✅ 允许(InnoDB 通过间隙锁缓解)
SERIALIZABLE 串行化 ❌ 禁止 ❌ 禁止 ❌ 禁止

1.隔离级别越高,一致性越强,并发性能越低。

2.四种隔离的并发性能如图:

1.5.3 查看和修改隔离级别

复制代码
-- 查询隔离级别(会话+全局)
select @@GLOBAL.transaction_isolation, @@transaction_isolation;
show variables like "transaction_isolation";

-- 修改当前会话隔离级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

-- 修改全局隔离级别,新连接生效
set global transaction isolation level repeatable read;

1.5.4 并发实操演示(两个 mysql 连接)

复制代码
use mydb17_transcation;
create table ac (id int(2) primary key not null, name varchar (10), money double);
insert into ac values(1, '张三', 1000);
insert into ac values(2, '李四', 2000);
set autocommit = 0;
  1. READ UNCOMMITTED:可以读到对方未提交修改,产生脏读;
  2. READ COMMITTED:只能读到对方提交之后的数据,解决脏读,但是会出现不可重复读;
  3. REPEATABLE READ(MySQL 默认):同一个事务内,多次读取结果一致,解决脏读、不可重复读,InnoDB 间隙锁抑制幻读;
  4. SERIALIZABLE 串行化:事务串行排队执行,读操作也会上锁,完全隔离,并发极差,生产极少使用。
相关推荐
悟道书生1 小时前
Kingbase数据库使用命令来执行init.sql(包含建库建表语句)
数据库·oracle
潇湘秦2 小时前
SQL Server 2016+ 整数类型元数据扩宽(秒级变更)完整说明
数据库·算法
中屹指纹浏览器svip2 小时前
指纹浏览器RPA自动化靠谱吗?先分清适用边界
运维·自动化·rpa
晴天¥2 小时前
记一次Oracle 归档进程卡死怎么去有效排查
数据库·oracle
zuiyuewentian2 小时前
借助 AI 在 Win11 上搭建 Rocky Linux:用 Visual Studio 直接调试 Linux 服务
linux·运维·visual studio
牢姐与蒯2 小时前
WSL2 迁移教程:把Ubuntu+WSL2从C盘挪到别的盘
linux·运维·服务器·网络·ubuntu·1024程序员节
小手cool2 小时前
PostgreSQL 基础入门:从安装到核心概念
数据库·postgresql
Bushlet2 小时前
Mysql建库及建表流程总结
数据库·mysql