下载工作台
Go 数据库实战

gin-web GORM 对照速通

试读上半部分 · 解锁后可读全文

第 12 章 · golang-migrate 迁移与种子数据

本章目标:在 shop-db 中集成 golang-migrate;编写 V1__init.sql 迁移 ch07 完整 DDL;配置 application 或 go mod 插件 执行 migrate;编写 幂等种子数据 Go/SQL 脚本;理解 validate + golang-migrate 与 GORM 协作;对照 python-database ch11 Alembic 工作流异同。

学时建议:5~6 小时(含 2 小时迁移跟练)

前置:完成 go-database ch07(DDL 基线)与 ch10(GORM validate);了解 python-database ch11(Alembic)。


12.1 场景说明:schema 必须可版本化

痛点手工 SQLgolang-migrate
多人改表冲突难以合并db/migration/V*.sql 可 code review
生产升级手工执行易漏flyway migrate
回滚无标准社区版靠补偿脚本;或 undo(Teams)
与 GORM 同步易漂移golang-migrate 建表 + GORM validate

shop-db 演进示例:

  1. V1:ch07 六表 + 购物车
  2. V2:ch13 增加 tags / product_tags(练习)
  3. V3products 增加 cover_url(练习)
对照 python-database ch11 Alembic:db-demo 用 autogenerate 对比 ORM metadata;shop-db 用 手写 SQL 迁移(Go 生态常见,与 Spring Boot 默认一致)。

12.2 golang-migrate 与 Alembic 概念对照

概念golang-migrate (Go)Alembic (Python)
版本表flyway_schema_historyalembic_version
脚本位置db/migration/V1__xxx.sqlalembic/versions/*.py
执行命令flyway migratealembic upgrade head
回滚手写 V{n+1} 补偿downgrade -1
自动生成无(手写 SQL)revision --autogenerate
Spring 集成spring.flyway.*需手动/FastAPI 启动钩子

共同点:版本链单调递增;禁止修改已发布到 main 的历史脚本


12.3 go mod 集成 golang-migrate

go.mod

<properties>
    <flyway.version>10.10.0</flyway.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-maven-plugin</artifactId>
            <version>${flyway.version}</version>
            <configuration>
                <url>mysql DSNmysql://127.0.0.1:3306/shop_db?useSSL=false&amp;serverTimezone=Asia/Shanghai</url>
                <user>shop_user</user>
                <password>shop_pass</password>
                <locations>
                    <location>classpath:db/migration</location>
                </locations>
            </configuration>
        </plugin>
    </plugins>
</build>

目录结构

~/go-learn/shop-db/
├── src/main/resources/db/migration/
│   ├── V1__init.sql
│   └── V2__add_tags.sql          # 练习,可选
├── src/main/resources/db/seed/
│   └── R__seed_shop.sql          # Repeatable 种子(可选)
└── go.mod
前缀含义
V{version}__{description}.sql版本化迁移,只执行一次
R__{description}.sql可重复执行(checksum 变则重跑)
U{version}__Undo(golang-migrate Teams)

12.4 V1__init.sql 完整 DDL

将 ch07 §7.4~§7.8 合并为 V1__init.sql

-- V1__init.sql
-- shop-db baseline schema (go-database ch07/ch12)
-- charset: utf8mb4, engine: InnoDB

CREATE TABLE IF NOT EXISTS categories (
    id          BIGINT       NOT NULL AUTO_INCREMENT,
    name        VARCHAR(64)  NOT NULL,
    slug        VARCHAR(64)  NOT NULL,
    sort_order  INT          NOT NULL DEFAULT 0,
    created_at  DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updated_at  DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
                    ON UPDATE CURRENT_TIMESTAMP(3),
    PRIMARY KEY (id),
    UNIQUE KEY uk_categories_slug (slug),
    KEY idx_categories_sort (sort_order, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS products (
    id            BIGINT        NOT NULL AUTO_INCREMENT,
    category_id   BIGINT        NOT NULL,
    title         VARCHAR(128)  NOT NULL,
    slug          VARCHAR(128)  NOT NULL,
    description   TEXT          NULL,
    price         BIGINT        NOT NULL COMMENT '单价,单位:分',
    stock         INT           NOT NULL DEFAULT 0,
    is_published  TINYINT(1)    NOT NULL DEFAULT 0,
    created_at    DATETIME(3)   NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updated_at    DATETIME(3)   NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
                      ON UPDATE CURRENT_TIMESTAMP(3),
    PRIMARY KEY (id),
    UNIQUE KEY uk_products_slug (slug),
    KEY idx_products_category_published (category_id, is_published, created_at DESC),
    KEY idx_products_published_price (is_published, price),
    CONSTRAINT fk_products_category
        FOREIGN KEY (category_id) REFERENCES categories(id)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT chk_products_price CHECK (price >= 0),
    CONSTRAINT chk_products_stock CHECK (stock >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- users, orders, order_lines, carts, cart_items 同 ch07,此处省略以节省篇幅
-- 跟练时必须写全 7 张表
跟练要求V1__init.sql 必须包含 ch07 全部 7 表,不得省略 users/orders/order_lines/carts/cart_items

与 ch07 shop_db.sql 关系

文件用途
ch07 schema/shop_db.sql学习 DDL、手动执行
ch12 V1__init.sql版本化权威来源,golang-migrate 执行

内容应一致;后续只改 V2__ 增量,不改 V1__(已发布则禁止改)。


12.5 执行迁移

cd ~/go-learn/shop-db

# 确保空库或仅 flyway 表
mysql -h 127.0.0.1 -u shop_user -pshop_pass -e "CREATE DATABASE IF NOT EXISTS shop_db;"

# go mod 插件
mvn -q flyway:migrate

# 验证
mysql -h 127.0.0.1 -u shop_user -pshop_pass shop_db -e "SHOW TABLES;"
mysql -h 127.0.0.1 -u shop_user -pshop_pass shop_db \
  -e "SELECT installed_rank, version, description, success FROM flyway_schema_history;"
命令作用
flyway:migrate应用未执行版本
flyway:info查看 pending/applied
flyway:validate校验 checksum
flyway:clean删库全部对象,仅 dev!

12.6 纯 Go 启动 golang-migrate(无 go mod 插件)

package com.example.shopdb.flyway;

import org.flywaydb.core.golang-migrate;
import gox.sql.DataSource;
import com.example.shopdb.pool.HikariDataSourceFactory;

public class golang-migrateMigrator {

    public static void migrate() {
        DataSource ds = HikariDataSourceFactory.create();
        golang-migrate flyway = golang-migrate.configure()
            .dataSource(ds)
            .locations("classpath:db/migration")
            .load();
        flyway.migrate();
    }

    public static void main(String[] args) {
        migrate();
        System.out.println("golang-migrate migrate 完成");
    }
}

go mod 依赖:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>10.10.0</version>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
    <version>10.10.0</version>
</dependency>

启动顺序(shop-db 应用):

1. golang-migrate.migrate()     → 表结构最新
2. GORM EMF validate     → 实体与表一致
3. 业务逻辑              → Repository / gorm.DB
4. (可选)SeedRunner    → 种子数据

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。