删除关联MSRun记录后仍触发Django RestrictedError的问题排查
问题背景
你遇到了一个非常矛盾的问题:明明已经删除了serum_sample关联的所有MSRun记录(count()返回0),但尝试删除Sample实例时,却触发了针对其他Sample的MSRun的RestrictedError。结合你提到的双数据库配置、父类重写delete()方法以及新增的维护字段传播逻辑,问题大概率出在自定义删除逻辑或数据库操作一致性上。
排查与解决方案
1. 显式指定数据库,排除跨库操作问题
虽然你确认serum_sample来自default数据库,但关联查询和删除操作可能因为数据库路由配置,意外使用了其他库(比如validation)。显式指定数据库执行操作,确保所有操作都在目标库完成:
# 显式使用default数据库删除关联的MSRun serum_sample.msruns.using("default").all().delete() # 再次确认default库中该Sample的MSRun数量 print(f"THIS SHOULD BE 0: {serum_sample.msruns.using('default').count()}") # 显式指定default库删除Sample serum_sample.delete(using="default")
2. 绕过自定义删除逻辑,验证核心功能
你的Sample继承的MaintainedModel和HierCachedModel都重写了delete()方法,新增的DFS传播逻辑可能存在隐式问题。尝试直接调用原生Django模型的delete()方法,排除自定义逻辑的干扰:
from django.db.models import Model # 跳过父类的自定义delete逻辑,直接调用原生删除 Model.delete(serum_sample, using="default")
如果这一步成功,说明问题出在自定义父类的delete()方法中,需要进一步排查:
- 检查
MaintainedModel的call_dfs_related_updaters():确认它是否在DFS遍历中误操作了其他MSRun的关联字段,比如意外修改了无关MSRun的sample属性,导致删除serum_sample时触发错误检查。 - 检查
HierCachedModel的delete_related_caches():确认缓存删除逻辑是否触发了数据库层面的意外操作,比如修改了关联记录的外键。
3. 验证报错中MSRun的实际关联对象
报错中提到的MSRun(如BAT-xz971的运行记录)理论上和serum_sample无关,但需要确认它们的sample外键是否真的指向其他Sample:
# 替换为报错中MSRun的实际ID msrun = MSRun.objects.using("default").get(id=<<MSRun_ID>>) print(f"MSRun关联的Sample: {msrun.sample}, 当前要删除的Sample: {serum_sample}") print(f"ID是否相同: {msrun.sample.id == serum_sample.id}")
如果输出显示这些MSRun确实关联到serum_sample,那说明之前的msruns.all().delete()操作没有覆盖所有关联记录(比如存在未被msruns反向关联涵盖的其他外键),需要重新检查模型定义。
4. 检查事务一致性
单元测试默认运行在事务中,可能存在删除操作未即时提交的情况,导致数据库层面仍存在关联记录。尝试手动提交事务后再删除Sample:
from django.db import transaction serum_sample.msruns.using("default").all().delete() # 手动提交事务,确保删除操作生效 transaction.commit() print(f"THIS SHOULD BE 0: {serum_sample.msruns.using('default').count()}") serum_sample.delete(using="default")
注意:单元测试中手动提交事务可能影响后续测试,建议仅在排查时使用。
5. 排查维护字段传播逻辑
你提到新增了向关联子模型传播维护字段变更的功能,且采用DFS顺序。需要检查该逻辑是否在delete()流程中意外触发了save()操作,导致无关MSRun的外键约束检查被误触发。比如,call_dfs_related_updaters()是否遍历了所有MSRun(而非当前Sample关联的),并调用了它们的save()方法,进而触发了不必要的外键检查?
总结
最可能的原因是自定义delete()逻辑中的DFS传播操作干扰了外键约束检查,或者数据库操作的一致性问题(比如未指定数据库导致操作在错误库执行)。优先尝试绕过自定义删除逻辑和显式指定数据库,定位问题根源后再修复自定义逻辑中的bug。
内容的提问来源于stack exchange,提问作者hepcat72

