事务处理
嗨!长安来讲事务了!事务保证数据的一致性和可靠性!
🎯 什么是事务?
事务是一组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会自动检测并中止一个事务
避免死锁的方法:
- 按相同顺序访问资源
- 尽量缩短事务时间
- 使用较低的隔离级别
📚 下一步
掌握了事务,你的数据就安全多了!
继续学习触发器!
长安的经验:
事务使用要点:
- 需要一致性的操作必须用事务
- 事务要尽量短,长事务会影响性能
- 避免在事务中执行慢查询
- 生产环境用Read Committed就够了
- 记得COMMIT或ROLLBACK
事务是数据安全的保障!💪
有问题来编程指南!