*文中表和字段均为虚构
一、问题现象描述
1、某次需求中,给tb_student表增加字段nick_name(NOT NULL DEFAULT ‘’),并且只有在这条链路才会写nick_name:
INSERT INTO tb_student
(name
,nick_name
,gender
,status
) VALUES (‘jack’,‘jjjjack’,1,1);
之前原有链路(后文称链路2)均为
INSERT INTO tb_student
(name
,gender
,status
) VALUES (‘ming’,1,1);
id | name | nick_name | gender | status |
---|---|---|---|---|
1 | jack | jjjjack | 1 | 1 |
3 | ming | 1 | 1 |
2、测试无问题,线上观察无问题
3、同事在做其他需求时,跑单测时,发现链路2必现报错(详细异常堆栈省略):
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
......
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'nick_name' cannot be null
......
但是如果启动服务,直接请求http接口,则无问题
4、某天线上也出现类似报错,但是之前一礼拜包括之后一礼拜再未出现报错(能查到日志)。
因此以上可以总结为两个问题:
- 问题1:http调用和单测现象不一致
- 问题2:线上偶发报错
二、问题排查
就这个诡异的现象,让排查无从下手。
1、异常定位
首先定位异常发生代码,发现流程为:
1、生成student数据,初始状态为1,插入tb_student表
2、事务中更新tb_course表、tb_teacher表。
3、更新步骤1中student状态为2,并update到tb_student表。上述异常,就是发生在这一步。
public void deal() {
Student student = Student.builder()
.name("ming")
.gender(1)
.status(1)
.build();
studentRepo.save(student);
studentRepo.flush();
try {
transactionTemplate.execute(t -> {
QCourse qCourse = QCourse.course;
jpaQueryFactory.update(qCourse);
QTeacher qTeacher = QTeacher.teacher;
jpaQueryFactory.update(qTeacher);
return t;
});
} catch (Exception e) {
} finally {
student.setStatus(2);
studentRepo.save(student);
studentRepo.flush();