Files
lab/go/projects/lyra/docs/goose.md
T
labmem 8c0c9978e5 feat(lyra): 接入 PostgreSQL 与 Goose 迁移
- 使用 pgx/sqlx 建立连接,从环境变量读取数据库密码
- 添加本地 PostgreSQL Compose 配置与初始迁移
- 补充 Goose 操作手册和连接串排错说明
2026-10-17 15:09:46 +08:00

7.3 KiB
Raw Blame History

Goose 迁移操作手册

Lyra 数据库迁移的唯一完整操作手册。migrations/ 目录是数据库结构的 唯一正式执行来源;demo.sql 仅保留作参考,不要再手工执行它。 迁移目录本身的说明见 migrations/README.md。

目录与前提

  • 所有 goose 命令都在 Lyra module 根目录(migrations/ 的上一级)执行:

    cd go/projects/lyra   # 从仓库根出发
    
  • 目标数据库已创建:迁移不负责建库,lab 库需先存在。

  • 首次迁移要求目标对象尚不存在:如果之前手工执行过 demo.sql 建过 同名对象,up 会报 already exists,先按故障表评估处理。

安装 goose CLI(固定 v3.29.0)

goose 只作为独立命令行工具使用(Go 应用尚未引入 goose 依赖):

go install -tags='no_clickhouse no_libsql no_mssql no_mysql no_sqlite3 no_vertica no_ydb' github.com/pressly/goose/v3/cmd/goose@v3.29.0
goose -version   # 应输出 v3.29.0
  • 固定 @v3.29.0,保证各机器行为一致。

  • -tags=... 是可选的 PostgreSQL 专用精简构建(no_* 标签已在 goose v3.29.0 源码中核实);不追求精简可省略。

  • 二进制装到 $(go env GOPATH)/bin(设了 GOBIN 则装到 GOBIN)。 goose -version 提示找不到命令时,把该目录加入 PATH:

    export PATH="$PATH:$(go env GOPATH)/bin"   # 可写入 ~/.bashrc
    

配置连接:.env.local

goose 不读取应用的 etc/lyra.toml,也不会只凭 PG_PASSWORD 之类的 应用环境变量自动拼出连接串——它只认自己环境变量里的 GOOSE_DBSTRING。 在 module 根创建 .env.local 并追加以下内容(全部是占位值,换成实际 信息;lab 库需已创建):

GOOSE_DRIVER=postgres
GOOSE_MIGRATION_DIR=./migrations
GOOSE_DBSTRING="postgres://example_user:URL_ENCODED_PASSWORD@127.0.0.1:5432/lab?sslmode=disable"

要点:

  • GOOSE_DRIVER=postgres 是 goose 的驱动名;Go 应用内用的是 pgx (驱动名 pgx),两边名字不同但都连 PostgreSQL,不要混填。

  • 本仓库 .gitignore 已忽略 .env*,.env.local 不会提交;建议再执行 chmod 600 .env.local 收紧文件权限。

  • 127.0.0.1 与端口必须换成实际可达的地址和端口(远程/容器按实际 映射填写)。

  • sslmode=disable 仅适用于本地确认无 TLS 要求的场景;远程环境按部署的 TLS 要求调整该参数。

  • 本手册所有 goose 命令统一用 -env=.env.local 显式加载该文件:goose 默认读的是 .env,-env=.env.local 既明确来源、又避免误用其他本地 配置。连接信息配置一次即可,无需每次交互输入密码。

  • 系统环境里已有的同名变量(如曾 export 过 GOOSE_DBSTRING)可能优先 于 .env.local 生效:改了 .env.local 仍行为异常时,先查是否存在同名 环境变量——只列变量名、不打印值,避免泄露连接串:

    env | cut -d= -f1 | grep -i '^GOOSE'
    

连接串特殊字符:URL 编码

