项目文件夹

文件
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

1 行
11 KiB
JSON

{"content": "---\nname: database-migration\ndescription: \"Master database schema and data migrations across ORMs (Sequelize, TypeORM, Prisma), including rollback strategies and zero-downtime deployments.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# Database Migration\n\nMaster database schema and data migrations across ORMs (Sequelize, TypeORM, Prisma), including rollback strategies and zero-downtime deployments.\n\n## Do not use this skill when\n\n- The task is unrelated to database migration\n- You need a different domain or tool outside this scope\n\n## Instructions\n\n- Clarify goals, constraints, and required inputs.\n- Apply relevant best practices and validate outcomes.\n- Provide actionable steps and verification.\n- If detailed examples are required, open `resources/implementation-playbook.md`.\n\n## Use this skill when\n\n- Migrating between different ORMs\n- Performing schema transformations\n- Moving data between databases\n- Implementing rollback procedures\n- Zero-downtime deployments\n- Database version upgrades\n- Data model refactoring\n\n## ORM Migrations\n\n### Sequelize Migrations\n```javascript\n// migrations/20231201-create-users.js\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n await queryInterface.createTable('users', {\n id: {\n type: Sequelize.INTEGER,\n primaryKey: true,\n autoIncrement: true\n },\n email: {\n type: Sequelize.STRING,\n unique: true,\n allowNull: false\n },\n createdAt: Sequelize.DATE,\n updatedAt: Sequelize.DATE\n });\n },\n\n down: async (queryInterface, Sequelize) => {\n await queryInterface.dropTable('users');\n }\n};\n\n// Run: npx sequelize-cli db:migrate\n// Rollback: npx sequelize-cli db:migrate:undo\n```\n\n### TypeORM Migrations\n```typescript\n// migrations/1701234567-CreateUsers.ts\nimport { MigrationInterface, QueryRunner, Table } from 'typeorm';\n\nexport class CreateUsers1701234567 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.createTable(\n new Table({\n name: 'users',\n columns: [\n {\n name: 'id',\n type: 'int',\n isPrimary: true,\n isGenerated: true,\n generationStrategy: 'increment'\n },\n {\n name: 'email',\n type: 'varchar',\n isUnique: true\n },\n {\n name: 'created_at',\n type: 'timestamp',\n default: 'CURRENT_TIMESTAMP'\n }\n ]\n })\n );\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.dropTable('users');\n }\n}\n\n// Run: npm run typeorm migration:run\n// Rollback: npm run typeorm migration:revert\n```\n\n### Prisma Migrations\n```prisma\n// schema.prisma\nmodel User {\n id Int @id @default(autoincrement())\n email String @unique\n createdAt DateTime @default(now())\n}\n\n// Generate migration: npx prisma migrate dev --name create_users\n// Apply: npx prisma migrate deploy\n```\n\n## Schema Transformations\n\n### Adding Columns with Defaults\n```javascript\n// Safe migration: add column with default\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n await queryInterface.addColumn('users', 'status', {\n type: Sequelize.STRING,\n defaultValue: 'active',\n allowNull: false\n });\n },\n\n down: async (queryInterface) => {\n await queryInterface.removeColumn('users', 'status');\n }\n};\n```\n\n### Renaming Columns (Zero Downtime)\n```javascript\n// Step 1: Add new column\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n await queryInterface.addColumn('users', 'full_name', {\n type: Sequelize.STRING\n });\n\n // Copy data from old column\n await queryInterface.sequelize.query(\n 'UPDATE users SET full_name = name'\n );\n },\n\n down: async (queryInterface) => {\n await queryInterface.removeColumn('users', 'full_name');\n }\n};\n\n// Step 2: Update application to use new column\n\n// Step 3: Remove old column\nmodule.exports = {\n up: async (queryInterface) => {\n await queryInterface.removeColumn('users', 'name');\n },\n\n down: async (queryInterface, Sequelize) => {\n await queryInterface.addColumn('users', 'name', {\n type: Sequelize.STRING\n });\n }\n};\n```\n\n### Changing Column Types\n```javascript\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n // For large tables, use multi-step approach\n\n // 1. Add new column\n await queryInterface.addColumn('users', 'age_new', {\n type: Sequelize.INTEGER\n });\n\n // 2. Copy and transform data\n await queryInterface.sequelize.query(`\n UPDATE users\n SET age_new = CAST(age AS INTEGER)\n WHERE age IS NOT NULL\n `);\n\n // 3. Drop old column\n await queryInterface.removeColumn('users', 'age');\n\n // 4. Rename new column\n await queryInterface.renameColumn('users', 'age_new', 'age');\n },\n\n down: async (queryInterface, Sequelize) => {\n await queryInterface.changeColumn('users', 'age', {\n type: Sequelize.STRING\n });\n }\n};\n```\n\n## Data Transformations\n\n### Complex Data Migration\n```javascript\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n // Get all records\n const [users] = await queryInterface.sequelize.query(\n 'SELECT id, address_string FROM users'\n );\n\n // Transform each record\n for (const user of users) {\n const addressParts = user.address_string.split(',');\n\n await queryInterface.sequelize.query(\n `UPDATE users\n SET street = :street,\n city = :city,\n state = :state\n WHERE id = :id`,\n {\n replacements: {\n id: user.id,\n street: addressParts[0]?.trim(),\n city: addressParts[1]?.trim(),\n state: addressParts[2]?.trim()\n }\n }\n );\n }\n\n // Drop old column\n await queryInterface.removeColumn('users', 'address_string');\n },\n\n down: async (queryInterface, Sequelize) => {\n // Reconstruct original column\n await queryInterface.addColumn('users', 'address_string', {\n type: Sequelize.STRING\n });\n\n await queryInterface.sequelize.query(`\n UPDATE users\n SET address_string = CONCAT(street, ', ', city, ', ', state)\n `);\n\n await queryInterface.removeColumn('users', 'street');\n await queryInterface.removeColumn('users', 'city');\n await queryInterface.removeColumn('users', 'state');\n }\n};\n```\n\n## Rollback Strategies\n\n### Transaction-Based Migrations\n```javascript\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n const transaction = await queryInterface.sequelize.transaction();\n\n try {\n await queryInterface.addColumn(\n 'users',\n 'verified',\n { type: Sequelize.BOOLEAN, defaultValue: false },\n { transaction }\n );\n\n await queryInterface.sequelize.query(\n 'UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL',\n { transaction }\n );\n\n await transaction.commit();\n } catch (error) {\n await transaction.rollback();\n throw error;\n }\n },\n\n down: async (queryInterface) => {\n await queryInterface.removeColumn('users', 'verified');\n }\n};\n```\n\n### Checkpoint-Based Rollback\n```javascript\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n // Create backup table\n await queryInterface.sequelize.query(\n 'CREATE TABLE users_backup AS SELECT * FROM users'\n );\n\n try {\n // Perform migration\n await queryInterface.addColumn('users', 'new_field', {\n type: Sequelize.STRING\n });\n\n // Verify migration\n const [result] = await queryInterface.sequelize.query(\n \"SELECT COUNT(*) as count FROM users WHERE new_field IS NULL\"\n );\n\n if (result[0].count > 0) {\n throw new Error('Migration verification failed');\n }\n\n // Drop backup\n await queryInterface.dropTable('users_backup');\n } catch (error) {\n // Restore from backup\n await queryInterface.sequelize.query('DROP TABLE users');\n await queryInterface.sequelize.query(\n 'CREATE TABLE users AS SELECT * FROM users_backup'\n );\n await queryInterface.dropTable('users_backup');\n throw error;\n }\n }\n};\n```\n\n## Zero-Downtime Migrations\n\n### Blue-Green Deployment Strategy\n```javascript\n// Phase 1: Make changes backward compatible\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n // Add new column (both old and new code can work)\n await queryInterface.addColumn('users', 'email_new', {\n type: Sequelize.STRING\n });\n }\n};\n\n// Phase 2: Deploy code that writes to both columns\n\n// Phase 3: Backfill data\nmodule.exports = {\n up: async (queryInterface) => {\n await queryInterface.sequelize.query(`\n UPDATE users\n SET email_new = email\n WHERE email_new IS NULL\n `);\n }\n};\n\n// Phase 4: Deploy code that reads from new column\n\n// Phase 5: Remove old column\nmodule.exports = {\n up: async (queryInterface) => {\n await queryInterface.removeColumn('users', 'email');\n }\n};\n```\n\n## Cross-Database Migrations\n\n### PostgreSQL to MySQL\n```javascript\n// Handle differences\nmodule.exports = {\n up: async (queryInterface, Sequelize) => {\n const dialectName = queryInterface.sequelize.getDialect();\n\n if (dialectName === 'mysql') {\n await queryInterface.createTable('users', {\n id: {\n type: Sequelize.INTEGER,\n primaryKey: true,\n autoIncrement: true\n },\n data: {\n type: Sequelize.JSON // MySQL JSON type\n }\n });\n } else if (dialectName === 'postgres') {\n await queryInterface.createTable('users', {\n id: {\n type: Sequelize.INTEGER,\n primaryKey: true,\n autoIncrement: true\n },\n data: {\n type: Sequelize.JSONB // PostgreSQL JSONB type\n }\n });\n }\n }\n};\n```\n\n## Resources\n\n- **references/orm-switching.md**: ORM migration guides\n- **references/schema-migration.md**: Schema transformation patterns\n- **references/data-transformation.md**: Data migration scripts\n- **references/rollback-strategies.md**: Rollback procedures\n- **assets/schema-migration-template.sql**: SQL migration templates\n- **assets/data-migration-script.py**: Data migration utilities\n- **scripts/test-migration.sh**: Migration testing script\n\n## Best Practices\n\n1. **Always Provide Rollback**: Every up() needs a down()\n2. **Test Migrations**: Test on staging first\n3. **Use Transactions**: Atomic migrations when possible\n4. **Backup First**: Always backup before migration\n5. **Small Changes**: Break into small, incremental steps\n6. **Monitor**: Watch for errors during deployment\n7. **Document**: Explain why and how\n8. **Idempotent**: Migrations should be rerunnable\n\n## Common Pitfalls\n\n- Not testing rollback procedures\n- Making breaking changes without downtime strategy\n- Forgetting to handle NULL values\n- Not considering index performance\n- Ignoring foreign key constraints\n- Migrating too much data at once\n"}