Encore Go 应用中的 PostgreSQL 数据库:供给、迁移、查询与连接实战指南
2026/9/15 14:29:47 网站建设 项目流程

Encore Go 应用中的 PostgreSQL 数据库:供给、迁移、查询与连接实战指南

【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore

Encore 将 SQL 数据库视为一种逻辑资源:你只需在代码中声明数据库并写好迁移文件,本地encore run与云端部署都会自动完成集群创建、Schema 迁移与连接注入。本篇指南以官方文档为基础,结合本仓库源码(运行时库、CLI 与守护进程实现),完整讲解如何在 Encore Go 服务中创建数据库、编写迁移、执行增删改查、连接外部工具,并深入剖析encore_services角色与迁移出错时的处理机制。

读完本文,你将能够:在任意 Encore 服务中用两行代码声明一个 PostgreSQL 数据库;用版本化迁移文件管理 Schema;用与database/sql几乎一致的 API 读写数据;通过encore db系列命令从外部连接数据库;并在迁移失败时安全地恢复。

数据库即逻辑资源:Encore 的核心抽象

在 Encore 中,SQL 数据库是应用声明的一部分,而不是需要你手工运维的外部依赖。原生支持PostgreSQL。声明数据库后,Encore 的静态分析器会识别它,并负责:

  • 在本地开发环境用 Docker 自动拉起 PostgreSQL 集群;
  • 在云端按环境类型自动供给合适的数据库服务;
  • 自动应用迁移文件、注入连接凭证,并把每次查询接入分布式追踪。

这种"声明式资源"模型意味着你的代码里没有连接串硬编码、没有环境判断分支——同一份代码在本地、开发环境和生产环境都能直接运行。

创建数据库:sqldb.NewDatabase

创建数据库的第一步是在服务包内导入encore.dev/storage/sqldb,调用sqldb.NewDatabase并把返回值赋给包级变量。数据库必须在某个 Encore 服务 中创建。

官方示例(todo服务):

-- todo/db.go -- package todo // Create the todo database and assign it to the "tododb" variable var tododb = sqldb.NewDatabase("todo", sqldb.DatabaseConfig{ Migrations: "./migrations", }) // Then, query the database using db.QueryRow, db.Exec, etc. -- todo/migrations/1_create_table.up.sql -- CREATE TABLE todo_item ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT false -- etc... );

sqldb.DatabaseConfig中只有Migrations一个字段,用于指定存放迁移文件的目录——这是你定义数据库 Schema 的唯一入口。

从源码看,NewDatabase的定义位于 runtimes/go/storage/sqldb/pkgfn.go:

func NewDatabase(name string, config DatabaseConfig) *Database { return Singleton.GetDB(name) }

这里有几个对使用者至关重要的约束,全部记录在源码注释中:

  • 参数必须是常量字面量:Encore 依赖静态分析识别数据库及其配置,因此nameconfig不能是运行时计算出来的值;
  • 只能在包级变量声明处调用:在函数体内调用NewDatabase会直接导致编译错误;
  • 命名规范:数据库名必须在整个应用内唯一,且使用 kebab-case(小写字母数字加连字符);
  • 命名不可更改:一旦创建并部署,切勿修改数据库名,否则 Encore 会认为你声明了一个全新数据库并重新创建。

本地运行的前提:Docker

本地环境下,执行encore run时 Encore 会用 Docker 自动创建数据库集群。因此运行前请确保 Docker 已安装并处于运行状态。

一个常见的坑:如果你的应用已经在运行,再新增一个数据库定义,需要停止并重启encore run,Encore 才会用 Docker 把新数据库创建出来。

本地 Docker 驱动的实现位于 cli/daemon/sqldb/docker/docker.go:它使用官方镜像encoredotdev/postgres:18(见该文件中的Image常量),启动容器时设置POSTGRES_USER=postgresPOSTGRES_PASSWORD=postgres,并通过数据卷持久化数据。CheckRequirements方法会在 Docker 不存在或守护进程未启动时给出明确报错("This application requires docker to run since it uses an SQL database.")。数据卷按"应用 ID + 命名空间"命名,保证不同应用、不同命名空间之间的数据相互隔离。

数据库迁移:定义 Schema 的版本化方式

命名约定

