# 并发下数据库commit提交时间突然变长
# 背景
项目调用其他项目接口, 保存大批量订单数据, 接口拿到批量订单数据, 直接拆成单个订单, 发到MQ中.
MQ并发消费场景下, 项目中获取流水号的接口提交事务的时间突然变长.
# 故事主线
故事的主人公是
获取流水号接口, 在并发消费场景下出现了性能问题, 下面先说下此接口的实现
# 获取流水号接口实现逻辑
接口需求: 调用此接口能获取到对应业务的流水号, 流水号为固定步长递增.
- 接口参数: 流水号类型(根据业务不同设定不同枚举)
- 接口方法上加如下注解, 保证事务独立.
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
- 流水号数据表设计
CREATE TABLE `sys_sequence` (
`seq_name` varchar(50) NOT NULL COMMENT '名称(对应枚举value)',
`seq_prefix` varchar(10) DEFAULT NULL COMMENT '前缀',
`current_value` bigint(20) NOT NULL COMMENT '当前值',
`increment_val` int(11) DEFAULT '1' COMMENT '步长',
`need_suffix` char(2) DEFAULT '0' COMMENT '0不需要后缀,1需要一个4位数随机数的后缀',
`c_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(数据库自己维护)',
`u_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间(数据库自己维护)',
PRIMARY KEY (`seq_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 实现细节
- 查询出当前
业务流水号类型的数据, 此处用for update给数据加锁 - 根据
当前值、步长和是否需要后缀计算出新值 - 更新数据表, 当前值=当前值+步长
- 查询出当前
# 场景说明
在50-100并发的情况下, 此接口耗时很久, 久的长达20秒, 快的也要5秒.
根据实现逻辑推断, 大部分并发线程都卡在了此处的for update上, 虽然此接口作为新起的事务提交了, 但却影响了整体的性能.
# 解决方案
将获取流水号接口的压力从mysql改为redis.
当需要流水号时, 先去从redis中获取, 如果redis没有流水号, 则从mysql中批量获取并生成到redis中. 但这种方案在并发时, 并且redis中没有号时去拿号, 会发生较长的等待, 导致性能降低. 有两个方案解决, 可以使用一个daemon线程或者定时线程去维护redis中的号, 当其低于某个特定值时就去生成流水号存入redis中, 或者在每次拿号时去判断如果流水号数量少于某个值, 则异步去mysql中批量获取并生成到redis中, 只是此时需要保证只有一个线程在生成号.
以上方案需要注意线程安全, 并发安全.
← 学会查看binlog文件内容 开篇 →