炭炭背单词免费
125.76MB · 2025-10-10
事务是数据库中不可分割的工作单元——它将多个SQL操作(如插入、更新、删除)打包成一个整体,确保这些操作要么全部成功执行,要么全部回滚到初始状态。
举个生活中的例子:银行转账时,“从A账户扣100元”和“向B账户加100元”必须同时完成。如果扣了A的钱但加B的钱失败(比如B账户不存在),整个操作必须回滚——A的钱不能白扣,这就是事务的核心作用。
事务的可靠性由ACID四大特性保障,这是数据库设计的基石:
PostgreSQL通过3个核心语句控制事务生命周期,语法简单且直观。
用BEGIN
或START TRANSACTION
标记事务的开始(两者等价)。
例子:
-- 启动一个事务
BEGIN;
-- 或 START TRANSACTION;
用COMMIT
确认事务中的所有操作,将修改持久化到数据库。
例子(银行转账):
BEGIN;
-- 从A账户扣100元
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 向B账户加100元
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 提交事务(修改生效)
COMMIT;
用ROLLBACK
撤销事务中的所有操作,回到事务开始前的状态。
例子(转账失败回滚):
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 假设B账户不存在,执行失败
UPDATE accounts SET balance = balance + 100 WHERE id = 999; -- 无此记录
-- 回滚事务(A的钱恢复)
ROLLBACK;
当多个事务同时执行时,会出现以下问题:
隔离级别就是用来控制这些问题的严重程度——级别越高,隔离性越好,但性能越低(因为要排队执行)。
PostgreSQL支持4个标准隔离级别,默认是“读已提交”:
隔离级别 | 避免脏读? | 避免不可重复读? | 避免幻读? | 性能 | 适用场景 |
---|---|---|---|---|---|
读未提交(Read Uncommitted) | (实际等价读已提交) | 最高 | 几乎不用(PostgreSQL中无意义) | ||
读已提交(Read Committed) | 高 | 大多数OLTP场景(如电商、支付) | |||
可重复读(Repeatable Read) | 中 | 报表、数据分析(需要一致结果) | |||
串行化(Serializable) | 最低 | 严格一致性场景(如金融清算) |
理论上允许脏读,但PostgreSQL中这个级别和“读已提交”完全一样——PostgreSQL不会让你读到未提交的修改(官网明确说明)。所以这个级别在PostgreSQL中是“摆设”,不用关注。
只能读到已提交的修改,是最常用的级别。
例子:
同一事务中多次读同一数据,结果完全一致,即使其他事务修改并提交了该数据。
例子:
最高级别,完全隔离——事务像“排队”一样执行,避免所有并发问题,但性能最低。
例子:
可以在启动事务时指定隔离级别,语法:
-- 方式1:启动事务时指定
BEGIN TRANSACTION ISOLATION LEVEL 隔离级别;
-- 方式2:事务中动态修改(不推荐,容易混乱)
SET TRANSACTION ISOLATION LEVEL 隔离级别;
例子:启动一个“可重复读”事务:
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 后续操作...
COMMIT;
保存点(Savepoint)是事务中的**“中间 checkpoint”**——可以回滚到某个保存点,而不是整个事务。
语法:
SAVEPOINT 保存点名称
:设置保存点。ROLLBACK TO 保存点名称
:回滚到保存点(保存点之前的操作保留,之后的操作撤销)。RELEASE SAVEPOINT 保存点名称
:删除保存点(可选)。例子:复杂事务中的部分回滚:
BEGIN;
-- 步骤1:扣减库存(成功)
UPDATE products SET stock = stock - 1 WHERE id = 100;
-- 设置保存点sp1
SAVEPOINT sp1;
-- 步骤2:创建订单(失败,比如用户ID错误)
INSERT INTO orders (user_id, product_id) VALUES (999, 100); -- 无此用户
-- 回滚到sp1(步骤2撤销,但步骤1保留)
ROLLBACK TO sp1;
-- 修正步骤2:用正确的用户ID
INSERT INTO orders (user_id, product_id) VALUES (1, 100);
-- 提交事务(步骤1和修正后的步骤2生效)
COMMIT;
电商下单需要完成3个操作:
这3个操作必须全部成功,否则全部回滚(比如库存不足时,不能创建订单;余额不足时,不能扣库存)。
首先创建表并插入测试数据:
-- 商品表(库存)
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
stock INT CHECK (stock >= 0) -- 库存不能为负
);
-- 订单表
CREATE TABLE orders (
id SERIAL PRIMARY KEY, -- 自增ID
user_id INT,
product_id INT,
quantity INT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 下单时间
);
-- 用户表(余额)
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
balance NUMERIC(10, 2) CHECK (balance >= 0) -- 余额不能为负
);
-- 插入测试数据:商品ID100有10件库存,用户ID1有5000元余额
INSERT INTO products VALUES (100, '笔记本电脑', 10);
INSERT INTO users VALUES (1, '张三', 5000.00);
然后用**PL/pgSQL(PostgreSQL的存储过程语言)**实现事务(带异常处理):
CREATE OR REPLACE FUNCTION place_order(
p_user_id INT, -- 用户ID
p_product_id INT, -- 商品ID
p_quantity INT -- 购买数量
) RETURNS VARCHAR AS $$
BEGIN
-- 启动事务(默认读已提交)
BEGIN TRANSACTION;
-- 1. 扣减库存(检查库存是否足够)
UPDATE products
SET stock = stock - p_quantity
WHERE id = p_product_id AND stock >= p_quantity;
-- 如果更新行数为0(库存不足),抛出异常
IF NOT FOUND THEN
ROLLBACK;
RETURN '库存不足';
END IF;
-- 2. 创建订单
INSERT INTO orders (user_id, product_id, quantity)
VALUES (p_user_id, p_product_id, p_quantity);
-- 3. 扣减用户余额(假设商品单价5000元)
UPDATE users
SET balance = balance - (5000.00 * p_quantity)
WHERE id = p_user_id AND balance >= (5000.00 * p_quantity);
-- 如果更新行数为0(余额不足),抛出异常
IF NOT FOUND THEN
ROLLBACK;
RETURN '余额不足';
END IF;
-- 提交事务(所有操作生效)
COMMIT;
RETURN '下单成功';
-- 捕获所有异常,回滚事务
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RETURN '下单失败:' || SQLERRM; -- 返回错误信息
END;
$$ LANGUAGE plpgsql;
-- 测试1:正常下单(库存10→9,余额5000→0)
SELECT place_order(1, 100, 1); -- 返回“下单成功”
-- 测试2:库存不足(库存9→8?不,购买2件的话库存只有9,不够)
SELECT place_order(1, 100, 2); -- 返回“库存不足”
-- 测试3:余额不足(余额0,再买1件需要5000元)
SELECT place_order(1, 100, 1); -- 返回“余额不足”
答案:BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
解析:PostgreSQL中可以在启动事务时直接指定隔离级别,语法是BEGIN TRANSACTION ISOLATION LEVEL [级别名称]
。
答案:
ROLLBACK
:回滚整个事务,所有操作都撤销。ROLLBACK TO [保存点]
:只回滚到保存点之后的操作,保存点之前的操作保留。SAVEPOINT sp1
,ROLLBACK TO sp1
会撤销sp1之后的操作,但sp1之前的操作仍然有效。答案:
SHOW transaction_isolation;
(返回read committed
)。原因:事务中的某条语句失败(比如违反约束、语法错误),导致事务进入“aborted”状态(报废),后续语句会被忽略。
例子:
BEGIN;
-- 违反CHECK约束(stock不能为负)
UPDATE products SET stock = -1 WHERE id = 100; -- 报错
-- 后续语句会被忽略
INSERT INTO orders ...; -- 报错“current transaction is aborted”
解决:执行ROLLBACK
回滚事务,然后重新执行正确的语句。
预防:用PL/pgSQL的EXCEPTION
块捕获错误,自动回滚(如5.2中的例子)。
原因:已经处于一个事务中,又执行了BEGIN
或COMMIT
(嵌套事务)。
例子:
BEGIN; -- 启动事务
UPDATE products ...;
BEGIN; -- 错误:已经在事务中
解决:检查事务控制语句的顺序,确保每个BEGIN
对应一个COMMIT
或ROLLBACK
,不要嵌套。
预防:执行BEGIN
前,用SELECT pg_transaction_status();
查看当前事务状态——返回idle
表示没有活跃事务。
原因:在**串行化(Serializable)**隔离级别下,两个事务并发修改同一数据,PostgreSQL为了保证串行化,终止其中一个事务。
例子:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE products SET stock = 9 WHERE id = 100;
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE products SET stock = 8 WHERE id = 100;
解决:捕获这个错误,重试被终止的事务(比如在应用层加重试逻辑)。
预防:如果经常出现这个错误,可以降低隔离级别到“可重复读”(大多数场景不需要串行化)。
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
,阅读完整的文章:银行转账不白扣钱、电商下单不超卖,PostgreSQL事务的诀窍是啥?
vivo 蓝河操作系统 3 将于 11 月开启手表端公测,首批支持 vivo / iQOO WATCH 5
微星推出 MAG X870E TOMAHAWK MAX WIFI PZ 背插主板,可超外频