使用JDK1.8 parallelStream 引发的 Lock wait timeout exceeded; try restarting transaction

一、背景说明

在线上环境中数据库监控中,突然出现Lock wait timeout exceeded; try restarting transaction 错误,并伴随着大量数据库请求超时报警。

二、了解问题

  • 问题说明
update data_sku SET sku_str ='测试',modified = now() where id = 123
java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

该问题发生在Mysql数据库中Innodb 引擎表中,当Innodb 引擎表中出现行锁等待超时并且行锁等待超时,会在等待行锁进程中抛出
Lock wait timeout exceeded; try restarting transaction错误。

  • 数据库行锁配置

在mysql数据库中,innodb_lock_wait_timeout 该参数控制 Innodb 行锁等待的超时时间,可通过以下命令查询数据库配置的当前值。

SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout';
  • 问题场景

   1) 多台服务器同一时间请求数据库中同一条data_sku数据
   2) 同一个事务对同一条data_sku数据进行插入和修改操作
   3) 并发对一条data_sku进行插入或修改操作
   4) 在查询或修改语句中导致了锁表

三、排查问题

1) 借助mysql数据库工具排查问题

--正在执行的慢SQL记录线程
show  processlist
--当前运行的所有事务
mysql> SELECT * FROM information_schema.INNODB_TRX;
--当前出现的锁
mysql> SELECT * FROM information_schema.INNODB_LOCKs;
--锁等待的对应关系
mysql> SELECT * FROM information_schema.INNODB_LOCK_waits;

注:由于数据库中以上数据一切正常,未能实践中使用到

2) 使用排除法排查问题,根据报错sql语句,在项目中检索查找使用地方,最终确定如下调用代码:

List<MySku> skus = new ArrayList<>();
/**......**/
long failCount = skus.parallelStream()
		.mapToLong(o -> {
			try {
				return skuService.create(o);
			} catch (Throwable e) {
				return -1L;
			}
		})
		.filter(o -> o <= 0)
		.count();

parallelStream并行执行流操作,底层借助fork/join框架提供并发执行能力,非线程安全,并行流在遍历时无法绝对有序。

关于parallelStream的介绍详见:https://blog.csdn.net/darrensty/article/details/79283146

四、定位问题

1) 结合出错时间日志,发现运营人员多次进行了导入操作,由于前端按钮未做请求控制,最终导致运营人员使用同一个导入文件执行了多次导入操作。
2) 多次导入操作命中到不同服务器,而每个服务器又都使用parallelStream进行的插入操作,最终导致问题的产生

模拟问题重现:在本地环境将mySql数据库,SET  GLOBAL innodb_lock_wait_timeout=1; 将行锁等待的超时时间调低,开发工具类使用多线程调用以上skuService.create(o);方法,最终在测试环境重现问题,详细代码如下所示:

private  void saves(List<MySku> skus){
	for(int i=0;i<10;i++){
		new Thread(new Runnable() {
			@Override
			public void run() {
				long failCount = skus.parallelStream()
						.mapToLong(o -> {
							try {
								return skuService.create(o);
							} catch (Throwable e) {
								return -1L;
							}
						})
						.filter(o -> o <= 0)
						.count();
				System.out.println(failCount);
			}
		}).start();
	}
}

五、解决问题

1) 使用stream流式串行调用skuService.create(o)方法
2) 前端对导入按钮进行控制

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值