数据库迁移管理 database-migration-management

这个技能涉及数据库迁移和架构版本控制,包括在PostgreSQL和MySQL中进行迁移框架设置、执行数据转换、添加或删除表和列、索引创建和优化、迁移测试和验证、回滚计划和执行以及多环境部署。

数据工程 0 次安装 0 次浏览 更新于 3/3/2026

数据库迁移管理

概览

实施健壮的数据库迁移系统,包括版本控制、回滚能力以及数据转换策略。包括迁移框架和生产部署模式。

何时使用

  • 架构版本控制和演化
  • 数据转换和清理
  • 添加/移除表和列
  • 索引创建和优化
  • 迁移测试和验证
  • 回滚计划和执行
  • 多环境部署

迁移框架设置

PostgreSQL - 架构版本控制

-- 创建迁移跟踪表
CREATE TABLE schema_migrations (
  version BIGINT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  duration_ms INTEGER,
  checksum VARCHAR(64)
);

-- 创建迁移日志表
CREATE TABLE migration_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  version BIGINT NOT NULL,
  status VARCHAR(20) NOT NULL,
  error_message TEXT,
  rolled_back_at TIMESTAMP,
  executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 函数记录迁移
CREATE OR REPLACE FUNCTION record_migration(
  p_version BIGINT,
  p_name VARCHAR,
  p_duration_ms INTEGER
) RETURNS void AS $$
BEGIN
  INSERT INTO schema_migrations (version, name, duration_ms)
  VALUES (p_version, p_name, p_duration_ms)
  ON CONFLICT (version) DO UPDATE
  SET executed_at = CURRENT_TIMESTAMP;
END;
$$ LANGUAGE plpgsql;

MySQL - 迁移跟踪

