在高并发业务场景中,MySQL的事务是保障数据一致性的核心手段,但如果使用不当,很容易出现超卖、重复扣款、死锁等问题。合理设计事务的结构、选择合适的隔离级别、控制锁的范围,是应对高并发事务的关键。

高并发下事务的核心原则
高并发场景下的事务设计需要遵循三个核心原则:尽可能缩短事务执行时间、缩小事务操作的数据范围、避免事务中出现非数据库操作。事务执行时间越长,持有的锁时间就越久,其他事务等待的概率就越高,进而引发性能瓶颈。
事务隔离级别的选择
MySQL支持四种事务隔离级别,不同级别对并发的影响差异很大,需要根据业务场景选择:
- 读未提交:性能最高但会出现脏读、不可重复读、幻读,几乎不会在高并发业务中使用
- 读已提交:解决了脏读问题,适合大多数读多写少的业务,是很多互联网项目的默认选择
- 可重复读:MySQL默认隔离级别,解决了不可重复读问题,通过间隙锁解决部分幻读问题,适合对数据一致性要求较高的场景
- 串行化:完全串行执行事务,性能最差,只适合对一致性要求极高的特殊场景
可以通过如下语句查看和设置当前会话的隔离级别:
-- 查看当前事务隔离级别 SELECT @@transaction_isolation; -- 设置当前会话为读已提交隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
锁机制的正确使用
高并发下事务的锁使用需要精准控制,避免不必要的锁竞争:
- 优先使用行锁而非表锁,InnoDB引擎默认支持行锁,只有未命中索引的更新操作才会升级为表锁
- 更新数据时使用
SELECT ... FOR UPDATE加排他锁,避免先查询再更新的并发问题 - 避免长事务持有锁,事务中不要加入接口调用、文件读写等耗时操作
下面是典型的库存扣减事务示例,通过行锁保障高并发下的库存准确性:
-- 开启事务 START TRANSACTION; -- 查询并锁定库存行,id是主键索引,只会锁对应的行 SELECT stock FROM product WHERE id = 1 FOR UPDATE; -- 判断库存是否充足,这里假设查询到的stock大于0 UPDATE product SET stock = stock - 1 WHERE id = 1 AND stock > 0; -- 提交事务 COMMIT;
高并发事务设计实践
控制事务粒度
事务的操作范围要尽可能小,只把需要保证一致性的数据库操作放在事务中。比如下单场景中,只需要把库存扣减、订单创建、订单明细插入放在事务中,用户积分更新、消息通知等操作可以放到事务外异步处理。
避免死锁
死锁是高并发事务的常见问题,可以通过以下方式规避:
- 所有事务按照相同的顺序访问表和行,比如都先操作订单表再操作库存表
- 设置合理的事务超时时间,避免事务长时间等待
- 尽量使用主键或者唯一索引作为更新条件,减少锁的范围
代码示例(Java + MyBatis)
下面是Spring框架下结合MyBatis的事务使用示例,通过@Transactional注解管理事务:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final ProductMapper productMapper;
private final OrderMapper orderMapper;
public OrderService(ProductMapper productMapper, OrderMapper orderMapper) {
this.productMapper = productMapper;
this.orderMapper = orderMapper;
}
// 开启事务,设置超时时间为3秒,隔离级别为读已提交
@Transactional(timeout = 3, isolation = Isolation.READ_COMMITTED)
public void createOrder(int productId, int buyNum) {
// 查询并锁定库存
Product product = productMapper.selectForUpdate(productId);
if (product.getStock() < buyNum) {
throw new RuntimeException("库存不足");
}
// 扣减库存
productMapper.reduceStock(productId, buyNum);
// 创建订单
Order order = new Order();
order.setProductId(productId);
order.setBuyNum(buyNum);
orderMapper.insert(order);
}
}
对应的ProductMapper中锁定库存的SQL实现:
<select id="selectForUpdate" resultType="Product">
SELECT id, stock FROM product WHERE id = #{productId} FOR UPDATE
</select>
<update id="reduceStock">
UPDATE product SET stock = stock - #{buyNum} WHERE id = #{productId} AND stock >= #{buyNum}
</update>
常见问题排查
如果出现高并发下事务相关的性能问题,可以通过以下方式排查:
- 查看
SHOW ENGINE INNODB STATUS的输出,分析死锁日志和锁等待情况 - 检查慢查询日志,看是否有长事务或者未命中索引的更新语句
- 监控事务的提交和回滚频率,过高的回滚率说明事务设计存在问题