问题来源:
在某日下午,技术团队里的小伙伴反馈某业务系统数据库响应缓慢,通过查询数据也是需要等待好久才能响应。
问题已经反馈了,接下来需要定位分析下问题;
show processlist;
发现存在有较多的sleep进程且耗时较长。
为什么会产生这么多sleep呢?
刚开始是比较怀疑代码中存在慢查询、sleep语句的,于是进入监控平台,查近半小时内的慢查询日志,结果慢查询日志没有数据,在业务系统中也没找到人为的sleep语句。
这时候就比较奇怪了,没有慢查询、没有sleep语句,怎么会有那么多sleep进程呢?
前端应用业务还在正常使用,基本的查询和修改没有问题。
但是部分应用出现了MySQL server has gone away的提示。
难道这是产生锁,造成延迟了?
于是开始排查,
通过与技术小伙伴们沟通、review代码后 终于找到了问题,这里先看代码:
public function handle()
{
try
{
$params = $this->params;
#log 日志记录
if(!$params)
{
return false;
}
return $this->getAppService()->syncData($params);
} catch (\Exception|ParamException $ex)
{
Log::error('DelayGetWdOrderJob-exception', [$ex->getMessage(), $ex->getCode()]);
}
}
syncData 方法
try
{
DB::beginTransaction();
//检测重复逻辑
$wdCustomerId = '';#此处省略
if(!$wdCustomerId)
{
continue;
}
//滤重
$checkTradeId = '';#此处省略
if ($checkTradeId)
{
continue;
}
//update or insert 数据处理逻辑 ,此处省略
DB::commit();
}
catch (\Exception $ex)
{
DB::rollBack();
Log::error(__FUNCTION__.'-importException',[$ex->getMessage(),$ex->getCode()]);
throw new ParamException($ex->getMessage(),$ex->getCode());
}
这里的代码就比较有意思了,小伙伴在准备导入其他应用系统的数据到这个业务系统循环处理数据的时候 ,在开启了事务 ,校验重复数据逻辑中直接continue 掉了,并没有对事务进行及时的commit或者关闭 。
数据库开启了事务,但是没有commit,并且据执行异常日志分享 还存在SQL字段值格式错误。事务没有提交,后面的语句还在等待执行,也就产生了锁,超时结束。
再者就是 这部分代码是被嵌套了在了一个循环当中,也就是说,这是在循环中提交事务。
MySQL 事务每提交一次 就会写一次redo log,假定这里循环1000次,就要有1000次重做日志,性能是比较慢的。(解决办法后面在聊)
至此,已然找到问题,于是本文这里对此做一个思考总结:
关于MySQL事务使用的一些注意点
在MySQL innodb 存储引擎中,InnoDB存储引擎默认是自动提交的(有超时时间设置)。即便有这么一层机制,但是实际使用当中,还是要求对事务尽快地释放,不能有长时间的事务。
写在后面:
在MySQL 5.5版本后,information_schema 库中增加了三个关于锁的表,分别如下:
innodb_trx:当前运行的所有事务
innodb_locks:当前出现的锁
innodb_lock_waits:锁等待的对应关系
该问题可以直接从这个几张表入手,找到了一直没有提交的只读事务,然后 kill thread id,最后确认只读事物是否被干掉了就OK了
总结反思:
mysql 提供了事务支持,但是有些不良好的事务使用习惯需要我们规避。
例如:
1.在循环中提交事务。
例如在循环插入一组数据时,循环多次开启多次事务,性能慢。
解决办法是可以 数据分组 组合 开启事务 并提交。
2.使用事务的自动提交。
这个不太推荐,开发人员养好的习惯更合适。
3.使用自动回滚。
4.使用长事务。
长事务是指执行时间较长的事务,执行数据量大,时间长,如果中间有数据库、操作系统、硬件等故障发生时,重新开始事务的代价就会很大。建议解决方案就是 转为 小批量的事务来进行处理,当事务发生错误是,可以回滚一部分数据,然后接着上次已经完成的事务继续执行。