一.服务异常描述
微服务架构,根据日志定位到错误是在B服务的某个方法插入表数据时抛出的,错误代码是:
ORA-02291: 违反完整约束条件 (*) - 未找到父项关键字
伪代码如下:
@Transactional
public void serviceB(){
int update = xxxMapper.save(beanB);
}
数据库用的是Oracle,错误提示也很明显.
ORA-02291: 违反完整约束条件 (*) - 未找到父项关键字总体说说可能出现的原因:情况场景:表A中有个字段是外键,关联了表B中的某字段,再往表A插入数据时,会出现这种情况。可能原因:1.在往A表插入时,外键关联的字段在B表中必须有数据,如果B表中没有数据则又这种情况。 2.产生了外键环,就是B表中被外键关联的字段又关联了C表中的字段,而C中相应字段却没有数据,则产生这种情况。3.如果不是上两种情况,那么就是一个非常容易疏忽的问题:A中的外键字段和B中的被外键关联字段数据类型和长度不一致。特别是数据长度,必须要一致。第三种情况是最容易忽略的,希望大家注意。
然后通过PL/SQL查看benaB对应的表假设为table_b中的外键约束,发现错误提示的外键约束表假设为table_a,table_b中的外键假设为FK_tableA_tableB,约束关系为table_b(id_tableA)->table_a(id),而id_tableA对应的字段值是另一个服务A远程调用传过来的,假设A中的伪代码如下:
@Transactional(readOnly = false, rollbackFor = { Exception.class })
public void submitData(){
int update = xxxMapper.save(beanA);
HttpClient.post(B.serviceB(paramJson));
...
}
在A服务中,先进行beanA数据保存(beanA对应的表为table_a),保存成功id会自动会写到beanA中,然后在 HttpClient.post(B.serviceB(paramJson))会传递到过去.
二.问题排查
table_a和table_b位于同一个数据库下,schema相同,且在服务A和服务B中连接串相同.
A服务中明明保存成功了,值也传到位了,为什么还说找不到呢?
看到了@Transactional注解,想到了可能和隔离级别有关.
假设隔离级别为READ_COMMITTED,A服务中的保存成功了,但此时事务还未提交,然后到了B服务中在保存时会因为强外键约束,读取不到A中还未提交的数据报了错误.既然这样,我代码中设置下A服务submitData()方法的隔离级别:
@Transactional(readOnly = false, rollbackFor = { Exception.class },isolation = Isolation.READ_UNCOMMITTED)
public void submitData(){
int update = xxxMapper.save(beanA);
HttpClient.post(B.serviceB(paramJson));
...
}
我把隔离级别改成读未提交的,结果测试报出如下错误:
百度了下,原来oracle只支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别,且默认系统事务隔离级别是READ COMMITTED,也就是读已提交.其实想想也知道,这中即使支持也是不起作用的,毕竟是跨jvm了,涉及到分布式事务了.
三.解决办法
1.去掉A中方法的事务,同时为了防止A服务保存数据成功了,但B服务调用失败,产生脏数据,进行代码编程手动删除替代自动回滚操作.
2.去掉外键约束
另注:在这中场景下,传播机制是不起作用的.
考虑是否要上个分布式事务呢?