云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

数据库重构的利器:扩展/收缩模式,让你的数据库平滑升级

jxf315 2025-02-03 13:26:59 教程文章 35 ℃

最近在对老项目进行数据库结构优化时,我们经常会面临这样的挑战:如何才能在不影响现有业务的情况下,安全地修改数据库模式?尤其是在高并发的生产环境中,任何停机时间都可能造成不可估量的损失。这时候,一种被称为“扩展/收缩(Expand/Contract)”的数据库重构模式就显得尤为重要。它提供了一种安全、平滑的数据库模式变更方案,允许我们在系统运行的同时进行数据库升级。

什么是扩展/收缩模式?

扩展/收缩模式是一种数据库重构策略,它将数据库模式的修改分解为三个阶段:扩展(Expand)过渡(Transition)收缩(Contract)。在整个过程中,系统保持在线,从而避免了停机时间。这种模式的核心思想在于,我们不是直接修改现有的数据库模式,而是先引入新的模式,然后逐步过渡到新模式,最后移除旧模式。让我们通过一个实际的例子来具体了解一下这个过程。

案例背景:用户表结构升级

假设我们有一个用户表 Users,最初只包含 idFullName 两个字段。由于业务发展,我们需要将 FullName 字段拆分为 FirstNameLastName 两个字段。采用扩展/收缩模式,我们可以按照以下步骤进行数据库重构:

  1. 扩展(Expand)阶段
  2. 在此阶段,我们首先引入新的数据库模式,在用户表 Users 中添加 FirstNameLastName 这两个新列。注意,我们并没有删除 FullName 列,而是保留了它。此时,数据库中同时存在新旧两种模式。
  3. 过渡(Transition)阶段
  4. 在过渡阶段,我们需要保持新旧模式的同步。这意味着,当 FullName 被更新时,FirstNameLastName 也应该相应更新;反之亦然。为了实现这个目标,我们通常会使用数据库触发器(Trigger)。在提供的代码示例中,就展示了一个 SQL Server 中的触发器 SyncFullname。这个触发器的作用是:
  • FullName 被更新时,触发器会根据 FullName的值,自动计算并更新 FirstNameLastName 字段。例如,如果 FullName 的值为 "John Doe",触发器会将 FirstName 设置为 "John", LastName设置为 "Doe"。
  • FirstNameLastName 被更新时,触发器会将 FullName 更新为 FirstNameLastName 的组合。
  • 同时,为了保证数据一致性,我们还要考虑如何处理历史数据。通常,我们会写一个数据迁移脚本,将现有 FullName 中的数据拆分到 FirstNameLastName 这两个新列中。

以下是触发器的主要代码逻辑:

CREATE TRIGGER SyncFullname
ON Users
AFTER INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    -- 如果 FullName 被更新,则更新 FirstName 和 LastName
    IF UPDATE(FullName)
    BEGIN
        UPDATE u
        SET
            u.FirstName = CASE
                WHEN CHARINDEX(' ', i.FullName) > 0
                THEN LEFT(i.FullName, CHARINDEX(' ', i.FullName) - 1)
                ELSE i.FullName
            END,
            u.LastName = CASE
                WHEN CHARINDEX(' ', i.FullName) > 0
                THEN LTRIM(SUBSTRING(i.FullName, CHARINDEX(' ', i.FullName) + 1, LEN(i.FullName)))
                ELSE NULL
            END
         , u.FullName = i.FullName    
        FROM Users u
        INNER JOIN Inserted i ON u.Id = i.Id
        WHERE i.FullName IS NOT NULL;
    END;

    -- 如果 FirstName 或 LastName 被更新,则更新 FullName
    IF UPDATE(FirstName) OR UPDATE(LastName)
    BEGIN
        UPDATE u
        SET
            u.FullName = RTRIM(COALESCE(i.FirstName, u.FirstName) + ' ' + LTRIM(COALESCE(i.LastName,u.LastName)))
        FROM Users u
        INNER JOIN Inserted i ON u.Id = i.Id
        WHERE i.FirstName IS NOT NULL OR i.LastName IS NOT NULL;
    END;