-- 创建MySQL的迁移表
CREATE TABLE schema_migrations (
  version BIGINT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  duration_ms INT,
  checksum VARCHAR(64)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 迁移状态表
CREATE TABLE migration_status (
  id INT AUTO_INCREMENT PRIMARY KEY,
  version BIGINT NOT NULL,
  status ENUM('pending', 'completed', 'failed', 'rolled_back'),
  error_message TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

常见迁移模式

添加列

PostgreSQL - 安全添加列:

-- 迁移:20240115_001_add_phone_to_users.sql

-- 添加列并设置默认值(非阻塞)
ALTER TABLE users
ADD COLUMN phone VARCHAR(20) DEFAULT '';

-- 添加约束后填充
ALTER TABLE users
ADD CONSTRAINT phone_format
CHECK (phone = '' OR phone ~ '^\+?[0-9\-\(\)]{10,}$');

-- 创建索引
CREATE INDEX CONCURRENTLY idx_users_phone ON users(phone);

-- 回滚:
-- DROP INDEX CONCURRENTLY idx_users_phone;
-- ALTER TABLE users DROP COLUMN phone;

MySQL - 添加列:

-- 迁移:20240115_001_add_phone_to_users.sql

-- 使用ALTER添加列
ALTER TABLE users
ADD COLUMN phone VARCHAR(20) DEFAULT '',
ADD INDEX idx_phone (phone);

-- 回滚:
-- ALTER TABLE users DROP COLUMN phone;

重命名列

PostgreSQL - 列重命名:

-- 迁移:20240115_002_rename_user_name_columns.sql

-- 重命名列
ALTER TABLE users RENAME COLUMN user_name TO full_name;
ALTER TABLE users RENAME COLUMN user_email TO email_address;

-- 更新索引
REINDEX TABLE users;

-- 回滚:
-- ALTER TABLE users RENAME COLUMN email_address TO user_email;
-- ALTER TABLE users RENAME COLUMN full_name TO user_name;

创建索引非阻塞

PostgreSQL - 并发索引创建:

-- 迁移:20240115_003_add_performance_indexes.sql

-- 创建索引不阻塞写入
CREATE INDEX CONCURRENTLY idx_orders_user_created
ON orders(user_id, created_at DESC);

CREATE INDEX CONCURRENTLY idx_products_category_active
ON products(category_id)
WHERE active = true;

-- 验证索引创建
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE indexname LIKE 'idx_%';

-- 回滚:
-- DROP INDEX CONCURRENTLY idx_orders_user_created;
-- DROP INDEX CONCURRENTLY idx_products_category_active;

MySQL - 在线索引创建:

-- 迁移:20240115_003_add_performance_indexes.sql

-- 使用ALGORITHM=INPLACE和LOCK=NONE创建索引
ALTER TABLE orders
ADD INDEX idx_user_created (user_id, created_at),
ALGORITHM=INPLACE, LOCK=NONE;

-- 监控进度
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE INFO LIKE 'ALTER TABLE%';

数据转换

PostgreSQL - 数据清理迁移:

-- 迁移:20240115_004_normalize_email_addresses.sql

-- 规范化现有电子邮件地址
UPDATE users
SET email = LOWER(TRIM(email))
WHERE email != LOWER(TRIM(email));

-- 通过保留最新的删除重复项
DELETE FROM users
WHERE id NOT IN (
  SELECT DISTINCT ON (LOWER(email)) id
  FROM users
  ORDER BY LOWER(email), created_at DESC
);

-- 回滚:从备份恢复(数据更改无安全回滚)

MySQL - 批量数据更新:

-- 迁移:20240115_004_update_product_categories.sql

-- 使用JOIN更新多行
UPDATE products p
JOIN category_mapping cm ON p.old_category = cm.old_name
SET p.category_id = cm.new_category_id
WHERE p.old_category IS NOT NULL;

-- 验证更新
SELECT COUNT(*) as updated_count
FROM products
WHERE category_id IS NOT NULL;

表结构变更

PostgreSQL - 修改表迁移:

-- 迁移:20240115_005_modify_order_columns.sql

-- 添加新列
ALTER TABLE orders
ADD COLUMN status_updated_at TIMESTAMP;

-- 添加约束
ALTER TABLE orders
ADD CONSTRAINT valid_status
CHECK (status IN ('pending', 'processing', 'completed', 'cancelled'));

-- 为现有记录设置默认值
UPDATE orders
SET status_updated_at = updated_at
WHERE status_updated_at IS NULL;

-- 将列设置为NOT NULL
ALTER TABLE orders
ALTER COLUMN status_updated_at SET NOT NULL;

-- 回滚:
-- ALTER TABLE orders DROP COLUMN status_updated_at;
-- ALTER TABLE orders DROP CONSTRAINT valid_status;

测试迁移

PostgreSQL - 事务中测试:

-- 在事务中测试迁移(将被回滚)
BEGIN;

-- 运行迁移语句
ALTER TABLE users ADD COLUMN test_column VARCHAR(255);

-- 验证数据
SELECT COUNT(*) FROM users;
SELECT COUNT(DISTINCT email) FROM users;

-- 如果发现问题则回滚
ROLLBACK;

-- 或者如果一切顺利则提交
COMMIT;

验证迁移:

-- 检查迁移是否已应用
SELECT version, name, executed_at FROM schema_migrations
WHERE version = 20240115005;

-- 验证表结构
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'users'
ORDER BY ordinal_position;

回滚策略

PostgreSQL - 双向迁移:

-- 迁移文件:20240115_006_add_user_status.sql

-- ===== UP =====
CREATE TYPE user_status AS ENUM ('active', 'suspended', 'deleted');
ALTER TABLE users ADD COLUMN status user_status DEFAULT 'active';

-- ===== DOWN =====
-- ALTER TABLE users DROP COLUMN status;
-- DROP TYPE user_status;

回滚执行:

-- 函数回滚到特定版本
CREATE OR REPLACE FUNCTION rollback_to_version(p_target_version BIGINT)
RETURNS TABLE (version BIGINT, name VARCHAR, status VARCHAR) AS $$
BEGIN
  -- 按逆序执行下迁移
  RETURN QUERY
  SELECT m.version, m.name, 'rolled_back'::VARCHAR
  FROM schema_migrations m
  WHERE m.version > p_target_version
  ORDER BY m.version DESC;
END;
$$ LANGUAGE plpgsql;

生产部署

安全迁移清单:

  • 在类似生产环境的数据库上测试迁移
  • 迁移前验证备份存在
  • 在低流量窗口安排
  • 监控表锁和长时间运行的查询
  • 准备好回滚计划
  • 测试回滚程序
  • 记录所有更改
  • 尽可能在事务中运行
  • 迁移后验证数据完整性
  • 与迁移协调更新应用程序代码

PostgreSQL - 长事务安全:

-- 使用语句超时防止挂起迁移
SET statement_timeout = '30min';

-- 使用锁超时防止死锁
SET lock_timeout = '5min';

-- 使用超时运行迁移
ALTER TABLE large_table
ADD COLUMN new_column VARCHAR(255),
ALGORITHM='INPLACE';

迁移示例

组合迁移 - 多项更改:

-- 迁移:20240115_007_refactor_user_tables.sql

BEGIN;

-- 1. 从旧列创建新列并填充数据
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
UPDATE users SET full_name = first_name || ' ' || last_name;

-- 2. 添加索引
CREATE INDEX idx_users_full_name ON users(full_name);

-- 3. 添加新约束
ALTER TABLE users
ADD CONSTRAINT email_unique UNIQUE(email);

-- 4. 验证后删除旧列
-- ALTER TABLE users DROP COLUMN first_name;
-- ALTER TABLE users DROP COLUMN last_name;

COMMIT;

资源