导读:本期聚焦于兔子创作的《PostgreSQL默认隔离级别为什么是读已提交?深入解析RC级别的实现原理与应用实践》,敬请观看详情。数据库事务隔离级别直接影响数据一致性和并发性能,PostgreSQL默认采用读已提交(Read Committed)级别,这与MySQL默认的可重复读形成鲜明对比。本文将从事务隔离的基本概念入手,详细讲解读已提交级别的具体表现:一个事务内多次执行同一条查询可能看到不同的数据快照,以及UPDATE语句在遇到并发修改时的阻塞重试机制。文章还会深入分析PostgreSQL基于MVCC多版本并发控制实现读已提交的底层原理,对比读已提交、可重复读和串行化三个级别的差异,并结合实际业务场景给出隔离级别的选型建议,帮助开发者在数据正确性与系统吞吐量之间找到平衡点。

PostgreSQL在事务隔离级别的选择上与MySQL走了不同的路线:MySQL的InnoDB引擎默认使用可重复读(Repeatable Read),而PostgreSQL则默认采用读已提交(Read Committed)。这个看似不起眼的差异,却会直接影响应用程序在并发场景下的行为表现。理解读已提交级别的语义和实现原理,是写好并发安全代码的基础,也是排查脏读、丢失更新等问题的前置知识。

PostgreSQL默认隔离级别为什么是读已提交?深入解析RC级别的实现原理与应用实践

一、读已提交级别的具体语义

读已提交是SQL标准中定义的四个隔离级别之一,它的核心承诺只有两条:一个事务只能看到其他已经提交的事务所做的修改,且不会出现脏读。换句话说,事务中的每一条语句,看到的都是该语句开始那一刻已经提交的最新数据快照。

这里有一个非常关键的细节容易被人误解:读已提交针对的是语句级别,而不是事务级别。PostgreSQL官方文档明确指出,在RC级别下,同一个事务内的每一条SQL语句都会获取一个新的快照。这意味着如果你在同一个事务里先执行一次SELECT,中间隔了几秒再执行同样的SELECT,两次查询可能返回不同的结果,这种现象称为不可重复读。此外,如果同一个查询在执行过程中扫描到被其他事务修改的行,还可能出现幻读。

来做一个简单的实验。打开两个psql会话,会话A执行下面的事务:

-- 会话A
BEGIN;
SELECT balance FROM account WHERE id = 1;  -- 返回 1000
-- 此时去会话B执行提交,再回来查询
SELECT balance FROM account WHERE id = 1;  -- 返回 2000,结果变了
COMMIT;

会话B中执行的语句如下:

-- 会话B
UPDATE account SET balance = 2000 WHERE id = 1;

只要会话B的UPDATE在会话A两次SELECT之间提交完成,会话A的第二次查询就会看到新值2000。这就是读已提交的典型表现:事务可以看到外部世界在语句间隙发生的变化。这与可重复读完全不同,后者会在整个事务期间固定使用第一条查询的快照,两次SELECT结果始终一致。

二、MVCC如何支撑读已提交的实现

PostgreSQL没有使用传统的锁来实现读一致性,而是依赖MVCC(多版本并发控制)。每一行数据在物理上都会携带几个隐藏的系统字段,其中最重要的是xmin和xmax。xmin记录创建或最后更新该行版本的事务ID,xmax记录删除或更新该行版本的事务ID(更新在PostgreSQL中被实现为删除加插入的组合)。判断一行对当前查询是否可见的核心逻辑,就是比较xmin和xmax对应事务的状态:只有事务已经提交,它产生的行版本才可见。

在读已提交级别下,快照的获取时机是每条语句开始时。PostgreSQL会调用内部的快照获取函数,记录下当前的活跃事务列表,随后这 条语句扫描数据时,根据快照判断每个行版本的可见性。可见的旧版本直接返回,不可见的版本则沿着多版本链回溯查找。由于读操作完全不阻塞写操作,写操作也不阻塞读操作,RC级别能提供非常高的并发吞吐。

可以用下面的查询直接观察行的版本信息:

