炭炭背单词免费
125.76MB · 2025-10-10
ACID是数据库事务的四个核心特性,是保证数据可靠性和一致性的基石。这四个字母分别代表:
PostgreSQL作为关系型数据库的佼佼者,通过事务日志(WAL)、MVCC(多版本并发控制)、锁机制等技术,完整实现了ACID特性。
原子性是事务的“灵魂”——它保证了复杂操作的“整体性”。比如转账业务中,“扣减A账户余额”和“增加B账户余额”必须同时成功或同时失败,不能出现“钱扣了但没到账”的情况。
PostgreSQL中,事务通过BEGIN
(或START TRANSACTION
)开启,COMMIT
提交(确认修改),ROLLBACK
回滚(撤销所有修改):
-- 开启事务
BEGIN;
-- 执行操作:扣减A账户200元
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
-- 执行操作:增加B账户200元
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
-- 提交事务(修改生效)
COMMIT;
如果中间任何一步出错(比如B账户不存在),PostgreSQL会自动回滚整个事务,所有修改都会被撤销。
当事务需要“部分回滚”时,可以用保存点(Savepoint)。比如,在一个长事务中,你可能想尝试某个操作,失败后只回滚该操作,而保留之前的修改:
BEGIN;
-- 第一步:扣减A账户200元(成功)
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
-- 创建保存点sp1
SAVEPOINT sp1;
-- 第二步:尝试给C账户转200元(失败,C账户不存在)
UPDATE accounts SET balance = balance + 200 WHERE id = 3;
-- 回滚到保存点sp1(仅撤销第二步)
ROLLBACK TO sp1;
-- 调整操作:给B账户转200元(成功)
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
-- 提交事务(最终A扣200,B加200)
COMMIT;
保存点的作用是将原子性“细分”,但整个事务依然保持原子性——只有COMMIT
后,所有修改才会永久生效。
一致性是指事务执行前后,数据库必须满足所有“数据完整性约束”。这些约束包括:
user_id
必须存在于用户表);balance >= 0
);PostgreSQL会在事务执行每个修改操作时自动检查约束。比如,当你尝试插入一条balance=-100
的记录时:
-- 尝试插入非法数据(balance为负)
INSERT INTO accounts (user_id, balance) VALUES (3, -100);
PostgreSQL会立即抛出错误:
ERROR: check constraint "accounts_balance_check" violated by row
此时事务会进入“终止状态”,必须通过ROLLBACK
撤销所有修改,才能继续操作。
一致性是应用层+数据库层共同作用的结果:
当多个用户同时操作数据库时,隔离性保证每个事务“看不到”其他事务的中间状态。PostgreSQL通过隔离级别和**MVCC(多版本并发控制)**实现这一特性。
PostgreSQL支持四种隔离级别(从弱到强):
隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-repeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
读未提交(Read Uncommitted) | 允许 | 允许 | 允许 |
读已提交(Read Committed) | 禁止 | 允许 | 允许 |
可重复读(Repeatable Read) | 禁止 | 禁止 | 禁止 |
串行化(Serializable) | 禁止 | 禁止 | 禁止 |
注意:PostgreSQL的默认隔离级别是读已提交(Read Committed),这是“性能与一致性的平衡选择”。
我们用一个简单的例子,看不同隔离级别的行为差异:
假设accounts
表中有一条记录:id=1, balance=1000
。
-- 事务A(读已提交)
BEGIN;
-- 第一次查询:得到1000
SELECT balance FROM accounts WHERE id=1;
-- 此时事务B执行:UPDATE accounts SET balance=1500 WHERE id=1; COMMIT;
-- 第二次查询:得到1500(不可重复读)
SELECT balance FROM accounts WHERE id=1;
COMMIT;
结论:读已提交级别下,每个语句都会读取“最新的已提交数据”,因此会出现“不可重复读”。
-- 事务A(可重复读)
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 第一次查询:得到1000(快照生成)
SELECT balance FROM accounts WHERE id=1;
-- 事务B执行UPDATE并提交
-- 第二次查询:依然得到1000(快照一致)
SELECT balance FROM accounts WHERE id=1;
COMMIT;
结论:可重复读级别下,事务会基于“开始时的快照”进行所有查询,避免了不可重复读和幻读。
-- 事务A(串行化)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id=1; -- 1000
-- 事务B尝试UPDATE accounts SET balance=1500 WHERE id=1;
-- 此时事务B会被阻塞,直到事务A提交或回滚
COMMIT;
结论:串行化级别下,事务会“串行执行”,完全避免所有并发问题,但性能最低(适合对一致性要求极高的场景,比如金融交易)。
PostgreSQL的隔离性依赖MVCC(多版本并发控制)。它的核心思想是:
VACUUM
)。比如,当事务B修改balance
为1500时,PostgreSQL会:
持久性是指事务提交后,修改会永久保存,即使系统崩溃也不会丢失。PostgreSQL通过**WAL(Write-Ahead Logging,预写日志)**实现这一特性。
WAL的核心逻辑是:所有修改必须先写入日志,再写入数据文件。具体流程如下:
UPDATE
操作时,PostgreSQL先将修改内容写入WAL日志(顺序写入,速度快);fsync
调用,保证日志不丢失);Checkpoint
机制)。即使系统突然崩溃(比如断电),重启后PostgreSQL会:
Checkpoint
是PostgreSQL定期执行的“磁盘同步操作”,它会:
这样,当系统崩溃时,PostgreSQL只需要重放检查点之后的WAL日志,大大减少恢复时间。
我们用一个经典的“转账场景”,演示如何用ACID特性保证数据正确性:
首先创建账户表(包含Check
约束,保证余额非负):
-- 创建账户表
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
balance NUMERIC(10,2) NOT NULL CHECK (balance >= 0)
);
-- 插入测试数据:用户1有1000元,用户2有500元
INSERT INTO accounts (user_id, balance) VALUES (1, 1000.00), (2, 500.00);
用户1给用户2转200元,事务必须保证:
-- 开启转账事务
BEGIN;
-- 步骤1:扣减用户1的余额
UPDATE accounts SET balance = balance - 200.00 WHERE user_id = 1;
-- 步骤2:增加用户2的余额
UPDATE accounts SET balance = balance + 200.00 WHERE user_id = 2;
-- 步骤3:检查是否有错误(比如用户1余额不足)
-- 如果一切正常,提交事务
COMMIT;
如果用户1的余额不足(比如只有100元),步骤1会触发Check
约束错误:
ERROR: check constraint "accounts_balance_check" violated by row
此时事务会自动终止,必须通过ROLLBACK
撤销所有修改:
ROLLBACK;
答案:原子性是事务要么全做要么全不做。PostgreSQL通过ROLLBACK
(回滚)和Savepoint
(保存点)实现原子性——当事务出错时,回滚所有修改;保存点允许部分回滚,但整个事务依然原子。
答案:默认隔离级别是读已提交(Read Committed)。它能避免脏读,但不能避免不可重复读和幻读。
答案:WAL要求所有修改先写入日志(同步到磁盘),再写入数据文件。即使系统崩溃,重启后PostgreSQL会重放WAL日志中的未完成修改,保证数据不丢失。
答案:将两个操作包裹在一个事务中:
BEGIN;
-- 扣减库存
UPDATE products SET stock = stock - 1 WHERE id = 100;
-- 创建订单
INSERT INTO orders (user_id, product_id) VALUES (1, 100);
COMMIT;
如果任何一步出错,ROLLBACK
会撤销所有修改,避免“库存扣了但订单没创建”的情况。
current transaction is aborted, commands ignored until end of transaction block
原因:事务中某条语句出错(比如违反约束),导致事务进入“终止状态”。
解决:执行ROLLBACK
撤销所有修改,再重新执行正确的语句。
预防:在应用层捕获错误,及时回滚事务。
could not serialize access due to read/write dependencies among transactions
原因:使用Serializable
隔离级别时,并发事务存在“读写依赖”,无法串行执行。
解决:重试事务,或降低隔离级别到Repeatable Read
。
预防:避免在高并发场景使用Serializable
级别。
check constraint "xxx" violated by row
原因:修改操作违反了Check
约束(比如余额为负)。
解决:检查数据合法性,修改后重新执行。
预防:应用层先验证数据(比如前端检查“转账金额不能为负”)。
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
,阅读完整的文章:转账不翻车、并发不干扰,PostgreSQL的ACID特性到底有啥魔法?