mcpField
D
database-migration

database-migration

github.com/sickn33数据与数据库48.8k405 次安装今天更新
代码仓库

关于 database-migration

面向数据库迁移任务的指导技能,涵盖 Sequelize、TypeORM 和 Prisma 等 ORM 的迁移,以及架构与数据变更、回滚和零停机部署。提供示例与验证建议;部分参考资料和脚本未随目录副本提供。

主要功能

  1. 01Sequelize、TypeORM、Prisma 迁移
  2. 02数据库架构变更指导
  3. 03数据转换与跨库迁移
  4. 04回滚与恢复策略
  5. 05零停机迁移建议
  6. 06迁移验证与最佳实践

适用场景

  1. 01在不同 ORM 之间迁移
  2. 02重构数据库架构或数据模型
  3. 03规划数据迁移及回滚
  4. 04升级数据库并减少停机

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill database-migration

安装后在 Claude Code 中输入/database-migration确认已加载。

详细介绍

支持材料可用性

本目录副本包含以下内嵌指南,但不包含以下支持路径中的文件。这些文件是外部前置条件,并未随附:

  • <support-root>/references/orm-switching.md
  • <support-root>/references/schema-migration.md
  • <support-root>/references/data-transformation.md
  • <support-root>/references/rollback-strategies.md
  • <support-root>/assets/schema-migration-template.sql
  • <support-root>/assets/data-migration-script.py
  • <support-root>/scripts/test-migration.sh

使用依赖这些路径的步骤前,请索取原始支持材料或现有安装;本目录未提供经验证的下载来源。请根据该安装解析每个路径,检查其内容,并核实适用的许可证和执行前置条件。所需文件不存在时,不要运行命令,也不要声称已遵循引用的流程。不要伪造缺失的脚本,也不要将名称相同但无关的文件视为等效文件。完全记录在此处的独立步骤,可在用户授权范围内继续执行;请将任何依赖项报告为不可用。

数据库迁移

掌握跨 ORM(Sequelize、TypeORM、Prisma)的数据库架构和数据迁移,包括回滚策略与零停机部署。

以下情况请勿使用此技能

  • 任务与数据库迁移无关
  • 需要使用本范围之外的其他领域或工具

说明

  • 明确目标、约束和所需输入。
  • 应用相关最佳实践并验证结果。
  • 提供可执行的步骤和验证方法。

以下情况请使用此技能

  • 在不同 ORM 之间迁移
  • 执行架构转换
  • 在数据库之间迁移数据
  • 实施回滚流程
  • 零停机部署
  • 数据库版本升级
  • 重构数据模型

ORM 迁移

Sequelize 迁移
// migrations/20231201-create-users.js
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('users', {
      id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      email: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      createdAt: Sequelize.DATE,
      updatedAt: Sequelize.DATE
    });
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('users');
  }
};

// Run: npx sequelize-cli db:migrate
// Rollback: npx sequelize-cli db:migrate:undo
TypeORM 迁移
// migrations/1701234567-CreateUsers.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateUsers1701234567 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'int',
            isPrimary: true,
            isGenerated: true,
            generationStrategy: 'increment'
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true
          },
          {
            name: 'created_at',
            type: 'timestamp',
            default: 'CURRENT_TIMESTAMP'
          }
        ]
      })
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

// Run: npm run typeorm migration:run
// Rollback: npm run typeorm migration:revert
Prisma 迁移
// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  createdAt DateTime @default(now())
}

// Generate migration: npx prisma migrate dev --name create_users
// Apply: npx prisma migrate deploy

架构转换

添加带默认值的列
// Illustrative migration: lock/rewrite behavior depends on engine/version/table size
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'status', {
      type: Sequelize.STRING,
      defaultValue: 'active',
      allowNull: false
    });
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'status');
  }
};
重命名列(扩展/收缩示意)

此示意不足以应对并发写入。请先部署兼容的双写,再执行有界回填;在回填期间协调变更,并仅在验证通过后切换读取;待所有读写方完成迁移后,再删除旧列。单次 UPDATE 可能导致更新丢失。

// Step 1: Add new column
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'full_name', {
      type: Sequelize.STRING
    });

    // Copy data from old column
    await queryInterface.sequelize.query(
      'UPDATE users SET full_name = name'
    );
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'full_name');
  }
};

// Step 2: Update application to use new column

// Step 3: Remove old column
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'name');
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'name', {
      type: Sequelize.STRING
    });
  }
};
更改列类型

此示例是具有破坏性且可能造成数据丢失的迁移,并非零停机方案。请先验证类型转换以及范围和空值的处理。保留原始值,直到单独获批的收缩步骤;转换回原类型无法恢复已丢弃的数据。

module.exports = {
  up: async (queryInterface, Sequelize) => {
    // For large tables, use multi-step approach

    // 1. Add new column
    await queryInterface.addColumn('users', 'age_new', {
      type: Sequelize.INTEGER
    });

    // 2. Copy and transform data
    await queryInterface.sequelize.query(`
      UPDATE users
      SET age_new = CAST(age AS INTEGER)
      WHERE age IS NOT NULL
    `);

    // 3. Drop old column
    await queryInterface.removeColumn('users', 'age');

    // 4. Rename new column
    await queryInterface.renameColumn('users', 'age_new', 'age');
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.changeColumn('users', 'age', {
      type: Sequelize.STRING
    });
  }
};

