数据库迁移工具使用
数据库迁移工具的基本概念
数据库迁移工具用于管理数据库结构变更,允许开发者以代码形式定义数据库变更,并在不同环境间同步这些变更。Koa2应用中常见的迁移工具包括Knex.js、Sequelize和TypeORM等。这些工具通常提供CLI命令来创建、运行和回滚迁移。
迁移文件通常包含两个主要方法:up
用于执行变更,down
用于撤销变更。这种设计使得数据库版本可以向前或向后移动,便于团队协作和持续集成。
// 示例:使用Knex创建迁移文件
exports.up = function(knex) {
return knex.schema.createTable('users', function(table) {
table.increments('id');
table.string('username').notNullable();
table.string('email').unique();
table.timestamps(true, true);
});
};
exports.down = function(knex) {
return knex.schema.dropTable('users');
};
Koa2集成Knex.js进行迁移
Knex.js是一个流行的SQL查询构建器,同时也提供强大的迁移功能。在Koa2项目中集成Knex需要先安装相关依赖:
npm install knex pg --save
npx knex init
初始化后会生成knexfile.js
配置文件,需要根据项目环境进行修改:
// knexfile.js示例
module.exports = {
development: {
client: 'postgresql',
connection: {
host: '127.0.0.1',
user: 'your_db_user',
password: 'your_db_password',
database: 'myapp_dev'
},
migrations: {
tableName: 'knex_migrations',
directory: './migrations'
}
}
};
创建新迁移文件的命令:
npx knex migrate:make create_users_table
编写复杂的迁移逻辑
实际项目中经常需要处理更复杂的迁移场景,比如添加外键约束、修改列类型或执行数据转换。Knex提供了丰富的方法支持这些操作:
exports.up = async function(knex) {
await knex.schema.alterTable('posts', (table) => {
table.integer('author_id').unsigned();
table.foreign('author_id').references('users.id');
table.text('content').alter();
});
// 数据迁移示例
await knex('posts').update({
published_at: knex.raw('created_at')
});
};
exports.down = async function(knex) {
await knex.schema.alterTable('posts', (table) => {
table.dropForeign('author_id');
table.dropColumn('author_id');
table.string('content').alter();
});
};
迁移中的事务处理
为了保证迁移的原子性,应该将操作放在事务中执行。Knex支持自动事务管理:
exports.up = function(knex) {
return knex.transaction(trx => {
return trx.schema.createTable('orders', table => {
table.increments();
table.integer('user_id').unsigned();
table.decimal('total_amount', 10, 2);
table.enu('status', ['pending', 'paid', 'shipped']);
})
.then(() => {
return trx.schema.createTable('order_items', table => {
table.increments();
table.integer('order_id').unsigned();
table.integer('product_id').unsigned();
table.integer('quantity');
table.decimal('price', 10, 2);
});
});
});
};
处理迁移冲突和团队协作
在团队开发中,可能会遇到多个开发者同时创建迁移文件的情况。最佳实践包括:
- 使用时间戳作为迁移文件名前缀(Knex默认采用)
- 在版本控制系统中及时提交迁移文件
- 运行迁移前先拉取最新代码
- 遇到冲突时按时间顺序解决
# 查看当前迁移状态
npx knex migrate:status
# 执行未完成的迁移
npx knex migrate:latest
# 回滚最近一次迁移
npx knex migrate:rollback
生产环境迁移策略
生产环境的数据库迁移需要特别谨慎:
- 先在预发布环境测试所有迁移
- 备份数据库后再执行迁移
- 考虑使用蓝绿部署减少停机时间
- 监控迁移执行情况
// 生产环境迁移脚本示例
const knex = require('knex');
const config = require('./knexfile').production;
async function runMigrations() {
const db = knex(config);
try {
await db.migrate.latest();
console.log('Migration completed successfully');
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
} finally {
await db.destroy();
}
}
runMigrations();
测试数据库迁移
为迁移脚本编写测试可以防止生产环境出现问题:
const test = require('ava');
const knex = require('knex');
const config = require('./knexfile').test;
test.beforeEach(async t => {
t.context.db = knex(config);
await t.context.db.migrate.rollback();
await t.context.db.migrate.latest();
});
test.afterEach.always(async t => {
await t.context.db.destroy();
});
test('users table should exist after migration', async t => {
const exists = await t.context.db.schema.hasTable('users');
t.true(exists);
});
自定义迁移扩展
对于复杂项目,可能需要扩展迁移功能。Knex允许创建自定义迁移源:
// custom-migration-source.js
const fs = require('fs');
const path = require('path');
class CustomMigrationSource {
constructor(migrationDirectory) {
this.migrationDirectory = migrationDirectory;
}
getMigrations() {
return fs.readdirSync(this.migrationDirectory);
}
getMigrationName(migration) {
return migration;
}
getMigration(migration) {
return require(path.join(this.migrationDirectory, migration));
}
}
// 使用自定义迁移源
knex.migrate.latest({
migrationSource: new CustomMigrationSource('./custom_migrations')
});
多数据库系统支持
如果需要支持多种数据库系统,迁移文件需要考虑不同数据库的语法差异:
exports.up = function(knex) {
if (knex.client.config.client === 'mysql') {
return knex.raw(`
ALTER TABLE users
MODIFY COLUMN email VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
`);
} else if (knex.client.config.client === 'postgresql') {
return knex.raw(`
ALTER TABLE users
ALTER COLUMN email TYPE VARCHAR(255),
ALTER COLUMN email SET DATA TYPE VARCHAR(255)
`);
} else {
// SQLite等不支持直接修改列类型
return knex.schema.renameTable('users', 'old_users')
.then(() => knex.schema.createTable('users', newTable => {
// 创建新表结构
}))
.then(() => knex('old_users').insert().into('users'))
.then(() => knex.schema.dropTable('old_users'));
}
};
性能优化技巧
大型数据库迁移可能需要性能优化:
- 禁用外键检查(MySQL)
- 批量处理数据迁移
- 在低峰期执行迁移
- 考虑使用临时表
exports.up = async function(knex) {
// MySQL优化示例
if (knex.client.config.client === 'mysql') {
await knex.raw('SET FOREIGN_KEY_CHECKS=0');
}
// 批量处理示例
const batchSize = 1000;
let offset = 0;
let results;
do {
results = await knex('large_table')
.select('id', 'old_column')
.offset(offset)
.limit(batchSize);
await Promise.all(results.map(row =>
knex('large_table')
.where('id', row.id)
.update('new_column', transformFunction(row.old_column))
));
offset += batchSize;
} while (results.length === batchSize);
if (knex.client.config.client === 'mysql') {
await knex.raw('SET FOREIGN_KEY_CHECKS=1');
}
};
迁移与数据填充
除了结构变更,迁移也常用于初始数据填充。Knex提供seed功能:
npx knex seed:make initial_data
// seeds/initial_data.js
exports.seed = function(knex) {
return knex('roles').insert([
{ name: 'admin', permissions: JSON.stringify(['create', 'read', 'update', 'delete']) },
{ name: 'user', permissions: JSON.stringify(['read']) }
]);
};
执行种子数据:
npx knex seed:run
迁移版本控制
良好的版本控制实践包括:
- 将迁移文件纳入版本控制系统
- 每个迁移文件应该是独立的
- 避免修改已提交的迁移文件
- 使用
migrate:currentVersion
检查当前版本
npx knex migrate:currentVersion
错误处理与恢复
迁移失败时的处理策略:
- 记录详细的错误日志
- 提供回滚路径
- 考虑部分迁移的可能性
- 维护迁移运行历史表
exports.up = async function(knex) {
try {
await knex.schema.createTable('new_feature', table => {
// 表结构定义
});
await knex('new_feature').insert({
// 初始数据
});
} catch (error) {
console.error('Migration failed:', error);
// 尝试自动清理
await knex.schema.dropTableIfExists('new_feature');
throw error; // 重新抛出以标记迁移失败
}
};
迁移工具的高级功能
现代迁移工具提供的高级功能:
- 条件迁移
- 迁移依赖管理
- 迁移验证
- 迁移钩子
// 条件迁移示例
exports.up = function(knex) {
return knex.schema.hasTable('departments').then(exists => {
if (!exists) {
return knex.schema.createTable('departments', table => {
table.increments('id');
table.string('name');
});
}
});
};
// 迁移钩子示例
exports.up = function(knex) {
return knex.schema.createTable('audit_logs', table => {
// 表结构
}).then(() => {
// 迁移后钩子
return knex.raw(`
CREATE TRIGGER log_user_changes
AFTER UPDATE ON users
FOR EACH ROW
EXECUTE PROCEDURE audit_user_change();
`);
});
};
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Mongoose 最佳实践
下一篇:第三方 UI 库的适配