CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
PostgreSQL 实战/课时/事务与 MVCC
RawBlame
课时目录12 节

PostgreSQL 实战

  1. 01PostgreSQL 是什么
  2. 02安装与 psql
  3. 03DDL 与约束
  4. 04数据类型
  5. 05JSONB
  6. 06索引与 EXPLAIN
  7. 07事务与 MVCC
  8. 08Spring Boot 接入
  9. 09迁移与 Flyway
  10. 10选型:PG vs MySQL
  11. 11Docker 运行 PG
  12. 12综合练习

事务与 MVCC

ACID、隔离级别、与 MySQL InnoDB 对比。

30 分钟

事务与 MVCC

这一课你将能做到

  1. 用 BEGIN / COMMIT / ROLLBACK 包住多步更新,并演示一次回滚
  2. 说出 PG 默认隔离级别 Read Committed 与 MySQL InnoDB 默认 Repeatable Read 的差异直觉
  3. 用两会话实验理解「写等待」与 MVCC「读不挡写」的基本图像

为什么要学

转账、改库存、发布课时状态——多语句必须成功一起提交或一起失败。PostgreSQL 用 MVCC(多版本并发控制)实现事务:读通常不阻塞写,写冲突用行锁或更高隔离级别处理。

你在 sql-transaction 已学 ACID;本课对齐官方 Transaction Isolation 与 MVCC 概念,并为 Spring @Transactional 换库时的默认隔离差异打预防针。

先建立图像

会话 A                         会话 B
BEGIN                          BEGIN
UPDATE 行 X …                 SELECT 行 X  → 读到已提交旧版(RC 下按语句快照)
(未提交)                       UPDATE 行 X → 等待 A 的行锁
COMMIT                         (A 提交后)B 继续 / 或按隔离级别失败
级别PostgreSQL 要点
Read Committed(默认)每条语句看到语句开始时已提交数据
Repeatable Read事务内重复读更一致;可有序列化异常需重试
Serializable更强;冲突时 serialization_failure

MySQL InnoDB 默认常是 Repeatable Read;不要假设两库默认相同。

跟着做

步骤 1:确认 autocommit 习惯

docker exec -it pg-dev psql -U codeforge -d codeforge_pg
-- 单条默认立即提交
UPDATE app.cf_track SET sort_order = sort_order
WHERE slug = 'sql-essentials';

psql 默认自动提交。显式事务必须 BEGIN。

步骤 2:一次可回滚的事务

BEGIN;

UPDATE app.cf_track
SET sort_order = sort_order + 100
WHERE category = 'language';

SELECT slug, sort_order FROM app.cf_track WHERE category = 'language';

ROLLBACK;  -- 撤销本事务全部更改

SELECT slug, sort_order FROM app.cf_track WHERE category = 'language';

第二次 SELECT 应回到原值。再练一次 COMMIT 留下变更(可自行加回)。

步骤 3:查看与设置隔离级别

SHOW transaction_isolation;

BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM app.cf_track;
-- 保持事务打开,另开会话插入一行后再在本会话 SELECT COUNT
-- RC:可能看到新行;RR:通常仍是事务开始时的快照行数
COMMIT;

用两个终端最直观(步骤 4)。

步骤 4:两会话写冲突

终端 A:

BEGIN;
UPDATE app.cf_track SET title = title || ' [A]'
WHERE slug = 'postgresql-essentials';
-- 先别 COMMIT

终端 B:

BEGIN;
UPDATE app.cf_track SET title = title || ' [B]'
WHERE slug = 'postgresql-essentials';
-- 观察:会等待 A

回到 A:COMMIT; —— B 通常随后完成(或按情况报错)。最后 B 也 COMMIT 或 ROLLBACK,并把 title 改回干净值。

步骤 5:感受长事务风险(只看勿久挂)

-- 开发环境观察锁(表 oid 写法示例)
SELECT pid, mode, granted
FROM pg_locks
WHERE relation = 'app.cf_track'::regclass;

长事务会妨碍 vacuum 回收旧版本 → 表膨胀。Web 请求事务应短;报表大事务要隔离到只读副本或分批。

步骤 6:联系 Spring

回忆:@Transactional 在方法边界开关事务,与底层是 PG 还是 MySQL 无关。换库时重点检查:

  • 默认隔离是否与你假设一致
  • 死锁/序列化失败是否要重试
    见 sb-transaction。

讲清楚要点

  1. 因为 MVCC 保留行版本,所以 普通 SELECT 一般不加锁挡写;写同一行仍要排队。
  2. 默认 Read Committed:语句级快照;业务若需「整笔事务所见一致」,显式提高隔离或在应用层避免依赖幻觉。
  3. ROLLBACK 是一等公民:探索性改数先开事务,看结果再决定提交。
  4. Serializable 不是免费的:冲突时事务 abort,应用要捕获并重试。
  5. 短事务:减少锁等待与旧版本堆积;与 MySQL 长事务拖垮 undo 的教训同构。
  6. DDL 与事务:多数 DDL 可事务化,但锁级别高;生产结构变更走迁移窗口,不在高峰手搓。

你可能会懵的地方

  • 「快照」是整库复制吗?
    不是。是可见性规则:你的事务能看见哪些行版本。

  • 为什么 RR 下有时仍要处理异常?
    PG 的 RR/Serializable 在写冲突时可能抛错,需重试——与「永远不失败」的想象不同。

  • autocommit 关了吗?
    psql 默认开。BEGIN 块内才是多语句事务。JDBC 由连接/框架管理。

  • 读已提交会不会脏读?
    标准脏读(读到未提交)在 PG 已提交级别下不会;要防的是并发下的不可重复与幻读等,按级别变化。

  • 和 MySQL 课冲突吗?
    ACID 相同;默认隔离与锁细节不同。换库读官方隔离章,别硬背一张表。

错法对照

错法问题正解
开着 BEGIN 去吃饭锁与膨胀事务短小;用完即提交/回滚
假定 PG 默认也是 RR并发行为与预期不符SHOW transaction_isolation
捕获异常后不重试 serialization_failure用户看到随机失败按官方建议有限次重试
在事务里跑超长分析 SQL 占主库拖垮 OLTP拆分、超时、只读实例
无 WHERE 的 UPDATE 后才想起来全表被改先 BEGIN + SELECT 确认再提交

自测三问

  1. psql 下怎样才进入多语句事务?默认单条语句提交吗?
  2. PG 默认隔离级别是什么?与本站 MySQL 常见默认有何不同?
  3. 两会话更新同一行时,后到的会话通常先观察到什么现象?

小结一句话

PostgreSQL 用 MVCC + 标准隔离级别保证 ACID;默认 Read Committed,短事务、必要时显式升级并处理重试。

官方延伸

  • PostgreSQL:Transaction Isolation
  • PostgreSQL:Concurrency Control
加载本节练习…
← 上一课索引与 EXPLAIN下一课 →Spring Boot 接入