PostgreSQL从入门到进阶
🏠 首页
  • 开始学习

    • PostgreSQL介绍
    • 安装配置
  • 基础教程

    • 数据库与表
    • 数据类型
    • CRUD操作
    • WHERE条件
  • 进阶查询

    • 联表查询
    • 聚合函数
    • 子查询
    • 视图
  • 性能优化

    • 索引优化
    • 查询优化
    • EXPLAIN分析
  • 事务处理
  • 触发器
  • 存储过程
  • JSON数据
  • 博客系统
  • 用户管理系统
  • 最佳实践
🔗 编程指南
🏠 首页
  • 开始学习

    • PostgreSQL介绍
    • 安装配置
  • 基础教程

    • 数据库与表
    • 数据类型
    • CRUD操作
    • WHERE条件
  • 进阶查询

    • 联表查询
    • 聚合函数
    • 子查询
    • 视图
  • 性能优化

    • 索引优化
    • 查询优化
    • EXPLAIN分析
  • 事务处理
  • 触发器
  • 存储过程
  • JSON数据
  • 博客系统
  • 用户管理系统
  • 最佳实践
🔗 编程指南
  • 开始之前

    • 教程介绍
    • 什么是PostgreSQL?
    • 安装PostgreSQL
  • 基础入门

    • 数据库和表的概念
    • 数据类型详解
    • 增删改查操作(CRUD)
    • 查询条件WHERE
  • 进阶查询

    • 多表联查JOIN
    • 聚合函数
    • 子查询
    • 视图VIEW
  • 性能优化

    • 索引详解
    • 查询优化
    • 执行计划分析
  • 高级特性

    • 事务处理
    • 触发器
    • 存储过程
    • JSON数据
  • 实战项目

    • 实战项目:博客系统
    • 实战项目:用户管理系统
    • 最佳实践

事务处理

嗨!长安来讲事务了!事务保证数据的一致性和可靠性!

🎯 什么是事务?

事务是一组SQL操作,要么全部成功,要么全部失败。

经典例子:转账

-- 长安给小明转账100元
BEGIN;

-- 1. 长安账户减100
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;

-- 2. 小明账户加100
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;

-- 提交事务
COMMIT;

如果中间出错(比如断电),两个操作会全部回滚,不会出现钱丢失的情况!

📝 事务的基本操作

BEGIN - 开始事务

BEGIN;
-- 或者
START TRANSACTION;

COMMIT - 提交事务

BEGIN;
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- 确认提交

ROLLBACK - 回滚事务

BEGIN;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 哎呀,发现错了!
ROLLBACK;  -- 撤销所有操作

SAVEPOINT - 保存点

BEGIN;

INSERT INTO logs VALUES (1, 'log1');

SAVEPOINT sp1;  -- 创建保存点

INSERT INTO logs VALUES (2, 'log2');

-- 出错了,只回滚到保存点
ROLLBACK TO SAVEPOINT sp1;

INSERT INTO logs VALUES (3, 'log3');

COMMIT;  -- 提交:log1和log3会保存,log2不会

🎯 ACID特性

事务的四大特性:

A - 原子性(Atomicity)

要么全部成功,要么全部失败:

BEGIN;
INSERT INTO orders VALUES (1, 100);
INSERT INTO order_items VALUES (1, 1, 'Product A');
-- 两条都成功才提交
COMMIT;

C - 一致性(Consistency)

数据始终保持一致:

-- 总金额始终守恒
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 前后总金额一致

I - 隔离性(Isolation)

多个事务互不干扰:

-- 事务A
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 100
-- 此时事务B也在操作...
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
COMMIT;

D - 持久性(Durability)

提交后永久保存:

BEGIN;
INSERT INTO logs VALUES (1, 'important data');
COMMIT;
-- 即使服务器崩溃,这条数据也不会丢失

🔒 隔离级别

PostgreSQL支持4种隔离级别:

1. Read Uncommitted(读未提交)

PostgreSQL不支持,最低级别是Read Committed。

2. Read Committed(读已提交) - 默认

只能读到已提交的数据:

-- 事务A
BEGIN;
UPDATE products SET price = 200 WHERE id = 1;
-- 还没COMMIT

-- 事务B
BEGIN;
SELECT price FROM products WHERE id = 1;
-- 读到的是旧值(100),读不到事务A未提交的200

3. Repeatable Read(可重复读)

在事务内,多次读取结果一致:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;

SELECT price FROM products WHERE id = 1;  -- 100

-- 此时其他事务修改了价格

SELECT price FROM products WHERE id = 1;  -- 还是100!
COMMIT;

4. Serializable(可串行化)

最严格,完全隔离:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- 完全隔离,就像一个接一个执行
COMMIT;

💡 实战案例

案例1:电商下单

BEGIN;

-- 1. 创建订单
INSERT INTO orders (user_id, total_amount, status) 
VALUES (1, 299.00, 'pending')
RETURNING id INTO order_id;

-- 2. 添加订单明细
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (order_id, 101, 1, 299.00);

-- 3. 减少库存
UPDATE products 
SET stock = stock - 1 
WHERE id = 101 AND stock > 0;

-- 检查是否减少成功
IF FOUND THEN
    COMMIT;
ELSE
    ROLLBACK;  -- 库存不足,回滚
END IF;

案例2:积分系统

BEGIN;

-- 用户消费,增加积分
UPDATE users SET points = points + 100 WHERE id = 1;

-- 记录积分变动
INSERT INTO point_logs (user_id, change, reason, created_at)
VALUES (1, 100, '消费奖励', NOW());

COMMIT;

案例3:批量导入

BEGIN;

-- 导入数据
COPY users FROM '/tmp/users.csv' CSV;

-- 验证数据
SELECT COUNT(*) FROM users WHERE email IS NULL;

-- 如果有问题就回滚
-- ROLLBACK;

-- 没问题就提交
COMMIT;

🎮 并发控制

悲观锁 - FOR UPDATE

-- 悲观锁:先锁定再操作
BEGIN;

SELECT * FROM products 
WHERE id = 1 
FOR UPDATE;  -- 锁定这一行

-- 其他事务必须等待
UPDATE products SET stock = stock - 1 WHERE id = 1;

COMMIT;  -- 释放锁

乐观锁 - 版本号

-- 添加版本号字段
ALTER TABLE products ADD COLUMN version INT DEFAULT 1;

-- 读取数据和版本号
SELECT id, stock, version FROM products WHERE id = 1;
-- 假设得到:stock=10, version=5

-- 更新时检查版本号
UPDATE products 
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5;  -- 版本号匹配才更新

-- 如果UPDATE影响行数为0,说明有并发冲突,需要重试

⚠️ 死锁

两个事务互相等待对方释放锁:

-- 事务A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 等待获取id=2的锁...
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 事务B(同时进行)
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
-- 等待获取id=1的锁...
UPDATE accounts SET balance = balance + 50 WHERE id = 1;

-- 死锁!PostgreSQL会自动检测并中止一个事务

避免死锁的方法:

  1. 按相同顺序访问资源
  2. 尽量缩短事务时间
  3. 使用较低的隔离级别

📚 下一步

掌握了事务,你的数据就安全多了!

继续学习触发器!


长安的经验:

事务使用要点:

  1. 需要一致性的操作必须用事务
  2. 事务要尽量短,长事务会影响性能
  3. 避免在事务中执行慢查询
  4. 生产环境用Read Committed就够了
  5. 记得COMMIT或ROLLBACK

事务是数据安全的保障!💪

有问题来编程指南!

最近更新: 2025/12/1 18:21
Next
触发器