MySQL

先查余额再扣钱,换什么隔离级别都不安全

sql
SELECT balance FROM acct WHERE id = 1;          -- 够 100
UPDATE acct SET balance = balance - 100 WHERE id = 1;

两步之间,别人可能已经扣过了。调高隔离级别解决不了这个问题。两种正确写法:

sql
-- 1. 一条带条件的 UPDATE,看影响行数是不是 1
UPDATE acct SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

-- 2. 先锁住再判断
BEGIN;
SELECT balance FROM acct WHERE id = 1 FOR UPDATE;
-- 判断够不够,再 UPDATE
COMMIT;

更一般的原则:隔离级别是整个库的配置,不按业务调。业务正确性靠这些:

  • 防重复:唯一索引
  • 防并发改错:带条件的 UPDATE,或者先锁父行
esc