• Welcome to HiddenMerit - Clyde's Blog
  • Welcome to try the game Torn: Referral Link
  • If you are my relative, friend, or netizen, quickly press Ctrl+D to bookmark Clyde's Blog
  • This site has a like feature. If you read any article, please hit the like button so I know someone has visited
  • Email: hiddenmeritATgmail.com (replace AT with @)

PostgreSQL 事务与并发系列 · 第一期

DBA Clyde Jin 3周前 (04-24) 8次浏览 0个评论

PostgreSQL 事务与并发系列 · 第一期

从 ACID 到 MVCC:PostgreSQL 并发控制的核心思想

本系列将带你系统掌握 PostgreSQL 事务与并发的每一步。第一期从 ACID 原则谈起,揭开 MVCC 如何实现”读不阻塞写、写不阻塞读”。

一、引言

在关系型数据库的世界里,事务(Transaction)是数据一致性与并发控制的基石。PostgreSQL 之所以被公认为”业界最先进的开源关系型数据库”,其强大的事务处理能力和高并发支持功不可没。

理解 PostgreSQ L的事务与并发机制,不仅仅是掌握几条 SQL 语句。它直接决定了:

  • 你写的应用在高并发下能否稳定运行
  • 你的数据库能否扛住千万级 QPS 的冲击
  • 你遇到死锁、性能瓶颈时能否快速定位并解决

本系列将从 ACID 原则讲起,逐步深入到 MVCC 实现原理、隔离级别的行为差异、锁机制的分类与死锁排查,最后涵盖快照隔离、长事务与膨胀治理,帮助你对 PostgreSQL 的事务与并发模块建立系统性的认知。


二、事务的 ACID 原则

什么是事务?

用数据库的术语来说,事务是由一个或多个 SQL 语句组成的工作单元。最经典的例子是银行转账——从账户 A 扣款和向账户 B 入账,这两个操作要么一起成功,要么一起失败,绝不允许出现”钱扣了,但对方没收到”这种中间状态。

事务所解决的四大并发现象

PGSQL 的事务机制正是为了解决以下四大潜在问题而产生的:脏读、不可重复读、幻读、序列化异常(后文将详细说明)。

ACID 四大特性

ACID 是事务的四大核心属性,也是 PostgreSQL 设计并发控制机制的根本原则:

特性 含义 PostgreSQL 的实现技术
原子性(Atomicity) 事务中的所有操作要么全部成功提交,要么全部失败回滚 事务管理 + 撤销日志
一致性(Consistency) 事务执行前后,数据库始终保持一致的状态(约束、规则) 主键、外键、检查约束等
隔离性(Isolation) 并发事务之间互不干扰 多版本并发控制(MVCC)
持久性(Durability) 事务一旦提交,其修改就永久保存,即使系统故障也不丢失 预写式日志(WAL)+ 恢复子系统

三、原始的并发控制:锁

在 MVCC 诞生之前,大多数数据库系统采用基于锁的并发控制机制,其中最具代表性的是 S2PL(严格两阶段锁)

  • 读操作需要获取共享锁
  • 写操作需要获取排他锁

这也正是 PostgreSQL 经典的“读不阻塞写,写也不会阻塞读”设计初衷得以实现的基础


四、MVCC:PostgreSQL 并发的灵魂

4.1 什么是 MVCC?

多版本并发控制(Multi-Version Concurrency Control, MVCC)是 PostgreSQL 实现高并发隔离性的核心技术。

核心理念是:当数据被修改时,数据库不直接覆盖原有数据,而是创建一个新的版本,保留历史版本

这种设计带来最直观的效果就是:读者永远不会被写者阻塞,写者也永远不会被读者阻塞

4.2 MVCC 的核心组件

PostgreSQL 中的每一条元组(tuple)都带有两个系统字段:

  • xmin:创建这行版本的事务 ID
  • xmax:删除/过期这行版本的事务 ID

当事务读取数据时,PostgreSQL 会根据当前事务的隔离级别和快照信息,来决定应该看到哪个版本的数据,实现在无需加锁的情况下保证事务隔离性和一致性。


五、事务隔离级别

SQL 标准定义了四种隔离级别,但 PostgreSQL 在内部只实现了三种不同的隔离级别——因为其”读未提交”的行为实际等同于”读已提交”。

