mysql中的事务

一、什么是事务

事务(Transaction)是一组 SQL 操作的集合,这些操作要么全部成功执行 ,要么全部回滚,不允许只执行一半。经典例子就是银行转账------A 扣钱和 B 加钱必须同时成功或同时失败。

二、ACID 四大特性

这是事务最核心的概念,面试必考。

三、事务控制语句

sql

复制代码
-- 方式一:手动控制
START TRANSACTION;   -- 或者 BEGIN
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;              -- 提交,所有操作永久生效
-- ROLLBACK;         -- 回滚,所有操作撤销

-- 方式二:查看/关闭自动提交
SELECT @@autocommit;       -- 默认为 1(开启)
SET autocommit = 0;        -- 关闭自动提交,需手动 COMMIT

**保存点(SAVEPOINT)**允许部分回滚,不必回到事务起点:

复制代码
START TRANSACTION;
INSERT INTO orders VALUES (1, 'A');
SAVEPOINT sp1;                    -- 设置保存点
INSERT INTO orders VALUES (2, 'B');
ROLLBACK TO SAVEPOINT sp1;        -- 只回滚到 sp1,第一条 INSERT 保留
COMMIT;

回滚 = 撤销本次事务里已经执行过的所有 SQL,让数据回到事务开始前的样子。

就像你写了一半的字,发现写错了,直接橡皮擦全部擦掉,跟没写过一样。

四、并发问题

多个事务并发执行时,如果不加控制,会出现以下三类经典问题:

sql

不可重复读和幻读的区别:前者是同一行数据两次读到不同的 ,后者是同一查询条件两次读到不同数量的

问题 核心描述
脏读 读了别人没提交的(可能回滚的)数据
不可重复读 同一事务内,同一条数据前后读到的值不一样(被改并提交了)
幻读 同一事务内,同一查询条件前后查到的行数不一样(被插入或删除了)

1. 脏读(Dirty Read)

定义 :事务 A 读取了事务 B 已修改但尚未提交的数据。随后事务 B 发生回滚,导致事务 A 读取到的数据是"无效的"或"从未真实存在过的"。

示例场景
时间 事务 A(查询) 事务 B(更新)
T1 开始事务 开始事务
T2 将账户余额从 100 改为 200
T3 读取余额:200
T4 回滚(余额恢复 100)
T5 提交事务(基于 200 做后续计算)

后果:事务 A 基于一个"脏数据" 200 进行了业务操作,导致数据逻辑错误。


2. 不可重复读(Non-Repeatable Read)

定义 :事务 A 内多次读取同一行数据 ,但在两次读取之间,该行数据被事务 B 修改并提交 ,导致事务 A 前后读取的值不一致

示例场景
时间 事务 A(两次读取) 事务 B(修改并提交)
T1 开始事务
T2 读取余额:100
T3 开始事务
T4 将余额改为 200
T5 提交事务
T6 再次读取余额:200
T7 提交事务

后果:事务 A 在同一个逻辑上下文中看到数据"变了",违反了事务内部的一致视图原则。


3. 幻读(Phantom Read)

定义 :事务 A 内多次查询符合某条件的记录集 (范围查询),在两次查询之间,事务 B 插入或删除了符合该条件的行 并提交,导致事务 A 前后看到的记录数量(或集合)不一致

示例场景
时间 事务 A(范围查询) 事务 B(插入)
T1 开始事务
T2 SELECT * FROM t WHERE age>18 → 10 条
T3 开始事务
T4 INSERT INTO t (age) VALUES (20)
T5 提交事务
T6 SELECT * FROM t WHERE age>1811 条
T7 提交事务

后果:就像出现了"幻觉"一样,多出了之前不存在的数据行。

五、隔离级别

SQL 标准定义了四个隔离级别,级别越高越安全,但并发性能越低:

MySQL InnoDB 默认隔离级别是可重复读(REPEATABLE READ) 。表中"基本无"幻读,是因为 InnoDB 通过 MVCC + 间隙锁(Gap Lock) 在这个级别下解决了大多数幻读场景,但在某些情况下(比如当前读)仍然可能出现。

查看和设置隔离级别的命令:

sql

