如果不加锁,在并发环境下会出现以下严重问题:
1. 脏读(Dirty Read)
-- 事务A更新但未提交
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 事务B此时读取到了未提交的数据
SELECT balance FROM accounts WHERE id = 1; -- 读到未提交的余额
-- 事务A回滚
ROLLBACK;2. 不可重复读(Non-repeatable Read)
// 同一个事务内,两次读取结果不一致
@Transactional
public void checkAccount() {
// 第一次读取
BigDecimal balance1 = accountDao.getBalance(userId); // 1000元
// 在此期间,另一个事务更新了数据
// UPDATE accounts SET balance = 500 WHERE id = userId;
// 第二次读取(在同一个事务内)
BigDecimal balance2 = accountDao.getBalance(userId); // 变成500元!
// balance1 != balance2 → 不可重复读
}3. 幻读(Phantom Read)
@Transactional
public void statisticalAnalysis() {
// 第一次查询
int count1 = orderDao.countTodayOrders(); // 100条
// 在此期间,另一个事务插入了新订单
// INSERT INTO orders ... (新增1条)
// 第二次查询
int count2 = orderDao.countTodayOrders(); // 变成101条!
// 同一事务内相同查询,结果集数量不同 → 幻读
}4. 丢失更新(Lost Update)
// 两个用户同时修改同一数据
public void concurrentUpdate() {
// 用户A和用户B同时读取
// 都读取到 stock = 10
// 用户A:stock = 10 - 1 = 9
// 用户B:stock = 10 - 2 = 8
// 后提交的覆盖先提交的 → 用户A的更新丢失!
// 最终结果可能是8(应该是7)
}5. 写倾斜(Write Skew)
-- 经典会议室预订场景
-- 规则:同一时间段只能有一个会议室被预订
-- 事务A检查:9:00-10:00时间段是否空闲
SELECT * FROM bookings
WHERE room_id = 1
AND time = '09:00-10:00';
-- 事务B检查:同一时间段是否空闲
SELECT * FROM bookings
WHERE room_id = 2
AND time = '09:00-10:00';
-- 两个事务都认为时间段空闲,同时插入 → 违反业务规则
INSERT INTO bookings (room_id, time) VALUES (1, '09:00-10:00');
INSERT INTO bookings (room_id, time) VALUES (2, '09:00-10:00');6. 具体场景示例
场景1:库存超卖
// 不加锁,100个并发请求购买最后10个库存
public boolean purchase(Long productId) {
Product product = productDao.selectById(productId);
if (product.getStock() > 0) {
// 多个线程同时通过检查
product.setStock(product.getStock() - 1);
productDao.updateById(product);
return true; // 结果:卖出100个,库存变-90!
}
return false;
}场景2:余额并发扣款
// 用户同时发起多次支付
public boolean pay(Long userId, BigDecimal amount) {
Account account = accountDao.selectById(userId);
if (account.getBalance().compareTo(amount) >= 0) {
// 并发时多个线程同时通过余额检查
account.setBalance(account.getBalance().subtract(amount));
accountDao.updateById(account);
return true; // 结果:100元余额被扣了3次50元
}
return false;
}7. 隔离级别与对应问题
8. 现实世界的后果
资金损失:用户重复提现、商户多发货
数据错乱:库存负数、余额错误
业务违规:超卖、超额预订
审计问题:账目对不上
客户投诉:订单状态混乱
关键结论:数据库操作不加锁就像在红灯时过马路——平时可能没事,但一旦出事就是灾难性的。在高并发系统中,要么加锁,要么用乐观锁/版本控制,没有第三条路。