7.3 事务与 ACID


7.3 事务与 ACID

本节摘要:事务把多个操作打包成"要么全做要么全不做"的原子单元。本节讲事务的 ACID 特性、COMMIT/ROLLBACK 用法、隔离级别。

核心问题

阅读完本节,你应当能够:

  1. 说清 ACID 四个特性
  2. 用 BEGIN/COMMIT/ROLLBACK 管理事务
  3. 理解四种隔离级别和并发问题
  4. 避免长事务的坑

概念脉络

一、为什么需要事务

转账场景:A 给 B 转 100 元,要扣 A 的余额、加 B 的余额两步。如果扣完 A 的余额后系统崩了,B 没收到——钱凭空消失。事务保证这两步"要么都成功要么都回滚",不会出现中间状态。

BEGIN; UPDATE Accounts SET Balance = Balance - 100 WHERE User = 'A'; UPDATE Accounts SET Balance = Balance + 100 WHERE User = 'B'; COMMIT; -- 两步都成功才提交 -- 若中间出错:ROLLBACK; 两步都撤销

二、ACID 四特性

图 7-3 事务 ACID

图 7-3 事务 ACID

  • A 原子性(Atomicity):事务里的操作要么全做要么全不做,出错回滚。
  • C 一致性(Consistency):事务前后数据满足所有约束(主键、外键、CHECK 等),状态合法。
  • I 隔离性(Isolation):并发事务互不干扰,靠隔离级别控制。
  • D 持久性(Durability):事务提交后即使系统崩溃也不丢。

三、BEGIN/COMMIT/ROLLBACK

BEGIN; -- 开启事务 INSERT INTO Orders ...; UPDATE Accounts ...; COMMIT; -- 提交,所有变更持久化 -- 或出错时 BEGIN; INSERT INTO Orders ...; -- 发现错误 ROLLBACK; -- 回滚,所有变更撤销

很多 DBMS 默认 autocommit(每条语句自动提交),要多个语句作为一个事务需显式 BEGIN

四、隔离级别

并发事务可能互相干扰,产生脏读、不可重复读、幻读等问题。隔离级别控制干扰程度:

隔离级别 脏读 不可重复读 幻读 性能
读未提交 可能 可能 可能 最快
读已提交 避免 可能 可能
可重复读 避免 避免 可能
串行化 避免 避免 避免 最慢
  • 脏读:读到别的事务未提交的数据(提交前回滚了就是脏的)。
  • 不可重复读:同一事务内两次读同一行,值不同(被别的事务改了)。
  • 幻读:同一事务内两次范围查询,行数不同(被别的事务插/删了)。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

级别越高隔离越强但并发越低。多数场景用默认(读已提交或可重复读)即可。

五、长事务的坑

事务持有锁,长事务长时间持锁影响并发,甚至导致死锁。原则:

  • 事务尽量短小,快速提交
  • 别在事务里做耗时操作(如网络请求、用户交互)
  • 按固定顺序访问资源,避免死锁

⚠️ 常见坑:开了事务忘提交(长事务),长时间持锁阻塞别人;或事务里做耗时操作拖慢系统。事务要短小快提交。

💡 关键直觉:事务保证"要么全做要么全不做",靠 ACID。隔离级别权衡隔离性和性能,多数用默认。事务要短小快提交,别在里头做耗时操作。

本节速览

  • 事务:把多操作打包成原子单元,BEGIN/COMMIT/ROLLBACK。
  • ACID:原子性(全做或全不做)、一致性(状态合法)、隔离性(并发不干扰)、持久性(提交不丢)。
  • 隔离级别:读未提交/读已提交/可重复读/串行化,隔离越强并发越低,多数用默认。
  • 并发问题:脏读、不可重复读、幻读,靠隔离级别避免。
  • 长事务:持锁久、影响并发、可能死锁。事务短小快提交,别做耗时操作。

下一节讲用户和权限管理——谁能做什么。

常见疑问

Q1:事务到底解决什么问题?

让"多个操作"变成"一个原子动作":要么全部成功,要么全部回滚。没有事务,转账就是"扣款成功、入账失败"的灾难。事务是数据库保证数据一致性的核心机制,凡是涉及"多步写操作"的业务都应该用事务。

Q2:四个隔离级别到底差在哪?

从低到高:读未提交(可读脏数据)到读已提交(默认,读不到未提交的,但不可重复读)到可重复读(MySQL 默认,同一事务内重复读结果一致,仍可能有幻读)到串行化(完全隔离,最慢)。级别越高隔离越好、并发越低。大多数场景用数据库默认即可,别随意调高。

Q3:什么是脏读、不可重复读、幻读?

脏读:读到别人未提交又回滚的数据;不可重复读:同一事务内两次读同一行,值被别的事务改了;幻读:同一事务内两次范围查询,行数被别的事务插入/删除了。理解这三个问题就能理解隔离级别的意义。

Q4:事务一定要手动 COMMIT 吗?

取决于数据库默认。MySQL 默认 autocommit=1(每条语句自动提交),所以多条语句组成事务要显式 BEGIN...COMMIT;PostgreSQL 默认在事务块内需显式 COMMIT。无论哪种,明确提交时机、及时提交/回滚都是好习惯。

Q5:长事务有什么危害?

持有锁时间过长,阻塞其他事务;占用连接和事务日志;回滚代价大。生产事故常见"开了事务忘了提交"。原则:事务要短、要快、不在事务里做耗时操作(网络请求、大批量查询)。

Q6:死锁怎么产生的?

两个事务互相持有对方需要的锁,各自等待,谁也无法继续。数据库会自动检测并回滚其中一个(牺牲者)。避免:固定加锁顺序、事务尽量短、减少锁范围。死锁报错后重试即可,不必恐慌。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U