迁移文件必须遵循严格的命名规范:

  • 以数字开头,后跟下划线_,数字必须依次递增
  • 文件名必须以.up.sql结尾;
  • 示例:1_first_migration.up.sql2_second_migration.up.sql3_migration_name.up.sql

为了让编辑器里的排序更美观,可以用前导零补位,例如0001_migration.up.sql

up 与 down 迁移

Encore自动执行up迁移(按顺序依次应用,每个迁移表达从上一次迁移到当前状态的增量变化),而down迁移必须手动执行,Encore 不负责回滚 Schema。

迁移目录结构

迁移文件位于服务包内的migrations目录中,每个文件命名为<number>_<name>.up.sql。典型目录结构如下:

/my-app ├── encore.app // ... and other top-level project files │ └── todo // todo service (a Go package) ├── migrations // todo service db migrations (directory) │ ├── 1_create_table.up.sql // todo service db migration │ └── 2_add_field.up.sql // todo service db migration ├── todo.go // todo service code └── todo_test.go // tests for todo service

首个迁移通常定义初始表结构,例如todo/migrations/1_create_table.up.sql

CREATE TABLE todo_item ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT false );

迁移引擎的源码细节

迁移执行逻辑位于 cli/daemon/sqldb/migrate.go。有两点值得了解:

  1. 非顺序迁移支持:Encore 的NonSequentialMigrator基于 go-migrate 库扩展而来,不要求迁移编号连续,而是通过读取schema_migrations表中已应用的版本号来决定下一个要执行的迁移。因此你可以在历史迁移文件中间插入一个新迁移(编号取中间值),Encore 只会应用那些尚未执行的。
  2. 同语句标记成功ReadUp会把insert into schema_migrations (version, dirty) values (...)直接拼接到迁移语句末尾,在同一个事务/语句中完成"执行迁移 + 标记成功",避免出现"迁移已执行但标记失败"的脏状态。

插入数据:与database/sql一致的 API

声明数据库之后,即可通过tododb变量上的方法操作数据。接口风格与 Go 标准库database/sql高度一致,参考 runtimes/go/storage/sqldb 包。

一种常见的做法是写一个使用Exec的辅助函数。例如向上面的示例 Schema 插入一条 todo 记录:

-- todo/insert.go -- // insert inserts a todo item into the database. func insert(ctx context.Context, id, title string, done bool) error { _, err := tododb.Exec(ctx, ` INSERT INTO todo_item (id, title, done) VALUES ($1, $2, $3) `, id, title, done) return err }

注意 SQL 中的占位符使用 PostgreSQL 风格的位置参数$1$2$3

除了在Database对象上调用方法,sqldb包还提供了对应的包级函数sqldb.Execsqldb.Querysqldb.QueryRowsqldb.Begin(见 pkgfn.go),它们会自动路由到"当前服务所对应的数据库"(通过getCurrentDB()实现),适合在无需显式持有数据库变量的场景使用。

从底层实现看(runtimes/go/storage/sqldb/db.go),Database.Exec基于pgxpool.Pool执行,默认连接池上限为30cfg.MaxConns = 30,可由MaxConnections配置覆盖),并在执行前后向分布式追踪系统写入DBQueryStart/DBQueryEnd事件——这意味着你在 Encore 开发面板里看到的每条 SQL 查询追踪,都来自这一层埋点。

查询数据:QueryRow、Scan 与错误处理

查询同样需要导入encore.dev/storage/sqldb(在服务包或其子包中)。例如读取上面示例 Schema 中的一条 todo:

var item struct { ID int64 Title string Done bool } err := tododb.QueryRow(ctx, ` SELECT id, title, done FROM todo_item LIMIT 1 `).Scan(&item.ID, &item.Title, &item.Done)

QueryRow预期最多返回一行。当没有匹配行时,它会报告一个错误,你应当导入标准库errors包,用errors.Is(err, sqldb.ErrNoRows)来判别。ErrNoRows定义在 sqldb.go 中(本质就是sql.ErrNoRows)。

错误分类与结构化错误

底层实现里,数据库服务器返回的错误会被统一转换为结构化的sqldb.Error(见 runtimes/go/storage/sqldb/errors.go),其中包含:

  • Code:错误大类(对应sqlerr.Code,例如外键冲突、唯一约束冲突等);
  • Severity:错误严重级别;
  • DatabaseCode:数据库服务器特定的 SQLSTATE 错误码;
  • Message:可读的错误消息;
  • SchemaName/TableName/ColumnName/DataTypeName/ConstraintName:当错误与某个数据库对象关联时提供。