GOOSE_DBSTRING 是 URL 格式:用户名、密码中的特殊字符必须逐个做 百分号编码,否则主机名/路径解析会错位。

  • 对用户名和密码分别编码,不要把整串 URL 一起编码,也不要为迁就 DSN 去修改数据库里的实际密码。避免对已经编码的内容重复编码。
  • 不要把真实密码交给在线网站转换;按下表手工替换即可。
字符 编码 字符 编码
@ %40 : %3A
# %23 / %2F
% %25 ? %3F

示例:密码 demo#pass(演示值)在 DSN 中写成 demo%23pass。

常用命令

# 离线检查迁移文件能否被 goose 解析(注解与语句拆分);
# 不连接数据库,也不验证 SQL 在 PostgreSQL 上的语义
goose -env=.env.local validate

# goose 工具自身版本(与数据库里的迁移版本是两回事)
goose -version

# 连接数据库:查看迁移状态 / 当前版本
goose -env=.env.local status
goose -env=.env.local version

# 执行迁移(连接数据库)
goose -env=.env.local up

注意:v3.29.0 的 validate 不接受位置路径参数,迁移目录只由 GOOSE_MIGRATION_DIR(.env.local 已设 ./migrations)或 -dir 标志 决定。validate 通过只证明文件能解析,不证明连接正常或 SQL 语义正确。

首次初始化

前提:lab 库已创建,且 00001 中的对象(函数、三张表、索引、触发器) 都不存在——不要先手工执行 demo.sql 再 up。

goose -env=.env.local validate   # 离线确认文件可解析
goose -env=.env.local status     # 显示 00001 Pending:连接成功,该迁移尚未应用
goose -env=.env.local up         # 执行 00001,并在 goose_db_version 中记录版本:
                                 # set_updated_at() 函数、users/categories/links 三张表、
                                 # 约束、注释、索引与触发器
goose -env=.env.local status     # 00001 显示应用时间
goose -env=.env.local version    # 显示 version 1
  • up 可重复执行:已应用版本按 goose_db_version 自动跳过。
  • 已执行过的迁移文件不再修改;要变更结构就新增迁移文件(见下节)。

日常新增迁移

goose -env=.env.local -s create add_example_column sql

-s 表示顺序编号:在现有 00001 基础上生成 migrations/00002_add_example_column.sql(实际编号按目录中现有最大版本 延续)。例如给 users 表加一列,编辑生成的文件:

-- +goose Up
ALTER TABLE public.users
    ADD COLUMN avatar_url text NOT NULL DEFAULT '';

-- +goose Down
ALTER TABLE public.users
    DROP COLUMN avatar_url;

然后按顺序执行:

goose -env=.env.local validate
goose -env=.env.local status
goose -env=.env.local up
goose -env=.env.local status

Up/Down 成对编写,保证迁移可回滚。

回滚警告

goose -env=.env.local down   # ⚠ 危险操作,见下
  • down 只回滚最近一个已应用迁移的 Down 部分,不是清库或重建的 通用命令。
  • 回滚 00001 会删除 links/categories/users 三张表及表中全部数据。
  • 执行任何 down 之前:阅读对应迁移 SQL、评估数据损失、备份数据库。 不建议随手“试一下” down,也不要把它列入日常维护流程。

故障速查

报错特征 常见原因与处理
hostname resolving error(主机名里混入非法字符) DBSTRING 格式或 URL 编码不对:检查用户名/密码特殊字符是否已按上表编码、:// 与 @ : 分隔符是否完整
password authentication failed 用户名或密码不对:核对 .env.local(编码后)的值;再排查系统环境同名变量是否优先生效(只查变量名,不打印值)
connection refused / i/o timeout 主机端口不可达:确认 PostgreSQL 实际监听地址、端口、容器端口映射与防火墙
already exists(对象已存在) 目标对象曾被手工创建(如执行过 demo.sql):先评估现有对象与迁移的差异再决定处理方式;不要直接删数据,也不要手工往 goose_db_version 插版本记录伪造状态