数据转换

复杂数据迁移

此处仅演示解析方法:请先定义并验证地址架构;按逗号拆分会造成数据丢失。请使用有界批次并设置检查点;在完成协调且经过测试的恢复方案通过前,保留源列。以下 down() 重建操作无法恢复原始格式。

module.exports = {
  up: async (queryInterface, Sequelize) => {
    // Get all records
    const [users] = await queryInterface.sequelize.query(
      'SELECT id, address_string FROM users'
    );

    // Transform each record
    for (const user of users) {
      const addressParts = user.address_string.split(',');

      await queryInterface.sequelize.query(
        `UPDATE users
         SET street = :street,
             city = :city,
             state = :state
         WHERE id = :id`,
        {
          replacements: {
            id: user.id,
            street: addressParts[0]?.trim(),
            city: addressParts[1]?.trim(),
            state: addressParts[2]?.trim()
          }
        }
      );
    }

    // Drop old column
    await queryInterface.removeColumn('users', 'address_string');
  },

  down: async (queryInterface, Sequelize) => {
    // Reconstruct original column
    await queryInterface.addColumn('users', 'address_string', {
      type: Sequelize.STRING
    });

    await queryInterface.sequelize.query(`
      UPDATE users
      SET address_string = CONCAT(street, ', ', city, ', ', state)
    `);

    await queryInterface.removeColumn('users', 'street');
    await queryInterface.removeColumn('users', 'city');
    await queryInterface.removeColumn('users', 'state');
  }
};

回滚策略

基于事务的迁移
module.exports = {
  up: async (queryInterface, Sequelize) => {
    const transaction = await queryInterface.sequelize.transaction();

    try {
      await queryInterface.addColumn(
        'users',
        'verified',
        { type: Sequelize.BOOLEAN, defaultValue: false },
        { transaction }
      );

      await queryInterface.sequelize.query(
        'UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL',
        { transaction }
      );

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'verified');
  }
};
基于检查点的恢复

请使用数据库引擎经过测试的备份/快照和时间点恢复流程,并在一次性恢复环境中验证。迁移前,请记录恢复点、架构、约束、索引、序列、权限和应用兼容性。CREATE TABLE AS SELECT 复制并非完整备份,绝不能用于自动错误恢复期间删除并重建正在使用的表。此操作会遗漏重要的架构属性,并可能丢失并发写入。部分状态不确定时,请停止操作,并在重试前完成协调。

零停机迁移

蓝绿部署策略
// Phase 1: Make changes backward compatible
module.exports = {
  up: async (queryInterface, Sequelize) => {
    // Add new column (both old and new code can work)
    await queryInterface.addColumn('users', 'email_new', {
      type: Sequelize.STRING
    });
  }
};

// Phase 2: Deploy code that writes to both columns

// Phase 3: Backfill data
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.sequelize.query(`
      UPDATE users
      SET email_new = email
      WHERE email_new IS NULL
    `);
  }
};

// Phase 4: Deploy code that reads from new column

// Phase 5: Remove old column
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'email');
  }
};

跨数据库迁移

从 PostgreSQL 迁移到 MySQL
// Handle differences
module.exports = {
  up: async (queryInterface, Sequelize) => {
    const dialectName = queryInterface.sequelize.getDialect();

    if (dialectName === 'mysql') {
      await queryInterface.createTable('users', {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true,
          autoIncrement: true
        },
        data: {
          type: Sequelize.JSON  // MySQL JSON type
        }
      });
    } else if (dialectName === 'postgres') {
      await queryInterface.createTable('users', {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true,
          autoIncrement: true
        },
        data: {
          type: Sequelize.JSONB  // PostgreSQL JSONB type
        }
      });
    }
  }
};

资源

  • references/orm-switching.md:ORM 迁移指南
  • references/schema-migration.md:Schema 转换模式
  • references/data-transformation.md:数据迁移脚本
  • references/rollback-strategies.md:回滚流程
  • assets/schema-migration-template.sql:SQL 迁移模板
  • assets/data-migration-script.py:数据迁移实用工具
  • scripts/test-migration.sh:迁移测试脚本

最佳实践

  1. 规划恢复方案:仅在确实可逆时提供经过测试的 down();否则,记录前向修复方案或经过测试的恢复方案,以及数据丢失边界
  2. 测试迁移:先在预发布环境中测试
  3. 使用事务:尽可能使用原子迁移
  4. 先备份:迁移前务必备份
  5. 小步变更:拆分为小的渐进步骤
  6. 监控:部署期间留意错误
  7. 记录文档:说明原因和做法
  8. 幂等性:迁移应可重复运行

常见问题

  • 未测试回滚流程
  • 未制定停机策略便进行破坏性变更
  • 忘记处理 NULL 值
  • 未考虑索引性能
  • 忽略外键约束
  • 一次迁移过多数据

限制

  • 仅当任务明确符合上述范围时,才使用此技能。
  • 不要将输出视为环境特定验证、测试或专家审查的替代品。
  • 如果缺少必要输入、权限、安全边界或成功标准,请暂停并寻求澄清。

评论

支持 Markdown