当数据库开始处理真正的业务数据时,你很快会遇到一个问题:
如果一组操作只做了一半就失败了,数据怎么办?如果多人同时修改同一批数据,又怎么办?
这就是事务和并发控制要解决的问题。
事务(transaction)可以理解成:一组要么一起成功,要么一起失败的数据库操作。
例如转账场景:
这两步必须绑定在一起。
如果只扣了 A,没有加到 B,数据就错了。
所以这类操作不能只看“单条 SQL 是否成功”,而要看“整组操作是否作为一个整体成功”。
事务最直观的价值是:保证关键操作不会只做一半。
它特别适合这些场景:
简化来说,事务是在保护“业务步骤的一致性”。
BEGIN;UPDATE accountsSET balance = balance - 100WHERE id = 1;UPDATE accountsSET balance = balance + 100WHERE id = 2;COMMIT;
这里:
BEGIN 表示开始事务COMMIT 表示提交事务如果中间出错,不想保留已经执行的部分,可以回滚:
ROLLBACK;
数据库事务经常会提到 ACID。初学阶段不用死背术语定义,更重要的是建立直觉。
一组操作要么全部成功,要么全部失败。
不要只做一半。
事务前后,数据应该仍然满足规则和约束。
例如:
多个事务同时运行时,彼此之间不能随意互相干扰到错误结果。
事务一旦提交成功,结果就应该被可靠保存下来,不是“看起来成功,过会儿又没了”。
学 PostgreSQL 时,一个很重要的关键词是 MVCC,也就是多版本并发控制(Multi-Version Concurrency Control)。
初学者不必先钻底层实现,先记住它带来的直觉:
读和写不一定总是互相堵住。
这很重要。
如果数据库里每次有人写数据,别人就完全不能读,那系统会很容易卡住。
MVCC 的思路可以粗略理解成:数据库会让不同事务看到合适的数据版本,从而减少很多本来会发生的读写阻塞。
所以 PostgreSQL 在并发读写体验上通常比较好,很多常见查询不需要因为别人正在更新而全部停下来。
PostgreSQL 支持多个事务隔离级别。入门阶段,最值得先认识两个:
Read CommittedRepeatable Read这是 PostgreSQL 里很常见的默认直觉起点。
它的意思可以先简单理解为:每条语句读取到的是它开始执行时已经提交的数据。
好处是:
但它也意味着,在同一个事务里,前后两次查询可能看到不同结果,因为别人已经提交了更新。
Repeatable Read 更强调“同一个事务里的读取视图更稳定”。
你可以先这样理解:事务里重复执行同样的查询时,更倾向看到一致的结果快照。
这对于某些需要稳定读取视图的场景会更有帮助。
不过隔离越强,通常并发处理的代价也越高,所以不是默认越高越好。
当事务开始修改数据时,锁就会变得重要。
行级锁影响的是某几行数据。
例如你更新一条订单记录时,数据库通常主要锁住被更新的那几行,而不是整张表。
这也是为什么很多业务系统即使并发很高,仍然能同时处理大量不同记录。
表锁影响的是整张表。
它的影响范围更大,所以在日常业务里,大家通常更希望尽量避免不必要的重型表级阻塞。
初学阶段可以先记住:行锁更细,表锁更重。
假设你在做库存扣减。
BEGIN;SELECT stockFROM productsWHERE id = 1;UPDATE productsSET stock = stock - 1WHERE id = 1 AND stock > 0;COMMIT;
这类操作的重点在于:
这样更容易避免“并发下超卖”这类问题。
再看一个更直观的账户例子:
BEGIN;UPDATE accountsSET balance = balance - 100WHERE id = 1;UPDATE accountsSET balance = balance + 100WHERE id = 2;COMMIT;
如果第二步失败,而你没有事务保护,那么第一步可能已经生效,数据就会不一致。
事务的意义,就是让这种关键业务变成一个整体。
初学者先不用一开始就研究所有锁类型和底层实现,更重要的是建立下面这些直觉:
如果业务步骤跨多条写操作,这个想法通常不安全。
更高的隔离通常意味着更高代价,不是所有场景都需要最强隔离。
锁本身不是坏事,它是数据库用来保护一致性的手段。真正的问题通常是:锁范围过大、持有时间过长、事务设计不合理。
你现在最值得先掌握的是:
Read Committed 和 Repeatable Read 是入门阶段最值得先认识的隔离级别下一篇会继续看 PostgreSQL 很有特色的一部分:JSON 和 JSONB,也就是在关系型数据库里处理半结构化数据。