咱们来搞清楚 READ COMMITTED 这个隔离级别到底干了啥。它的名字其实就暗示了:你在事务里读到的内容,都是别的事务已经“提交”过的。就像现实生活里,我们只相信那些已经被官方记录下来的八卦。
说正经点,这个级别保证了事务不会看到其他事务还没提交的更改。这就解决了“脏读”(Dirty Read)的问题。不过要注意,你读到的数据可能会变——如果别的事务在你两次查询之间提交了更改。这就有了“不可重复读”(Non-Repeatable Read)的风险。
在 PostgreSQL 里,READ COMMITTED 是默认隔离级别。就像默认模式一样——你啥都不用特意设置,直接用就行。
隔离级别 READ COMMITTED 的使用示例
咱们来看看实际操作是啥样。假设有个 accounts 表,里面存着用户和他们的余额:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
account_name TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (account_name, balance)
VALUES ('Alice', 1000.00), ('Bob', 500.00);
现在想象一下下面这个场景。Session 1 和 Session 2 —— 我们故事里的两个主角,都在操作 accounts 表。流程是这样的:
Session 1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_name = 'Alice';
-- 还没 COMMIT 或 ROLLBACK。
这时候,Alice 的 balance 暂时被减了 100,但结果还没提交。
Session 2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE account_name = 'Alice';
结果:Session 2 看到 Alice 的余额还是 1000.00,因为 Session 1 的事务还没结束(还没 COMMIT)。READ COMMITTED 保护我们不被“脏读”坑到。
Session 1 结束事务:
COMMIT;
现在 Alice 的余额更新了,数据库里已经是 900.00。
Session 2 再查一次:
SELECT balance FROM accounts WHERE account_name = 'Alice';
结果:Session 2 现在看到 Alice 的新余额——900.00。注意,这次结果和上次查的不一样,上次是 1000.00。这就是“不可重复读”的问题。
什么时候用 READ COMMITTED?
READ COMMITTED 隔离级别就是性能和一致性的平衡点。有些场景它用起来特别爽:
简单的 CRUD 操作: 你只是单纯地查查、改改数据,没有啥复杂的关联。
批量更新记录: 比如批量改表里的数据,想马上看到已经提交的更改。
处理事务: 比如支付系统,只让用户看到已经确认过的数据。
不过如果你要做复杂的分析查询,或者数据量特别大,可能得考虑别的隔离级别,比如 REPEATABLE READ。
READ COMMITTED 的优缺点
READ COMMITTED 隔离级别算是个黄金中间值。它能保护你不被脏数据坑:你不会看到别的事务刚开始但还没结束的更改。也就是说,没人能读到“半成品”信息,这些信息随时可能被回滚掉。
这个模式比更严格的隔离级别(REPEATABLE READ 或 SERIALIZABLE)快,因为它不需要复杂的锁和额外的检查。它够轻便,也挺靠谱——所以才是默认选项,日常用用完全没问题。
虽然它能防止脏读,READ COMMITTED 还是挡不住:
- “不可重复读”(
Non-Repeatable Read):如果别的事务在你两次查询之间改了数据,你读到的值就变了。 - “幻读”(
Phantom Read):别的事务可能加了新行,影响了你的查询结果。
用 READ COMMITTED 时的小建议
一定要结束事务:别忘了用 COMMIT 或 ROLLBACK,不然容易锁表出问题。
确认隔离级别够用:如果你需要保证事务期间数据不会变,考虑用 REPEATABLE READ。
用好索引:这样 PostgreSQL 查数据、改数据都更快。
示例:订单处理
假设有个 orders 表,存着订单信息:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
);
INSERT INTO orders (customer_name, status)
VALUES ('Alice', 'pending'), ('Bob', 'pending');
我们想把所有状态是 "pending" 的订单状态改掉:
BEGIN;
SELECT * FROM orders WHERE status = 'pending';
UPDATE orders SET status = 'completed' WHERE status = 'pending';
COMMIT;
如果在你操作的过程中,别的事务加了一个新的 "pending" 订单并且 COMMIT 了,你的事务是不会处理到这条新记录的,因为它是在你开始读之后才加进来的。
这就是“幻读”的例子。如果你想避免这种情况,就得用 SERIALIZABLE。
READ COMMITTED 隔离级别是大多数数据库(包括 PostgreSQL)的默认选择。它能防止脏读,所以很适合大多数常规操作。但如果你有更高的一致性要求,可能得用更严格的隔离级别。隔离级别怎么选,还是得看你具体的需求和性能要求。
GO TO FULL VERSION