隔离级别 脏读 不可重复读 幻读 PostgreSQL 默认?
读未提交 可能 可能 可能
读已提交 不可能 可能 可能 ✅ 默认
可重复读 不可能 不可能 可能 🟡 可设置
可串行化 不可能 不可能 不可能 🟡 最高级别

三种的不可重复出现性及其影响:PostgreSQL 都提供了完整的保障


六、快照隔离

6.1 什么是快照?

快照是 PostgreSQL 实现 MVCC 的核心数据结构。当一个事务开始时,PostgreSQL 会为该事务生成一个快照,记录:

  • xmin:当前未完成的最小事务 ID
  • xmax:下一个被分配的事务 ID
  • xip_list:当前活跃事务列表

通过这个快照,PostgreSQL 能准确地判断每个数据版本对当前事务是否可见。

6.2 不同隔离级别下的快照行为

  • 读已提交:事务中的每条语句执行前都会重新获取一次快照,这意味着同一个事务中的不同语句可能看到不同的最新数据。
  • 可重复读:事务只在其第一条语句执行时获取一次快照,后续所有语句都复用这个快照,保证整个事务内看到的数据是一致的。
  • 可串行化:基于可重复读的快照机制,但额外增加了对事务间依赖的检测,在冲突时主动终止事务以保持”真正的串行化”语义。

七、PostgreSQL 中的锁

MVCC 减少了大多数情况下的锁竞争,但在某些场景下,锁依然是不可或缺的。

7.1 标准锁的分类

PostgreSQL 的锁分为多个类型,常见的有:

锁模式 使用场景 冲突模式
表级锁 控制对整个表的并发访问 ACCESS SHARE / ROW EXCLUSIVE / ACCESS EXCLUSIVE 等
行级锁 控制对特定行的并发修改 FOR UPDATE / FOR SHARE

7.2 建议锁

PostgreSQL 提供了一种应用层面的锁机制——建议锁。应用可以选择任意一个 64 位键值作为锁标识,然后申请锁、执行业务逻辑、释放锁,实现跨进程的分布式协调,非常适合防止重复处理(如计费调度、消息队列中的任务重复执行)。

7.3 FOR UPDATE 与 FOR UPDATE SKIP LOCKED

SELECT ... FOR UPDATE 是一种显示行锁,它会锁定被选中的行,直到当前事务结束,确保这些行不会被其他事务并发修改。在 PostgreSQL 9.5 引入的 SKIP LOCKED 修饰符,改变了这一默认行为:当被锁定的行正在被其他事务持有时,SELECT ... FOR UPDATE SKIP LOCKED 会直接跳过这些行,只返回当前可锁定的行,这是构建任务队列、工作池模式的最佳实践。


八、死锁

8.1 什么是死锁?

死锁(Deadlock) 是指两个或多个事务互相等待对方释放锁,从而形成循环依赖,导致谁都无法继续执行的情况。

8.2 如何诊断死锁

当死锁发生时,PostgreSQL 会将相关信息记录到日志中。你可以通过查询 pg_locks 视图并结合 pg_stat_activity,定位到阻塞链中的具体进程及其正在执行的查询。

8.3 如何避免死锁

最佳实践是:让所有事务以一致的顺序访问资源。例如,如果需要同时更新多张表,可以在应用层面约定:总是先锁表 A,再锁表 B。

如果死锁确实发生了,PostgreSQL 的死锁检测器会在等待一段时间后(由 deadlock_timeout 参数控制)自动终止其中一个事务,让另一个继续执行。


九、总结与下期预告

在本期中,我们探索了事务的 ACID 原则、MVCC 的核心思想、隔离级别的行为差异、快照的机制,以及锁和死锁的基础知识。这些是你理解和驾驭 PostgreSQL 并发的”第一块拼图”。

第二期预告:我们将深入 MVCC 的内部实现,详细解读可见性判断规则、事务快照的内存布局,并通过实践案例让你亲手感知”读不阻塞写,写不阻塞读”到底是怎么做到的。

  • 读已提交 vs 可重复读:实际查询中的可见性差异
  • 快照源码解析:GetSnapshotData 函数的作用
  • 从实战案例看 MVCC:一个行版本如何在不同隔离级别下被不同事务”看见”或”看不见”

下一期,我们将一起”动手”操作 MVCC,真正把它还原为代码和数据。敬请期待!


绩隐金 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:PostgreSQL 事务与并发系列 · 第一期
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址