Django重构:如何安全移除模型同时保留对应数据库表数据?
方案评估
你的整体思路方向是可行的,但存在几个容易踩的隐性坑,部分细节设计有缺陷,直接上线可能出问题。
现有方案的合理点
- 先拦截旧调用再改模型的思路是对的,能在上线前尽可能揪出漏改的旧逻辑,避免静默bug
- 借助Django原生的重命名迁移保留存量表的逻辑成立,正常操作不会删除存量数据
未考虑到的潜在隐患
- assert拦截完全不可靠
Python运行时加上-O优化参数时,全局所有assert语句会被直接剥离,到时候你写的拦截逻辑会直接失效,旧代码调用时不会触发任何告警,只会拿到不符合预期的返回值,排查难度极高。且assert抛出的通用AssertionError没有明确标识,很难快速定位到是调用了废弃接口的问题。 - 分步改字段和模型名容易生成错误迁移
先改related_name再重命名模型的操作,会让Django的迁移检测器两次扫描模型变更,很容易把外键关联识别成新增/删除字段,要是迁移审核不仔细,误执行了删字段的SQL会直接破坏表内的关联数据。 - 重命名模型默认会改数据库表名
Django默认表名生成规则是应用名_模型名小写,如果不给重命名后的模型显式指定原表名,生成的RenameModel迁移会自动执行ALTER TABLE 原表 RENAME TO 新表名的SQL。虽然数据不会丢失,但如果有离线脚本、数据对账逻辑直接写死查原表名,会直接报表不存在的错误。 - property拦截覆盖不到ORM隐式查询场景
你加的类属性只能拦截prs_instance.prs_blocks这种直接实例属性访问的场景,像PRS2.objects.filter(prs_blocks__xxx=xxx)、prefetch_related('prs_blocks')、order_by('prs_blocks__create_time')这类拼SQL的跨表查询,根本不会走Python层的property逻辑,拦截完全失效,上线后会直接抛关联不存在的报错。
调整后的稳妥操作步骤
- 第一步:零数据库变更先做调用拦截
不要先动原PRSblock模型的任何字段配置,直接在PRS2模型里加同名属性拦截访问,不要用assert,抛明确的异常即可,这一步不需要生成迁移,没有任何数据库风险:
加完之后跑全量测试、走一遍核心业务链路,把所有触发这个报错的旧调用全部替换成新逻辑。@property def prs_blocks(self): raise RuntimeError("prs_blocks关联已废弃,请勿调用,存量数据请直接查询原jobs_prsblock表") - 第二步:清理隐式查询调用
全局搜索代码里所有在filter、prefetch_related、order_by、values等ORM方法中用到prs_blocks的场景,全部替换为新实现的逻辑,这部分是property拦不住的,必须靠人工扫描+测试覆盖兜底。 - 第三步:重命名模型并锁死原表
确认所有旧调用清理干净后,再将模型重命名为符合大驼峰命名规范的ObsoletePRSblock(不要用obsolete_PRSblock这种不符合Python类命名规范的写法),把外键的related_name改成obsolete_prs_blocks,同时在模型Meta里显式指定原表名,禁止Django修改表名:
执行class ObsoletePRSblock(models.Model): PRS = models.ForeignKey('jobs.PRS2', models.CASCADE, related_name='obsolete_prs_blocks') class Meta: db_table = 'jobs_prsblock' # 强制绑定原表名,迁移不会执行改表名操作makemigrations时确认是模型重命名操作,打开生成的迁移文件检查,确认只有RenameModel操作,没有删表、删字段、改表名的SQL,再执行迁移即可。 - 可选兜底:给
ObsoletePRSblock加个自定义管理器,重写create、update、delete方法,直接抛错,防止后续有同事误写这个废弃表的数据。
注意:迁移上线前一定要在测试环境验证所有涉及PRS2查询的接口,确认没有残留的旧关联调用,再发布生产。
内容的提问来源于stack exchange,提问作者nigel222
相关产品推荐
相关产品推荐

