数据库迁移
数据库迁移工具将 Schema 变更(建表、加列、加索引)纳入版本控制,与代码一起发布,解决手动执行 SQL 脚本混乱、环境不一致的问题。
Flyway vs Liquibase
| 对比 | Flyway | Liquibase |
|---|---|---|
| 配置格式 | SQL 文件(主)/ Java | YAML / XML / JSON / SQL |
| 学习成本 | 低,约定优于配置 | 中,需理解 changeset 概念 |
| 回滚支持 | 付费版支持(开源版无) | 原生支持 rollback |
| 版本管理 | 顺序版本号(V1, V2…) | changeSet id + author |
| 适合场景 | 简单、纯 SQL 迁移 | 需要回滚、多格式、复杂变更 |
Flyway
引入依赖
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.flywaydb:flyway-core'
// MySQL 8+
implementation 'org.flywaydb:flyway-mysql'配置
spring:
flyway:
enabled: true
locations: classpath:db/migration # 脚本目录
baseline-on-migrate: true # 对已有数据库首次使用时建基线
baseline-version: 0 # 基线版本号
validate-on-migrate: true # 迁移前校验已执行脚本的 checksum
out-of-order: false # 禁止乱序执行(如 V3 已执行,不允许再跑 V2)
table: flyway_schema_history # 历史记录表名
placeholders: # 脚本中的占位符替换
schema: mydb
env: prod命名规则
db/migration/
├── V1__Create_users_table.sql ← 版本迁移(不可修改)
├── V2__Add_email_to_users.sql
├── V3__Create_orders_table.sql
├── V3_1__Add_orders_index.sql ← 子版本
├── R__Refresh_user_stats_view.sql ← 可重复迁移(内容变化即重新执行)
└── U3__Undo_orders_table.sql ← 撤销迁移(Flyway Teams)
命名格式:{前缀}{版本}__{描述}.sql
V:版本迁移(只执行一次,执行后不能修改)R:可重复迁移(视图、存储过程,checksum 变化则重新执行)- 双下划线
__分隔版本号和描述,描述中空格用_代替
SQL 脚本示例
-- V1__Create_users_table.sql
CREATE TABLE users (
id BIGINT NOT NULL AUTO_INCREMENT,
username VARCHAR(64) NOT NULL,
email VARCHAR(128) NOT NULL,
password VARCHAR(256) NOT NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- V2__Add_phone_to_users.sql
ALTER TABLE users
ADD COLUMN phone VARCHAR(20) NULL AFTER email,
ADD INDEX idx_phone (phone);-- V3__Create_orders_table.sql
CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(12, 2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no),
INDEX idx_user_id (user_id),
INDEX idx_status_created (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Java 迁移(复杂数据初始化)
// db/migration/V4__Migrate_user_data.java(需放在 db.migration 包下)
@Component
public class V4__Migrate_user_data implements JavaMigration {
@Override
public MigrationVersion getVersion() {
return MigrationVersion.fromVersion("4");
}
@Override
public String getDescription() { return "Migrate user data"; }
@Override
public Integer getChecksum() { return 42; } // 手动指定,修改时记得更改
@Override
public boolean isUndo() { return false; }
@Override
public boolean canExecuteInTransaction() { return true; }
@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
// 复杂数据迁移逻辑
stmt.execute(
"UPDATE users SET display_name = username WHERE display_name IS NULL"
);
}
}
}常用 Flyway 命令
# Maven
mvn flyway:migrate # 执行所有待执行的迁移
mvn flyway:validate # 验证迁移脚本完整性(checksum 校验)
mvn flyway:info # 显示所有迁移的状态
mvn flyway:repair # 修复失败的迁移记录(failed 状态)
mvn flyway:baseline # 对已有数据库建立基线(跳过历史变更)
mvn flyway:clean # ⚠️ 危险:清空整个数据库(生产禁用)Liquibase
引入依赖
implementation 'org.liquibase:liquibase-core'配置
spring:
liquibase:
enabled: true
change-log: classpath:db/changelog/db.changelog-master.yaml
default-schema: mydb
contexts: prod # 只执行标记了 prod context 的 changesetChangelog 结构
# db/changelog/db.changelog-master.yaml(主入口,引入各版本)
databaseChangeLog:
- include:
file: db/changelog/v1/create-tables.yaml
- include:
file: db/changelog/v2/add-columns.yaml
- include:
file: db/changelog/v3/seed-data.yaml# db/changelog/v1/create-tables.yaml
databaseChangeLog:
- changeSet:
id: 1-create-users
author: zhangsan
context: prod, dev # 在 prod 和 dev 环境执行
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: username
type: VARCHAR(64)
constraints:
nullable: false
unique: true
- column:
name: email
type: VARCHAR(128)
constraints:
nullable: false
rollback:
- dropTable:
tableName: users
- changeSet:
id: 2-add-phone
author: lisi
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: VARCHAR(20)
- createIndex:
tableName: users
indexName: idx_phone
columns:
- column:
name: phone
rollback:
- dropIndex:
tableName: users
indexName: idx_phone
- dropColumn:
tableName: users
columnName: phoneSQL 格式的 Changeset(复杂 SQL 直接写)
- changeSet:
id: 5-complex-migration
author: wangwu
changes:
- sql:
sql: |
UPDATE orders
SET status = 'COMPLETED'
WHERE status = 'PAID' AND updated_at < NOW() - INTERVAL 30 DAY;
rollback:
- sql:
sql: |
UPDATE orders
SET status = 'PAID'
WHERE status = 'COMPLETED' AND updated_at > NOW() - INTERVAL 30 DAY;测试环境处理
# application-test.yml
spring:
flyway:
locations:
- classpath:db/migration # 正式迁移脚本
- classpath:db/testdata # 测试数据(仅测试环境)
clean-on-validation-error: true # 测试时可自动清理并重建(生产禁用)// 测试中使用 H2 内存数据库 + Flyway
@SpringBootTest
@ActiveProfiles("test")
class OrderRepositoryTest {
// Flyway 自动在测试启动时执行迁移
}CI/CD 集成最佳实践
开发 → 编写迁移脚本 → 提交 Git
│
CI 流水线 → 运行测试(H2 + Flyway)→ 通过
│
CD 部署 → 应用启动前先执行 Flyway migrate
→ 迁移成功后启动应用
→ 迁移失败则部署中止(保护线上数据)
Spring Boot 默认在应用启动时自动执行 flyway:migrate,无需手动触发。
生产环境安全规则:
- 禁用
flyway.clean-on-validation-error(默认已禁用) - 禁用
flyway.clean-disabled: false(默认已禁用) - 迁移脚本一旦提交执行,永远不要修改(会导致 checksum 校验失败)
- 数据变更(UPDATE / DELETE)需在脚本中备份原始数据
相关链接
- 数据访问 — JPA / MyBatis 与数据库 Schema 的关系
- 事务管理 — 迁移脚本执行时的事务控制
- 多数据源 — 多数据源场景下每个数据源独立配置 Flyway 实例
- 环境与Profile — 测试环境与生产环境使用不同迁移脚本
- Docker部署 — 容器启动时自动执行迁移的时序问题