一个常见的问题是:“当应用程序代码需要数据库模式发生变化时,我应该在数据库模式更改之前、之后还是同时修改应用程序代码?”
实际上,无论是我们的应用程序还是数据库,它们都不是孤立的。在几乎所有情况下,你都不应该将数据库模式变更和应用程序代码变更耦合在一起。尽管同时发布两者可能看起来是个好主意,但通常会为你和你的用户带来很多痛苦。以下是五个主要原因:


风险:同时部署两个关键系统(例如数据库和应用程序)的更改会使出现问题的风险加倍。 部署问题:应用程序代码和数据库模式的更改无法原子地同时部署。如果它们相互依赖同时发布,应用程序在其中一个较慢完成时可能会短暂错误。

迁移时间:随着数据量增长,数据迁移可能变得更耗时。从30秒到几小时,甚至可能需要一天以上!你不希望应用程序部署因为迁移时间被阻塞。

阻塞开发流程:如果数据库模式更改出现问题并且与应用程序代码耦合,那么应用程序的部署可能会被阻塞。一项更改可能让生产环境的发布完全停滞,直到问题得到解决。

最佳实践:将两者分离迫使你遵守数据库的最佳实践,确保向后兼容的变更。本篇博客将讨论这一点。
那么,当你的应用程序代码需要数据库模式变更时,你应该如何处理呢?
这篇博客将回答这个问题,并拆分出需要遵循的步骤,帮你安全地改变数据库模式,同时确保用户不会遇到停机或其他干扰。一点建议:这个过程第一次尝试时可能显得比较复杂,但经过练习后,你就能熟练地快速应对,满怀信心地进行操作。


一些背景知识

虽然PlanetScale可以帮助你安全地实施模式变更,但以下模式可以应用于任何关系数据库的模式变更。


数据库模式更改的不同类型

一些常见的数据库模式更改类型包括:

  • 添加一个表或视图
  • 添加一个列
  • 修改现有的列、表或视图
  • 删除现有的列、表或视图

通常,添加表、列或视图是低风险的,这只要求在你的应用程序代码使用这些变更之前先去部署数据库模式的变更即可。
而更改或删除列或表则风险较高。这时向后兼容变更十分重要。最常用的模式是“扩展(Expand)”、“迁移(Migrate)”、“收缩(Contract)”。你可能会见到其他类似的名称,比如“并行变更”或“向后兼容变更”。我更喜欢“扩展、迁移和收缩”,因为它能形象地描述操作步骤。让我们来具体解释一下。


扩展、迁移和收缩模式

只要操作涉及生产环境中应用程序使用的模式,那么都应该使用向后兼容变更。这可以确保在流程的任何阶段,你都能够回滚而不会丢失数据或对用户产生重大中断。这大大降低了风险,并使你能够更加快速和自信地进行迭代。
例如,当你需要:

  • 重命名一个现有的列或表
  • 改变现有列的数据类型
  • 拆分现有列或表的数据,或对它们进行其他修改

如果只是添加一个列或表而不影响现有的模式,则无需遵循这个模式。
以下是一张帮助你理解这一模式的示意图,其中列出了各步骤中应用程序代码和数据库模式发生变化的位置:


模式拆分步骤详解

扩展(Expand)

第1步 – 扩展现有数据库模式

第一步是对数据库模式进行扩展。根据应用程序需求,你可以创建新的列或表。通过具体操作你可以做到以下几点:

  • 进行多个小型增量变更,而不是一次性进行重大变更,因为小型变更更安全。
  • 确保新添加的列具有“Nullable”(可为空)或默认值,以避免潜在的数据库错误。

你可以在本地测试这些变更,或者使用一个数据库分支完成测试后,部署到生产环境中。

第2步 – 扩展应用程序代码

目标:读写到新的列或表,同时继续读写到老的列或表。
在完成数据库模式扩展后,接下来需要更新应用程序代码,使其能够同时写入到旧的和新的模式中。这样做的原因是,你需要确保应用程序在写入新的模式时不会发生错误。如果需要更改数据存储方式(例如,将用户名改为存储用户ID),应用程序会将旧格式数据写入旧列,并将新格式数据写入新列或表。
在这一步完成后,即使有问题出现,应用程序仍然能够安全地写入旧列,不会对用户造成影响。部署这一步的应用程序代码后,应用程序的行为应该和之前保持一致。


迁移(Migrate)

第3步 – 数据迁移

从这一阶段开始,你需要处理之前写入旧模式的历史数据,在新列或表上进行数据填充。
需要编写数据迁移脚本,具体情况有以下两种:

  • 如果对数据进行了任何更改(例如对字符串进行拆分或格式调整),需要在迁移脚本中包含这些操作逻辑。
  • 如果只是将旧数据移动到新结构,不涉及任何变更,则直接插入不经过变异的数据即可。

