database-migration-helper
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDatabase Migration Helper
数据库迁移助手
Create and manage database migrations safely with proper rollback support.
安全地创建和管理数据库迁移,提供完善的回滚支持。
Quick Start
快速开始
Create migration files with up/down functions, test locally, backup before production, run migrations incrementally.
创建包含up/down函数的迁移文件,先在本地测试,生产环境执行前备份数据库,增量运行迁移。
Instructions
操作指南
Migration Structure
迁移文件结构
Basic migration:
javascript
// migrations/001_create_users_table.js
exports.up = async (db) => {
await db.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('email').unique().notNullable();
table.string('password_hash').notNullable();
table.timestamps(true, true);
});
};
exports.down = async (db) => {
await db.schema.dropTable('users');
};基础迁移示例:
javascript
// migrations/001_create_users_table.js
exports.up = async (db) => {
await db.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('email').unique().notNullable();
table.string('password_hash').notNullable();
table.timestamps(true, true);
});
};
exports.down = async (db) => {
await db.schema.dropTable('users');
};Creating Tables
创建数据表
With Knex:
javascript
exports.up = async (knex) => {
await knex.schema.createTable('posts', (table) => {
table.increments('id').primary();
table.integer('user_id').unsigned().notNullable();
table.string('title', 200).notNullable();
table.text('content');
table.enum('status', ['draft', 'published', 'archived']).defaultTo('draft');
table.timestamps(true, true);
// Foreign key
table.foreign('user_id').references('users.id').onDelete('CASCADE');
// Indexes
table.index('user_id');
table.index('status');
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('posts');
};With raw SQL:
sql
-- migrations/001_create_users.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
-- migrations/001_create_users.down.sql
DROP TABLE IF EXISTS users;使用Knex:
javascript
exports.up = async (knex) => {
await knex.schema.createTable('posts', (table) => {
table.increments('id').primary();
table.integer('user_id').unsigned().notNullable();
table.string('title', 200).notNullable();
table.text('content');
table.enum('status', ['draft', 'published', 'archived']).defaultTo('draft');
table.timestamps(true, true);
// Foreign key
table.foreign('user_id').references('users.id').onDelete('CASCADE');
// Indexes
table.index('user_id');
table.index('status');
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('posts');
};使用原生SQL:
sql
-- migrations/001_create_users.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
-- migrations/001_create_users.down.sql
DROP TABLE IF EXISTS users;Adding Columns
添加列
javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.string('phone', 20);
table.boolean('is_verified').defaultTo(false);
});
};
exports.down = async (knex) => {
await knex.schema.table('users', (table) => {
table.dropColumn('phone');
table.dropColumn('is_verified');
});
};javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.string('phone', 20);
table.boolean('is_verified').defaultTo(false);
});
};
exports.down = async (knex) => {
await knex.schema.table('users', (table) => {
table.dropColumn('phone');
table.dropColumn('is_verified');
});
};Modifying Columns
修改列
javascript
exports.up = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.string('email', 320).alter(); // Increase length
table.string('name', 100).notNullable().alter(); // Add NOT NULL
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.string('email', 255).alter();
table.string('name', 100).nullable().alter();
});
};javascript
exports.up = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.string('email', 320).alter(); // Increase length
table.string('name', 100).notNullable().alter(); // Add NOT NULL
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.string('email', 255).alter();
table.string('name', 100).nullable().alter();
});
};Adding Indexes
添加索引
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.index('created_at');
table.index(['user_id', 'created_at']); // Composite index
});
};
exports.down = async (knex) => {
await knex.schema.table('posts', (table) => {
table.dropIndex('created_at');
table.dropIndex(['user_id', 'created_at']);
});
};javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.index('created_at');
table.index(['user_id', 'created_at']); // Composite index
});
};
exports.down = async (knex) => {
await knex.schema.table('posts', (table) => {
table.dropIndex('created_at');
table.dropIndex(['user_id', 'created_at']);
});
};Data Migrations
数据迁移
javascript
exports.up = async (knex) => {
// Add column
await knex.schema.table('users', (table) => {
table.string('full_name');
});
// Migrate data
const users = await knex('users').select('id', 'first_name', 'last_name');
for (const user of users) {
await knex('users')
.where('id', user.id)
.update({ full_name: `${user.first_name} ${user.last_name}` });
}
// Drop old columns
await knex.schema.table('users', (table) => {
table.dropColumn('first_name');
table.dropColumn('last_name');
});
};
exports.down = async (knex) => {
// Add old columns
await knex.schema.table('users', (table) => {
table.string('first_name');
table.string('last_name');
});
// Migrate data back
const users = await knex('users').select('id', 'full_name');
for (const user of users) {
const [firstName, ...lastNameParts] = user.full_name.split(' ');
await knex('users')
.where('id', user.id)
.update({
first_name: firstName,
last_name: lastNameParts.join(' ')
});
}
// Drop new column
await knex.schema.table('users', (table) => {
table.dropColumn('full_name');
});
};javascript
exports.up = async (knex) => {
// Add column
await knex.schema.table('users', (table) => {
table.string('full_name');
});
// Migrate data
const users = await knex('users').select('id', 'first_name', 'last_name');
for (const user of users) {
await knex('users')
.where('id', user.id)
.update({ full_name: `${user.first_name} ${user.last_name}` });
}
// Drop old columns
await knex.schema.table('users', (table) => {
table.dropColumn('first_name');
table.dropColumn('last_name');
});
};
exports.down = async (knex) => {
// Add old columns
await knex.schema.table('users', (table) => {
table.string('first_name');
table.string('last_name');
});
// Migrate data back
const users = await knex('users').select('id', 'full_name');
for (const user of users) {
const [firstName, ...lastNameParts] = user.full_name.split(' ');
await knex('users')
.where('id', user.id)
.update({
first_name: firstName,
last_name: lastNameParts.join(' ')
});
}
// Drop new column
await knex.schema.table('users', (table) => {
table.dropColumn('full_name');
});
};Foreign Keys
外键
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.foreign('user_id')
.references('id')
.inTable('users')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
};
exports.down = async (knex) => {
await knex.schema.table('posts', (table) => {
table.dropForeign('user_id');
});
};javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.foreign('user_id')
.references('id')
.inTable('users')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
};
exports.down = async (knex) => {
await knex.schema.table('posts', (table) => {
table.dropForeign('user_id');
});
};Renaming Tables/Columns
重命名表/列
javascript
exports.up = async (knex) => {
await knex.schema.renameTable('posts', 'articles');
await knex.schema.table('articles', (table) => {
table.renameColumn('content', 'body');
});
};
exports.down = async (knex) => {
await knex.schema.table('articles', (table) => {
table.renameColumn('body', 'content');
});
await knex.schema.renameTable('articles', 'posts');
};javascript
exports.up = async (knex) => {
await knex.schema.renameTable('posts', 'articles');
await knex.schema.table('articles', (table) => {
table.renameColumn('content', 'body');
});
};
exports.down = async (knex) => {
await knex.schema.table('articles', (table) => {
table.renameColumn('body', 'content');
});
await knex.schema.renameTable('articles', 'posts');
};Migration Tools
迁移工具
Knex.js:
bash
undefinedKnex.js:
bash
undefinedCreate migration
Create migration
npx knex migrate:make create_users_table
npx knex migrate:make create_users_table
Run migrations
Run migrations
npx knex migrate:latest
npx knex migrate:latest
Rollback last batch
Rollback last batch
npx knex migrate:rollback
npx knex migrate:rollback
Rollback all
Rollback all
npx knex migrate:rollback --all
npx knex migrate:rollback --all
Check status
Check status
npx knex migrate:status
**TypeORM:**
```bashnpx knex migrate:status
**TypeORM:**
```bashGenerate migration
Generate migration
npm run typeorm migration:generate -- -n CreateUsersTable
npm run typeorm migration:generate -- -n CreateUsersTable
Run migrations
Run migrations
npm run typeorm migration:run
npm run typeorm migration:run
Revert last migration
Revert last migration
npm run typeorm migration:revert
**Prisma:**
```bashnpm run typeorm migration:revert
**Prisma:**
```bashCreate migration
Create migration
npx prisma migrate dev --name create_users_table
npx prisma migrate dev --name create_users_table
Apply migrations
Apply migrations
npx prisma migrate deploy
npx prisma migrate deploy
Reset database
Reset database
npx prisma migrate reset
**Sequelize:**
```bashnpx prisma migrate reset
**Sequelize:**
```bashCreate migration
Create migration
npx sequelize-cli migration:generate --name create-users-table
npx sequelize-cli migration:generate --name create-users-table
Run migrations
Run migrations
npx sequelize-cli db:migrate
npx sequelize-cli db:migrate
Undo last migration
Undo last migration
npx sequelize-cli db:migrate:undo
undefinednpx sequelize-cli db:migrate:undo
undefinedBest Practices
最佳实践
1. Always include rollback:
javascript
// Every migration must have down()
exports.down = async (knex) => {
// Reverse the changes
};2. Make migrations idempotent:
javascript
exports.up = async (knex) => {
const exists = await knex.schema.hasTable('users');
if (!exists) {
await knex.schema.createTable('users', (table) => {
// ...
});
}
};3. Test migrations:
bash
undefined1. 始终包含回滚逻辑:
javascript
// Every migration must have down()
exports.down = async (knex) => {
// Reverse the changes
};2. 确保迁移具有幂等性:
javascript
exports.up = async (knex) => {
const exists = await knex.schema.hasTable('users');
if (!exists) {
await knex.schema.createTable('users', (table) => {
// ...
});
}
};3. 测试迁移:
bash
undefinedRun migration
Run migration
npm run migrate
npm run migrate
Test application
Test application
npm test
npm test
Rollback
Rollback
npm run migrate:rollback
npm run migrate:rollback
Run again
Run again
npm run migrate
**4. Backup before production:**
```bashnpm run migrate
**4. 生产环境前备份数据库:**
```bashPostgreSQL
PostgreSQL
pg_dump dbname > backup.sql
pg_dump dbname > backup.sql
MySQL
MySQL
mysqldump dbname > backup.sql
mysqldump dbname > backup.sql
Then run migration
Then run migration
npm run migrate
**5. Run migrations incrementally:**
```bashnpm run migrate
**5. 增量运行迁移:**
```bashDon't run all at once in production
Don't run all at once in production
Run one migration at a time
Run one migration at a time
npx knex migrate:up 001_create_users_table.js
npx knex migrate:up 001_create_users_table.js
Verify
Verify
Then next migration
Then next migration
npx knex migrate:up 002_create_posts_table.js
undefinednpx knex migrate:up 002_create_posts_table.js
undefinedCommon Patterns
常见模式
Add column with default:
javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.boolean('is_active').defaultTo(true);
});
};Add enum column:
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.enum('status', ['draft', 'published', 'archived'])
.defaultTo('draft');
});
};Add timestamp columns:
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.timestamps(true, true); // created_at, updated_at
});
};Add JSON column:
javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.json('metadata');
});
};添加带默认值的列:
javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.boolean('is_active').defaultTo(true);
});
};添加枚举列:
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.enum('status', ['draft', 'published', 'archived'])
.defaultTo('draft');
});
};添加时间戳列:
javascript
exports.up = async (knex) => {
await knex.schema.table('posts', (table) => {
table.timestamps(true, true); // created_at, updated_at
});
};添加JSON列:
javascript
exports.up = async (knex) => {
await knex.schema.table('users', (table) => {
table.json('metadata');
});
};Handling Large Tables
大表处理
Add index without locking (PostgreSQL):
sql
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);Add column with default (PostgreSQL 11+):
sql
-- Fast: doesn't rewrite table
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT true;Batch data migration:
javascript
exports.up = async (knex) => {
const batchSize = 1000;
let offset = 0;
while (true) {
const users = await knex('users')
.select('id', 'email')
.limit(batchSize)
.offset(offset);
if (users.length === 0) break;
for (const user of users) {
await knex('users')
.where('id', user.id)
.update({ email_lower: user.email.toLowerCase() });
}
offset += batchSize;
}
};添加索引时避免锁表(PostgreSQL):
sql
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);添加带默认值的列(PostgreSQL 11+):
sql
-- Fast: doesn't rewrite table
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT true;批量数据迁移:
javascript
exports.up = async (knex) => {
const batchSize = 1000;
let offset = 0;
while (true) {
const users = await knex('users')
.select('id', 'email')
.limit(batchSize)
.offset(offset);
if (users.length === 0) break;
for (const user of users) {
await knex('users')
.where('id', user.id)
.update({ email_lower: user.email.toLowerCase() });
}
offset += batchSize;
}
};Migration Checklist
迁移检查清单
Before creating:
- Understand the change needed
- Plan rollback strategy
- Consider data migration
- Check for dependencies
In migration:
- Include up and down functions
- Add appropriate indexes
- Set constraints
- Handle existing data
Before running:
- Test locally
- Test rollback
- Backup database
- Plan maintenance window
After running:
- Verify changes
- Test application
- Monitor performance
- Document changes
创建迁移前:
- 明确所需的变更内容
- 规划回滚策略
- 考虑数据迁移需求
- 检查依赖关系
编写迁移时:
- 包含up和down函数
- 添加合适的索引
- 设置约束条件
- 处理现有数据
运行迁移前:
- 本地测试
- 测试回滚
- 备份数据库
- 规划维护窗口
运行迁移后:
- 验证变更
- 测试应用
- 监控性能
- 记录变更
Troubleshooting
故障排除
Migration fails:
- Check error message
- Verify database connection
- Check for syntax errors
- Ensure dependencies exist
Can't rollback:
- Check down() function
- Verify rollback logic
- May need manual intervention
- Restore from backup if needed
Performance issues:
- Add indexes after data load
- Use CONCURRENTLY for indexes
- Batch large data migrations
- Run during low traffic
迁移失败:
- 查看错误信息
- 验证数据库连接
- 检查语法错误
- 确保依赖项存在
无法回滚:
- 检查down()函数
- 验证回滚逻辑
- 可能需要手动干预
- 必要时从备份恢复
性能问题:
- 数据加载完成后再添加索引
- 对索引使用CONCURRENTLY
- 批量处理大型数据迁移
- 在低流量时段运行