并发环境下的扣费和充值操作可能导致金额不一致,主要原因包括: - 竞态条件(Race Condition):多个操作同时读取和修改同一账户余额 - 非原子性操作:读取-计算-写入过程被其他操作打断 - 数据库隔离级别不足
乐观锁:
-- 更新时检查版本号或时间戳
UPDATE accounts
SET balance = balance + :amount,
version = version + 1
WHERE account_id = :id AND version = :old_version
悲观锁:
-- 查询时加锁
SELECT * FROM accounts WHERE account_id = :id FOR UPDATE
-- 然后执行更新操作
UPDATE accounts SET balance = balance + :amount WHERE account_id = :id
分布式锁:
# 使用Redis实现分布式锁
def update_balance(account_id, amount):
lock_key = f"account_lock:{account_id}"
with redis.lock(lock_key, timeout=10):
# 获取当前余额
balance = get_balance(account_id)
# 更新余额
set_balance(account_id, balance + amount)
队列串行化处理: - 将所有资金变动操作放入消息队列 - 单线程消费队列消息,保证顺序处理
补偿事务模式: 1. 记录操作前的余额快照 2. 执行操作 3. 验证结果,不一致时回滚并重试
TCC模式(Try-Confirm-Cancel):
// Try阶段:预留资源
boolean tryDebit(Account account, BigDecimal amount) {
if (account.getBalance() >= amount) {
account.setFrozenAmount(account.getFrozenAmount() + amount);
return true;
}
return false;
}
// Confirm阶段:确认操作
void confirmDebit(Account account, BigDecimal amount) {
account.setBalance(account.getBalance() - amount);
account.setFrozenAmount(account.getFrozenAmount() - amount);
}
// Cancel阶段:取消操作
void cancelDebit(Account account, BigDecimal amount) {
account.setFrozenAmount(account.getFrozenAmount() - amount);
}
余额流水表设计:
CREATE TABLE account_transactions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
account_id BIGINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
transaction_type ENUM('DEPOSIT', 'WITHDRAW') NOT NULL,
before_balance DECIMAL(18,2) NOT NULL,
after_balance DECIMAL(18,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts(id)
);
计算余额时从流水表汇总:
SELECT SUM(
CASE
WHEN transaction_type = 'DEPOSIT' THEN amount
WHEN transaction_type = 'WITHDRAW' THEN -amount
END
) FROM account_transactions WHERE account_id = :account_id
选择哪种方案取决于您的具体业务场景、系统架构和性能要求。