如果有大量数据迁移,可以考虑通过后台任务分阶段完成迁移,以避免对生产数据库性能或用户体验造成影响。
迁移完成后,请进行一些测试,检查数据是否准确、完整,并验证它们是否可以安全投入使用。


第4步 – 迁移应用程序代码

在确认数据迁移完成并且新结构正确后,你可以更新应用程序代码只从新模式中读取数据。
在部署之前,这是最后一次确认数据是否完整以及新模式是否为“生产就绪”的机会。如果部署后发现严重问题,可能会对生产环境用户带来一些影响,因此这一步需要额外注意测试。
此方法的一大好处是,即使应用代码部署完成后出现问题,你依然可以回滚,因为数据库仍然保持向后兼容状态。


收缩(Contract)

第5步 – 收缩应用程序代码

在验证生产环境工作正常后,可以更新应用程序代码停止向旧模式写入数据,仅向新模式写入数据。这一步完成后,旧模式数据不再被更新。
你需要进行全面测试,确保系统行为正常。随后部署这部分代码,你的应用程序已经完成从旧模式转向新模式的过渡。


第6步 – 收缩数据库模式

最后一步是安全删除旧列或表。
在确认新模式工作正常并且整个流程无问题的几天后,旧的列或表可以被清除。确保你的应用程序不会再依赖它们进行任何读写操作。
如果对删除数据库元素仍有顾虑(例如担心其他团队或应用可能依赖它),有两种备选方法:

  • 如果是列,可以将其在MySQL中标记为不可见(通过SELECT *查询不会包含此列)。
  • 如果是列或表,可以重命名它们,这样如果其他程序试图使用这些数据,会报错但不会造成数据丢失。

恭喜!

通过这个模式,你已经完成了一次安全的数据库模式变更,避免了同时部署数据库与代码变更可能产生的风险。


示例演练

以下是一个完整的示例,展示如何在实际场景中应用扩展、迁移和收缩模式:


问题场景

假设你开发了一个应用程序来跟踪GitHub仓库的star数量。这种“虚荣指标”可能有一些信号用途。最初的数据库中包含以下两张表:
repo表:保存GitHub仓库的基本信息。

  • Id
  • repo_name
  • organization

star表:保存GitHub仓库的star数量等信息。

  • id
  • repo_name
  • organization
  • star_count

而现在,由于业务需求变更,你希望合并这两个表,在repo表中直接存储star_count字段,从而减少数据冗余并简化查询逻辑。任务分解如下:

  1. 在repo表中新增列star_count,并迁移数据。
  2. 删除原来的star表,同时确保没有数据丢失或对用户造成任何中断。

解决步骤

第1步 – 扩展现有数据库模式

首先,通过ALTER TABLE命令在repo表中添加一个新的star_count列:

ALTER TABLE repo
ADD COLUMN star_count INT;

此时,repo表的状态为:

idrepo_nameorganizationstar_count
1vtprotobufplanetscale
2beamplanetscale
3database-jsplanetscale

star表维持原样:

idrepo_nameorganizationstar_count
1vtprotobufplanetscale637
2beamplanetscale1837
3database-jsplanetscale854

第2步 – 扩展应用程序代码

在完成数据库变更后,更新应用程序代码,使其在向数据库写入时同时更新repo表的新列star_count和star表中的数据。


第3步 – 数据迁移

编写脚本将star表中的数据迁移到repo.star_count列。对于相对较少的数据,可以直接运行一次性迁移脚本。
迁移完成后,repo表变为:

idrepo_nameorganizationstar_count
1vtprotobufplanetscale637
2beamplanetscale1837
3database-jsplanetscale854

第4步 – 迁移应用程序代码

更新应用程序代码,使其只从repo.star_count列中读取数据,而不再从star表读取。


第5步 – 收缩应用程序代码

移除应用程序代码中对star表的写操作,现在只写入到repo表的star_count列。


第6步 – 收缩数据库模式

在确认几天内没有问题后,可以安全地删除star表:

DROP TABLE star;

总结

通过这种扩展、迁移和收缩模式,你能够安全地进行数据库模式的变更,同时避免对用户造成任何中断,也是开发人员中常被推荐的一种做法。希望这篇博客能够帮助你对数据库变更有更深的理解和实践。


安全进行数据库模式变更 这篇模式是安全进行数据库模式变更的一部分技巧之一。关于其他技术与相关数据库功能,你可以查看之前的博客或PlanetScale文档,学习如何在快速交付需求变更的同时保证变更安全性。



向后兼容的数据库变更插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://folen.top/2025/09/13/backward-compatible-databases-changes/