SELECT xmin, xmax, balance FROM account WHERE id = 1;
-- xmin 是最后修改该行的事务ID, xmax 非零说明有删除或更新操作发生过

需要说明的是,PostgreSQL的可重复读是通过SSI(可串行化快照隔离)的前身,快照隔离技术实现的,事务开始后的第一条查询决定整个事务的快照点。因此从RC切换到可重复读,本质上是改变了快照的获取策略,从每语句一次变成每事务一次,成本变化不大,但语义差异巨大。而Oracle或MySQL中常见的锁定读(SELECT FOR UPDATE的等待行为)在PostgreSQL中也有独特表现,这一点下面会展开。

三、UPDATE遇到并发修改时的阻塞与重试

读已提交级别下最值得深入理解的行为,是UPDATE语句在目标行被其他未提交事务锁定时的处理方式。假设事务A和事务B同时执行UPDATE account SET balance = balance - 100 WHERE id = 1,PostgreSQL的规则是:后到的UPDATE会阻塞等待,直到先前的持有者事务结束。

这里的行为分为两种情况。如果持有锁的事务回滚了,等待的UPDATE正常获取锁并继续执行,逻辑上没有任何问题。但如果持有锁的事务提交了,等待的UPDATE不会直接在旧版本数据上操作,而是会重新评估该行的最新版本是否仍然满足WHERE条件,即EvalPlanQual机制。如果新版本仍满足条件,UPDATE会在新版本上继续执行;如果新版本已经不满足条件,这行会被跳过,不会报错。这种行为有效避免了基于过期数据做计算的问题。

-- 会话A
BEGIN;
UPDATE account SET balance = balance - 500 WHERE id = 1;  -- 锁定该行,未提交

-- 会话B
UPDATE account SET balance = balance - 300 WHERE id = 1;
-- 阻塞,等待会话A的结果
-- 若会话A提交,B会在新版本上重新评估条件并执行扣减

尽管EvalPlanQual机制保证了单条UPDATE语句的正确性,但它不能解决应用层面的丢失更新问题。典型错误写法是先SELECT读出余额,在应用代码中计算新值,再用UPDATE写回,整个过程如果依赖RC级别,两次读到的数据可能不一致,最终覆盖掉其他事务的修改。正确的做法有三种:使用原子UPDATE语句(如上面的balance = balance - 100)、使用SELECT FOR UPDATE显式加锁,或者改用可重复读并在冲突时重试事务。

四、隔离级别选型建议与实践配置

PostgreSQL共支持三个隔离级别:读已提交、可重复读和串行化,标准中的读未提交在PostgreSQL中被当作读已提交处理。查看和设置当前级别的方式如下:

SHOW default_transaction_isolation;       -- 查看默认级别
SET default_transaction_isolation = 'repeatable read';  -- 会话级修改
BEGIN ISOLATION LEVEL REPEATABLE READ;    -- 单事务指定
BEGIN ISOLATION LEVEL SERIALIZABLE;
COMMIT;

选型上可以遵循这样的思路:绝大多数OLTP业务,读已提交配合原子更新语句和显式行锁就足够了,它锁冲突少、吞吐量高,是PostgreSQL将其设为默认的合理之处。报表类或需要一致性快照导出的任务,适合用可重复读,让整个事务看到同一时刻的数据视图。对正确性要求极高、并发冲突不激烈的场景(比如金融账户间的资金转移),串行化级别通过SSI检测危险的读写依赖并主动中止部分事务,能从数据库层面保证可串行化执行,代价是冲突时应用必须实现重试逻辑。

最后提醒一点,切换隔离级别不是银弹。串行化事务一旦被中止会抛出序列化失败错误,应用代码必须捕获SQLSTATE为40001的错误并重新执行整个事务,否则比RC级别下的隐式风险更糟。总体而言,读已提交作为默认级别在性能与安全之间取得了很好的平衡,理解它的语句级快照语义和UPDATE阻塞重试机制,比盲目调高隔离级别更有实际价值。

PostgreSQL读已提交隔离级别修改时间:2026-09-09 21:38:50

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0909/53617.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。