复制代码
-- 查看当前隔离级别
SELECT @@transaction_isolation;  --mysql80 默认为repeatable readl

-- 设置当前会话的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

set session transaction isolation level repeatable read;

-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

六、完整的转账事务示例

把以上所有知识点串联起来:

sql

复制代码
-- 确保关闭自动提交
SET autocommit = 0;

START TRANSACTION;

-- 检查余额
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- 加排他锁

-- 扣款
UPDATE accounts SET balance = balance - 500 WHERE id = 1;

-- 收款
UPDATE accounts SET balance = balance + 500 WHERE id = 2;

-- 检查余额是否合法(一致性由应用层保证)
-- 如果余额不够,回滚
-- ROLLBACK;

COMMIT;  -- 持久化到磁盘(redo log 保证)

知识点总结

维度 要点
ACID 原子/一致/隔离/持久,A靠undo log,D靠redo log
并发问题 脏读 > 不可重复读(行值变)> 幻读(行数变)
隔离级别 默认 REPEATABLE READ,InnoDB用MVCC解决幻读
控制语句 BEGIN → 操作 → COMMIT / ROLLBACK
保存点 SAVEPOINT 支持部分回滚
自动提交 autocommit=1 默认开启,每条语句都是独立事务

测试过程:

问题:

我执行完select* from account,在这之前没有commit的结果是第二张图,但是我在datagrid后面真实查看的时候是第三张图,为什么会这样?

回答:

你在客户端(DataGrip 的 SQL 编辑器)里执行的 select * from account 看到的是 1900/2100,因为那条 SELECT 和前面的两条 UPDATE 在**同一个会话(session)**里,读到了同一个事务内还未提交的修改。

而 DataGrip 的 Data 面板 (第三张图那种表格视图)用的是另一个独立的数据库连接去查询数据,它看到的是已提交到数据库的数据。由于你没有执行 COMMIT,两条 UPDATE 的修改对其他连接是不可见的,所以它看到的仍然是初始值 2000/2000。

用一张图来理解:

这正是 MySQL **隔离性(Isolation)**在起作用:一个事务未提交的数据,对其他事务不可见(MySQL 默认隔离级别是可重复读)。

解决方法

执行完两条 UPDATE 之后,加上 COMMIT 即可:

复制代码
insert into account (id, name, money) values (null, '张三', 2000), (null, '李四', 2000);

start transaction;

update account set money = money - 100 where name = '张三';
update account set money = money + 100 where name = '李四';

commit;  -- 加上这行,Data 面板就能看到 1900/2100 了

select * from account;

提交之后,DataGrip 的 Data 面板刷新就会和编辑器里查到的结果一致了。

总结:

相关推荐
NineData1 天前
NineData 智能数据管理平台新功能发布|2026 年 3 月
数据库·oracle·架构·dba·ninedata·数据复制·数据迁移工具
小陈工1 天前
2026年4月7日技术资讯洞察:下一代数据库融合、AI基础设施竞赛与异步编程实战
开发语言·前端·数据库·人工智能·python
❀͜͡傀儡师1 天前
k8s部署的Nexus 3 数据库损坏恢复指南:从删除损坏数据库到完整数据重建
数据库·kubernetes·nexus3
StackNoOverflow1 天前
Spring Security权限控制框架详解
java·数据库·sql
不愿透露姓名的大鹏1 天前
Oracle归档日志爆满急救指南
linux·数据库·oracle·dba
a里啊里啊1 天前
Redis面试题记录
数据库·redis·缓存
数据知道1 天前
claw-code 源码分析:OmX `$team` / `$ralph`——把 AI 辅助开发从偶发灵感变成可重复流水线
数据库·人工智能·mysql·ai·claude code·claw code
__土块__1 天前
大厂后端一面模拟:从线程安全到分布式缓存的连环追问
jvm·redis·mysql·spring·java面试·concurrenthashmap·大厂后端
麦聪聊数据1 天前
企业数据流通与敏捷API交付实战(六):内部API门户与自助分发机制
数据库·低代码·restful·etl
做个文艺程序员1 天前
深入 MySQL 内核:MVCC、Buffer Pool 与高并发场景下的极限调优
数据库·mysql·adb