你可以用sqldb.ErrCode(err)提取错误大类,实现精细化的业务错误处理(例如将唯一约束冲突映射为 HTTP 409)。同时,convertErr会把无行、事务关闭、超时、取消等情况分别包装为对应的 Encore 错误类别(NotFound、Internal、DeadlineExceeded、Canceled 等),便于与 beta/errs 的错误体系无缝衔接。

事务与底层驱动访问

需要事务时使用tododb.Begin(ctx),返回的Tx提供ExecQueryQueryRowCommitRollback方法(见 sqldb.go),语义与database/sql.Tx一致。

如果你需要把连接交给第三方库(例如 ORM 或专门工具):

  • tododb.Stdlib()返回一个连到同一数据库的*sql.DB,可直接用于sqlx、GORM 等期望*sql.DB的库(db.go);
  • sqldb.Driver*pgxpool.Pool)以类型安全的方式直接取出底层*pgxpool.Pool
  • sqldb.DriverConn(conn, func(driverConn *pgx.Conn) error {...})让你在*sql.Conn上安全地访问底层*pgx.Conn

数据库的自动供给:本地与云端

Encore 会自动按应用需求供给数据库——当你定义数据库后,下一次部署时 Encore 就会完成供给。供给方式因环境而异:

  • 本地开发:使用 Docker 创建一个数据库集群(见上文"本地运行的前提"小节);
  • 云端生产环境(production):通过所选云厂商的托管 SQL 数据库服务供给;
  • 云端开发环境(development):以Kubernetes Deployment + 持久化磁盘的方式供给。

不同云厂商、不同环境类型下具体供给的基础设施细节,参见 基础设施文档。环境类型的概念详见 环境说明。

连接数据库:从应用外部访问

有时你需要从后端应用之外连接数据库——例如跑脚本、临时查询、导出数据做分析。Encore 不会在本地环境或 Encore Cloud 环境中暴露数据库用户凭据,但提供了更安全便捷的连接字符串方案。

如果需要把外部工具(如数据管道、BI 平台)接入数据库,关于 SSL 证书与网络访问的指引,参见 连接外部工具到数据库。

使用 Encore CLI

Encore CLI 内置了三种连接数据库的方式(命令实现见 cli/cmd/encore/db.go):

encore db shell <database-name> [--env=<name>]

打开一个 psql 交互式 shell 连接到指定环境中的<database-name>数据库。省略--env时默认连接本地开发环境。默认以只读权限连接,可用以下标志提升权限(三者互斥):

标志连接权限
--write读写权限
--admin管理员权限
--superuser超级用户权限

从源码看,dbShellCmd会优先使用本机$PATH中的psql;若未安装,则回退到用docker run拉起容器内的 psql(在 macOS/Windows 上会自动把连接地址中的localhost/127.0.0.1替换为host.docker.internal以适配 Docker 网络)。此外还支持--test(连接集成测试数据库)与--shadow(连接影子数据库,用于 Prisma 等工具做漂移检测),二者都隐含--env=local。如果不指定数据库名,CLI 会在当前目录向上寻找包含migrations目录的服务并自动推断数据库名,支持目录自动补全。

encore db conn-uri <database-name> [--env=<name>]

输出一条数据库连接字符串。指定云端环境时,返回的连接字符串是临时的。省略--env默认输出本地开发环境的连接串。

encore db proxy [--env=<name>]

建立一个本地代理,把进入的连接转发到指定环境的数据库。省略--env默认连接本地开发环境。可用--port指定监听端口(默认为随机端口)。

更多数据库管理命令(如encore db reset)参见encore help db

使用数据库用户凭据(AWS/GCP)

对于 AWS/GCP 上的云端环境,可以查看 Encore 供给数据库时创建的用户凭据:打开 Encore Cloud 控制台中的应用,进入对应环境的Infrastructure页面,在相关Database ClusterUSERS区块中找到。本地环境与 Encore Cloud 环境不提供此功能。

处理迁移错误

