CodeGym /课程 /SQL SELF /处理事务时的常见错误

处理事务时的常见错误

SQL SELF
第 40 级, 课程 4
可用

这节课我们主要聊聊在用事务时经常会踩到的坑,以及怎么绕开它们。相信我,就算是最牛的 SQL 大佬,有时候也会忘记写 COMMIT!我们会给你一些建议,让事务出错变成小概率事件。

很遗憾(也许也是好事),数据库可不是魔法城堡,啥都能自动搞定。事务出错其实挺常见的,尤其是刚开始写代码的时候。我们来详细拆解一下。

忘记写 COMMIT 或 ROLLBACK

忘记结束事务,简直就是“经典老梗”。想象一下你在餐厅点了菜,结果服务员忘了给你结账。在 PostgreSQL 里,这就意味着数据库“卡”在事务状态,占着资源,还会锁住别的操作。

错误示例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 哎呀!我们忘了加 COMMIT 或 ROLLBACK。

当事务“卡住”时,资源锁可能会影响整张表。如果 DBA 发现情况不妙,他可能会用“强硬”手段结束它。但最好别让事情走到这一步。

怎么避免?

  • 每次都要明确结束事务:COMMIT 或 ROLLBACK。
  • 用一些客户端工具,它们会自动提醒你有挂起的事务。
  • 如果事务没结束你就重启了应用,数据库会自动 ROLLBACK,但这对系统状态来说不一定方便。

用了不合适的隔离级别

选隔离级别看起来很无聊,但其实它对防止各种奇怪问题很关键。比如你在重要的金融操作里用了 READ UNCOMMITTED,那就可能读到“脏数据”,这些数据后面可能会被撤销。

示例:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- 读取被其他事务修改的数据
SELECT balance FROM accounts WHERE account_id = 1;
-- 另一个事务执行了 ROLLBACK,你的数据就失效了。

怎么避免?

  • 先想清楚你的应用对数据有多重要。
  • 大多数场景用 READ COMMITTED,这样能避免“脏读”。
  • 如果特别怕数据被改或者出现幻读,就用更严格的 REPEATABLE READ、SERIALIZABLE。

事务冲突和锁

有时候两个或更多事务想同时改同一份数据。这时 PostgreSQL 会锁住其中一个,等另一个结束。这可能会导致 死锁(deadlock)。

错误示例:

-- 第一个事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

-- 第二个事务
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 等待第一个事务...

如果两个事务都占着对方需要的资源,就死锁了。PostgreSQL 会检测到死锁,结束其中一个事务并报错:

ERROR: deadlock detected

怎么避免?

  • 事务里的操作顺序要固定。
  • 尽量让事务执行得快一点,减少被锁的概率。
  • 只有真的需要时才用 SERIALIZABLE 隔离级别。

SAVEPOINT 用错了

SAVEPOINT 是个很棒的部分回滚工具,但用不好会让人一头雾水。比如你忘了释放保存点(RELEASE SAVEPOINT),就可能多出一些没必要的锁或者报错。

错误示例:

BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- 忘了释放 SAVEPOINT!

怎么避免?

  • 用完 SAVEPOINT 记得删掉它。
  • 别搞太多保存点,不然 SQL 会变得很难看懂。

事务和外部系统不兼容

想象一下,PostgreSQL 里的事务要和外部系统打交道:发通知、更新 API 等等。如果外部系统出问题,回滚就很麻烦了。

示例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 发送通知失败:email-server 没响应。
COMMIT; -- 数据改了,但通知没发出去。

怎么避免?

  • 尽量把和外部系统的操作隔离开。
  • 用中间表或者任务队列,来协调和外部系统的交互。

大事务导致的错误

大事务里有很多操作,更容易出错,比如被锁、超时、死锁啥的。

示例:

BEGIN;
-- 几千条更新操作
UPDATE orders SET status = 'completed' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- 可能会很慢。

怎么避免?

  • 把大事务拆成几个小事务。
  • 用批处理(batch)来更新数据。
  • 尽量减少单个事务里要改的数据量。

忘了检查错误

不是所有 SQL 语句在事务里都会成功。比如有一条操作报错,整个事务就挂了。

示例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- 错误:account_id 不存在。
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- 因为有错误,没执行。

怎么避免?

  • 每次都要检查每个操作的执行结果。
  • 在 SQL 或客户端代码里加上错误处理。

误解 ROLLBACK 的行为

很多开发者以为 ROLLBACK 能撤销所有更改,把一切恢复原样。其实 ROLLBACK 只对当前事务有效。

误区示例:

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- 错误!没用事务,这样不行。

怎么避免?

  • 记住:BEGIN 是好基友,没有它 ROLLBACK 就没用。
  • 所有关键操作都要放在事务里。
2
任务
SQL SELF, 第 40 级, 课程 4
已锁定
使用 `SAVEPOINT` 实现部分回滚
使用 `SAVEPOINT` 实现部分回滚
1
调查/小测验
事务隔离级别入门第 40 级,课程 4
不可用
事务隔离级别入门
事务隔离级别入门
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION