最近在公司接到了一个工单,表象是运营人员在配置一个表单的时候执行一个接口请求,所有的日志和返回都是正确,但是 updateinsert 语句都没有执行,日志系统却都追踪到执行了此语句。更奇怪的是,这个接口的前半部分都执行成功,后半部分都失败了。如下中语句1、语句2、语句3、都成功了。语句4、语句5、语句6都失败了。

问题

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$parameters = [];
$db->select;//语句1
$db->insert;//语句2
$db->update;//语句3
$db->begin;
$re = $this->judge(parameters)
if($re == false){
return false;
}
$db->commit;
$db->update;//语句5
$db->insert;//语句6
return true;
function judge(parameters){
if ( $parameters[xx] = null){
$db->update;//语句4
return true;
}
}

排查

拿到这个问题按照经验来讲,就是通过几个方面来排查:

  1. 代码问题,是否有bug
  2. 是否启用了事务,执行失败导致回滚。
  3. 是否是因为数据库配置了主从,updateinsert 的主表,select 的从表,但是主从不一致。

随后和架构师以及运维沟通,排除了3,数据库日志都显示正常。那就去排查2,发现虽然代码中有一个事务,但是并不影响主流程的执行。1排查了半天,由于是老代码,且写代码的人离职了,所以并不能仔细的追踪到问题点。

于是就造了一些数据,把线上的拉到本地执行,发现了问题的真正所在:1+2一起造成了这次bug。

原因

在语句3之后开启了事务,参数 $parameters[xx] = null 的时候,加了一个判断,问题是在判断之后,直接返回了个true,也就是说此时开启了事务,随后所有的语句都会掉到这个事务中,由于一直没有commit,语句4和语句4以后的sql都得不到执行。在长时间没有执行的情况下,sql语句会自动回滚。相当于语句4和语句4以后的sql都没有执行。但是只从代码上看,语句5、语句6的确是执行到了。

所以 实际上,本次事故,的原因是有两个:

  • 第一是代码问题。
  • 第二是我们的日志系统没有追中到事务的执行,只是单纯的记录了sql的执行。

修复

直接修复代码即可。使用事务,一定要完整且闭合。