END;

代码解释:

  • CREATE TRIGGER SyncFullname ON Users AFTER INSERT, UPDATE AS BEGIN ... END; 创建了一个名为 SyncFullname 的触发器,它在 Users 表的 INSERTUPDATE 操作之后执行。
  • SET NOCOUNT ON; 表示触发器不返回受影响的行数,优化性能。
  • IF UPDATE(FullName) 判断 FullName 列是否被更新。如果是,则执行以下操作:
    * 使用 CASE 语句判断 FullName 中是否存在空格。如果存在,则使用 LEFTSUBSTRING 函数提取 FirstNameLastName。如果不存在,则将 FirstName 设置为 FullNameLastName 设置为 NULL
  • IF UPDATE(FirstName) OR UPDATE(LastName) 判断 FirstNameLastName 列是否被更新。如果是,则执行以下操作:将 FirstNameLastName 列拼接成 FullName。使用 COALESCE 函数处理 NULL值,使用 RTRIMLTRIM 函数去除首尾空格。
  • INNER JOIN Inserted i ON u.Id = i.Id 将更新后的行与 Inserted 表连接,以便获取更新后的值。
  • WHERE i.FullName IS NOT NULLWHERE i.FirstName IS NOT NULL OR i.LastName IS NOT NULL 保证只有在相关列被更新时才执行更新操作。

通过这个触发器,我们可以确保在过渡阶段,新旧模式的数据是同步的。无论是通过修改 FullName 还是 FirstNameLastName 来修改用户数据,最终都会反映到三个列中。

  1. 收缩(Contract)阶段
  2. 一旦所有客户端都迁移到新模式,并且我们确信旧模式不再被使用,我们就可以移除 FullName 列了。这样,我们就完成了数据库重构,而且整个过程没有造成任何停机时间。

扩展/收缩模式的优势

  • 零停机时间:这是这种模式最显著的优点。由于我们不是直接修改数据库模式,而是引入新模式并逐步过渡,因此可以在不中断现有业务的情况下进行数据库升级。
  • 风险可控:通过逐步过渡,我们可以随时回滚到旧模式,从而降低了重构风险。如果在过渡阶段出现问题,我们可以快速回退到之前的状态,从而避免重大损失。
  • 易于回滚:如果重构过程中出现问题,我们可以快速回滚到旧模式,从而降低了重构风险。
  • 代码解耦: 新旧模式并存的阶段,可以帮助我们逐步切换代码,减少一次性修改带来的风险,实现代码的平滑过渡。

扩展/收缩模式的局限性

  • 实现复杂: 需要编写额外的触发器和数据迁移脚本,实现起来比直接修改数据库模式复杂。
  • 性能开销: 额外的触发器和数据同步机制可能会带来一定的性能开销,需要在设计时仔细考虑。

总结

扩展/收缩模式是一种非常有效的数据库重构策略,它允许我们在不影响现有业务的情况下,安全地修改数据库模式。虽然实现起来相对复杂,但其带来的好处是显而易见的,尤其是在需要保证系统高可用性的场景下。

在实际应用中,我们需要根据具体情况选择适合的重构策略。扩展/收缩模式并非万能,但它为我们提供了一种在复杂环境下安全进行数据库重构的思路。希望通过这篇文章,你能对扩展/收缩模式有一个更深入的了解。下次在进行数据库重构时,不妨尝试一下这种模式。

思考问题

  • 除了 FullName 拆分为 FirstNameLastName的场景外,你还能想到哪些场景适合使用扩展/收缩模式?
  • 在实施扩展/收缩模式时,如何监控和度量重构过程的进展?
  • 在你的实际项目中,你是否使用过类似的模式进行数据库重构?如果有,你的经验是什么?

希望这篇文章对你有所帮助,期待你的反馈和讨论!

最近发表
标签列表