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 依赖静态分析识别数据库及其配置,因此
name和config不能是运行时计算出来的值; - 只能在包级变量声明处调用:在函数体内调用
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=postgres、POSTGRES_PASSWORD=postgres,并通过数据卷持久化数据。CheckRequirements方法会在 Docker 不存在或守护进程未启动时给出明确报错("This application requires docker to run since it uses an SQL database.")。数据卷按"应用 ID + 命名空间"命名,保证不同应用、不同命名空间之间的数据相互隔离。
数据库迁移:定义 Schema 的版本化方式
命名约定
迁移文件必须遵循严格的命名规范:
- 以数字开头,后跟下划线
_,数字必须依次递增; - 文件名必须以
.up.sql结尾; - 示例:
1_first_migration.up.sql、2_second_migration.up.sql、3_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。有两点值得了解:
- 非顺序迁移支持:Encore 的
NonSequentialMigrator基于 go-migrate 库扩展而来,不要求迁移编号连续,而是通过读取schema_migrations表中已应用的版本号来决定下一个要执行的迁移。因此你可以在历史迁移文件中间插入一个新迁移(编号取中间值),Encore 只会应用那些尚未执行的。 - 同语句标记成功:
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.Exec、sqldb.Query、sqldb.QueryRow、sqldb.Begin(见 pkgfn.go),它们会自动路由到"当前服务所对应的数据库"(通过getCurrentDB()实现),适合在无需显式持有数据库变量的场景使用。
从底层实现看(runtimes/go/storage/sqldb/db.go),Database.Exec基于pgxpool.Pool执行,默认连接池上限为30(cfg.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提供Exec、Query、QueryRow、Commit、Rollback方法(见 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 Cluster的USERS区块中找到。本地环境与 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-read、encore-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 数据库的完整使用闭环:
- 声明:用
sqldb.NewDatabase+DatabaseConfig{Migrations}声明数据库(运行时入口); - 建模:用编号递增的
.up.sql迁移文件定义 Schema; - 读写:用
Exec/Query/QueryRow/Begin操作数据(运行时实现); - 供给与连接:本地自动用 Docker 拉起(Docker 驱动),云端按环境类型自动供给,用
encore db系列命令从外部连接(CLI 实现); - 排障:通过
schema_migrations表处理迁移失败,利用encore_services角色打通迁移与运行时的权限。
如果你想进一步深入,推荐阅读仓库中的相关文档:服务概念、数据库迁移与 Schema 变更、共享数据库、连接现有数据库,以及包含完整 SQL 数据库示例的 uptime 教程。
【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考