Encore 应用迁移时,迁移不一定是干净的,失败原因可能包括:

  • 迁移文件中的 SQL 语法错误;
  • 试图添加UNIQUE约束,但表中现有数据并不唯一;
  • 现有数据库 Schema 与预期不符,要修改的数据库对象实际不存在;
  • 以及其他各种原因。

一旦失败,Encore 会回滚该迁移;如果发生在云端部署期间,整个部署会被中止。修复问题后,本地重新执行encore run,云端推送更新后的代码,即可重试。

schema_migrations

Encore 通过schema_migrations表跟踪已应用的迁移:

database=# \d schema_migrations Table "public.schema_migrations" Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- version | bigint | | not null | dirty | boolean | | not null | Indexes: "schema_migrations_pkey" PRIMARY KEY, btree (version)

version列记录最后一次应用的迁移版本。如果想跳过某个迁移或重新执行某个迁移,直接修改这一列的值即可。例如要重跑最后一个迁移,执行:

UPDATE schema_migrations SET version = version - 1;

注意:Encore 默认不使用dirty标志(从 migrate.go 的SetVersion实现可以看到,PSQL 下迁移在同一事务内执行,失败会自动回滚,因此无需标记 dirty)。

encore_services角色:连接迁移与运行时的权限桥

Encore 使用一个共享数据库角色encore_services,在迁移执行的权限服务运行时连接数据库的权限之间搭建桥梁。这个角色在所有环境中都存在——本地和云端一致。

它的工作方式非常简洁:

  • 所有执行迁移的角色都被授予encore_services
  • 所有服务运行时连接数据库的角色也都被授予encore_services

这意味着:任何由encore_services拥有的对象,或授予encore_services的权限,迁移角色和运行时服务角色都能自动访问。你可以利用迁移脚本给encore_services授予额外权限,这些权限会被运行时的服务角色继承。

从 cli/daemon/sqldb/cluster.go 的集群角色创建逻辑可以看到具体实现:迁移角色(encore-migrator)被授予GRANT encore_services TO ... WITH ADMIN OPTION(带管理员选项,使其可以把角色转授出去),而服务角色(encore-service)被授予GRANT encore_services TO ...(仅继承)。此外 PostgreSQL 14+ 环境下还会创建encore-readencore-write等预定义角色,并授予pg_read_all_data/pg_write_all_data系统预置角色权限。

物化视图示例

物化视图是encore_services最有价值的应用场景之一:它在迁移期间创建,但需要在运行时由服务角色刷新。通过"创建专用属主角色 → 授予encore_services→ 在创建视图时切换为该角色",就能把这一缺口补上:

-- In a migration file CREATE ROLE matview_owner; GRANT matview_owner TO encore_services; SET ROLE matview_owner; CREATE MATERIALIZED VIEW my_view AS SELECT id, count(*) AS total FROM todo_item GROUP BY id; RESET ROLE;

运行时,服务角色通过encore_services继承来的权限即可扮演matview_owner并刷新视图:

_, err := tododb.Exec(ctx, `SET ROLE matview_owner`) _, err = tododb.Exec(ctx, `REFRESH MATERIALIZED VIEW my_view`) _, err = tododb.Exec(ctx, `RESET ROLE`)

旧版行为(Legacy)

在 Encore 早期版本中,迁移使用与服务运行时同一个数据库账号执行。如需恢复这一旧行为,设置以下环境变量:

ENCOREDEBUG="sqldbrole=legacy"

该选项仅为向后兼容而保留,新应用不建议使用

小结与推荐阅读

至此,你已经掌握了 Encore Go 应用中 SQL 数据库的完整使用闭环:

  1. 声明:用sqldb.NewDatabase+DatabaseConfig{Migrations}声明数据库(运行时入口);
  2. 建模:用编号递增的.up.sql迁移文件定义 Schema;
  3. 读写:用Exec/Query/QueryRow/Begin操作数据(运行时实现);
  4. 供给与连接:本地自动用 Docker 拉起(Docker 驱动),云端按环境类型自动供给,用encore db系列命令从外部连接(CLI 实现);
  5. 排障:通过schema_migrations表处理迁移失败,利用encore_services角色打通迁移与运行时的权限。

如果你想进一步深入,推荐阅读仓库中的相关文档:服务概念、数据库迁移与 Schema 变更、共享数据库、连接现有数据库,以及包含完整 SQL 数据库示例的 uptime 教程。

【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询