mysql中的事务

张开发
2026/4/12 13:55:57 15 分钟阅读

分享文章

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 改为 200T3读取余额200T4回滚余额恢复 100T5提交事务基于 200 做后续计算后果事务 A 基于一个“脏数据” 200 进行了业务操作导致数据逻辑错误。2. 不可重复读Non-Repeatable Read定义事务 A 内多次读取同一行数据但在两次读取之间该行数据被事务 B修改并提交导致事务 A 前后读取的值不一致。示例场景时间事务 A两次读取事务 B修改并提交T1开始事务T2读取余额100T3开始事务T4将余额改为 200T5提交事务T6再次读取余额200T7提交事务后果事务 A 在同一个逻辑上下文中看到数据“变了”违反了事务内部的一致视图原则。3. 幻读Phantom Read定义事务 A 内多次查询符合某条件的记录集范围查询在两次查询之间事务 B插入或删除了符合该条件的行并提交导致事务 A 前后看到的记录数量或集合不一致。示例场景时间事务 A范围查询事务 B插入T1开始事务T2SELECT * FROM t WHERE age18→ 10 条T3开始事务T4INSERT INTO t (age) VALUES (20)T5提交事务T6SELECT * FROM t WHERE age18→11 条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 logD靠redo log并发问题脏读 不可重复读行值变 幻读行数变隔离级别默认REPEATABLE READInnoDB用MVCC解决幻读控制语句BEGIN→ 操作 →COMMIT/ROLLBACK保存点SAVEPOINT支持部分回滚自动提交autocommit1默认开启每条语句都是独立事务测试过程问题我执行完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 面板刷新就会和编辑器里查到的结果一致了。总结

更多文章