先查余额再扣钱,换什么隔离级别都不安全
SELECT balance FROM acct WHERE id = 1; -- 够 100
UPDATE acct SET balance = balance - 100 WHERE id = 1;两步之间,别人可能已经扣过了。调高隔离级别解决不了这个问题。两种正确写法:
-- 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,或者先锁父行