Skip to main content
FastAPI 是一个异步 Python Web 框架,SQLAlchemy 则是使用最广泛的 Python ORM。在本指南中,您将定义一个带类型注解的 SQLAlchemy 模型,通过 Alembic 迁移创建对应的表,并使用 FastAPI 提供增、查、改、删 (CRUD) 端点。该应用使用 asyncpg 驱动程序,所有连接均启用 TLS,并执行完整的证书验证。

前置条件

  • uv 以及 Python 3.11 或更高版本。本指南已在 Python 3.13、FastAPI 0.142、SQLAlchemy 2.1、asyncpg 0.31、Alembic 1.20 和 Uvicorn 0.54 环境下测试通过。
  • 一个 ClickHouse Cloud 账户
  • psql,用于创建数据库。也可以在 SQL 控制台中运行 CREATE DATABASE 语句。

创建 ClickHouse Managed Postgres 服务

在 ClickHouse Cloud 控制台中,点击 New service,然后选择 Postgres。几分钟后实例即可就绪。详细操作步骤请参阅快速入门。

获取连接详细信息

在服务的左侧边栏中点击 Connect。弹出的对话框会显示您的用户名、密码、服务器和端口,并提供 Directly / via PgBouncer 切换开关。 本指南会同时用到这两种连接:
  • PgBouncer (端口 6432) :供应用使用。SQLAlchemy 会在每个 Uvicorn 工作线程中维护一个连接池,而内置的 PgBouncer 可让众多工作线程和副本共享少量 Postgres 连接。它以事务池化模式运行。
  • 直连 (端口 5432) :供 Alembic 使用。迁移需要执行 DDL,可能耗时较长,还可能依赖会话状态,因此应直接连接 Postgres。
开启 Use SSL 并点击 Download CA certificate。您也可以在 Settings → CA Certificate 中下载。该证书为您的实例所独有,因此驱动程序可以借助它验证所连接的确实是您的服务器。 开启 Use SSL 后,对话框中的 URL 会以 sslmode=verify-full&sslrootcert=... 结尾。asyncpg 不会读取这些参数,因此只需从对话框中复制密码和主机即可。CA 证书则由应用在代码中传入 (参见配置连接) 。

设置项目

创建一个项目,然后安装 FastAPI、Uvicorn、带有 asyncio extra 的 SQLAlchemy、asyncpg 和 Alembic:
要集成到现有应用中?跳过 uv init 和 mkdir 命令。在项目中运行 uv add "sqlalchemy[asyncio]" asyncpg alembic,然后继续阅读配置连接。如果您的应用通过异步 SQLAlchemy 使用 SQLite:
  • 将 create_async_engine 调用修改为与 app/db.py 中的一致。在 migrations/env.py 中,从您自己的模块导入 Base 和模型。
  • 从启动流程中移除 Base.metadata.create_all。Alembic 会在 Postgres 中创建您现有的表,但不会从 SQLite 迁移数据。

配置连接

将下载的 CA 证书移动到项目目录中,并重命名为 ca-certificate.pem。 为应用创建一个数据库。将 <PASSWORD> 和主机地址替换为 Connect 弹窗中显示的值:
在项目根目录中创建 .env 文件,或将这些变量添加到现有的 .env 文件中。这两个 URL 仅端口不同,均使用 postgresql+asyncpg 方案 (scheme) ,且不带任何 SSL 参数:
.env
DATABASE_CA_CERT 的路径相对于执行命令时所在的目录进行解析。如果密码中包含 @、/ 或 # 等特殊字符,请对其进行 URL 编码。请勿将 .env 纳入版本控制。 创建 app/db.py。该文件负责构建 TLS 设置、引擎,以及供应用其他部分共用的会话工厂:
app/db.py
使用 PROTOCOL_TLS_CLIENT 创建的 ssl.SSLContext 要求证书有效,并会校验主机名,效果等同于 sslmode=verify-full。SQLAlchemy 会通过 connect_args 将其传递给 asyncpg。
通过 connect_args 传递 CA 证书
  • 如果未提供 ssl 参数,asyncpg 会在服务器支持 TLS 时使用 TLS,但不会验证证书。
  • 不要将 Connect 弹窗中的 sslmode 或 sslrootcert 复制到 URL 中。SQLAlchemy 会将 URL 参数作为关键字参数传递给 asyncpg,导致连接失败并报错 TypeError: connect() got an unexpected keyword argument 'sslmode'。
  • 在 Python 3.13 及更高版本中,ssl.create_default_context 会启用严格的 X.509 检查,因此用它创建的上下文会导致连接失败并报错 certificate verify failed: Missing Authority Key Identifier。请按示例使用 ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 创建上下文。
  • 如果 CA 证书不正确,连接会失败并报错 certificate verify failed: unable to get local issuer certificate。如果主机名不匹配 (例如通过 IP 地址连接) ,则会报错 certificate verify failed: IP address mismatch。
如果您希望将 TLS 设置保留在 URL 中,或者您的应用使用同步 SQLAlchemy 会话,请改用 psycopg 驱动程序来替代 asyncpg。它基于 libpq 构建,可直接使用 Connect 弹窗中的参数,例如 postgresql+psycopg://...:5432/guide_fastapi?sslmode=verify-full&sslrootcert=ca-certificate.pem,并且同时支持 create_engine 和 create_async_engine。

定义模型

创建 app/models.py,并在其中定义一个带类型注解的 SQLAlchemy 模型:
app/models.py

运行迁移

基于 async 模板创建 Alembic 环境:
替换自动生成的 migrations/env.py 文件内容。新版本会加载你的模型以供 autogenerate 使用,并使用与应用相同的 TLS 设置直接连接 Postgres:
migrations/env.py
Alembic 会忽略 alembic.ini 中的占位值 sqlalchemy.url,因为 env.py 会自行创建引擎。 根据模型生成迁移脚本,然后执行迁移:
检查 migrations/versions 目录中生成的文件,并将其与代码一同提交。Alembic 会在 alembic_version 表中记录已应用的修订版本。之后每次修改模型,都需要重新运行 revision --autogenerate 和 upgrade head。

添加 API 端点

创建 app/main.py。每个请求都会通过 FastAPI 依赖项获取独立的 AsyncSession,应用关闭时,引擎的连接池也会随之关闭:
app/main.py
SQLAlchemy 会将每个会话中的语句放在同一个事务中执行,该事务在调用 session.commit 时结束。会话关闭时,所有未提交的操作都会被回滚。执行 INSERT 时,SQLAlchemy 会通过 RETURNING 读回生成的 id 和 created_at,因此 create_todo 无需再额外执行一次查询。

运行与验证

启动应用:
在另一个终端中,创建两个待办事项,将第一个标记为已完成,然后删除第二个:
DELETE 请求返回 204 No Content。如果请求的 todo 不存在,则返回 404:
在浏览器中打开 http://localhost:3112/todos 可列出剩余的待办事项;打开 http://localhost:3112/docs 则可在 FastAPI 的交互式 API 文档中试用各个端点。 要确认直连使用的是经过验证的 TLS,请使用与应用相同的 TLS 设置查询 pg_stat_ssl:
要在控制台中查看数据,请在服务的左侧边栏中打开 SQL 控制台,依次展开 guide_fastapi 和 public,然后点击 todos 表。旁边的 alembic_version 表保存着迁移历史记录。todos 表中只剩下一行数据: 按 Ctrl+C 停止应用。

使用 PgBouncer

使用默认的 asyncpg 和 SQLAlchemy 设置时,应用可以通过 PgBouncer 正常运行:
  • asyncpg 会将每个查询作为协议层的命名预处理语句执行,SQLAlchemy 则为每个连接最多缓存 100 条此类语句。内置的 PgBouncer 在事务池化模式下支持这些语句,而且 asyncpg 通过协议层的 Close 消息 (而非 SQL DEALLOCATE) 释放它们。因此无需设置 statement_cache_size=0,也无需自定义 prepared_statement_name_func。如果您仍要关闭 asyncpg 的语句缓存,还需在 URL 中添加 prepared_statement_cache_size=0,以同时关闭 SQLAlchemy 的缓存。
  • 通过 SET 设置的会话设置 (例如 search_path) 不会延续到下一个事务,还可能泄漏给其他客户端。请改为在事务内使用 SET LOCAL。
  • pg_stat_ssl 反映的是 PgBouncer 与 Postgres 之间的连接,因此通过端口 6432 连接时,上文的 TLS 检查会返回 (False, None)。但应用到 PgBouncer 的连接仍会经过验证:使用错误的 CA 证书时,连接同样会失败。
  • Alembic 不使用 advisory 锁,且每次 upgrade 都在单个事务中执行,因此同样可以通过 PgBouncer 运行。不过对于耗时较长的迁移,直连仍是更好的选择。
更改列的类型PgBouncer 会在其到 Postgres 的连接上保留预处理语句。如果迁移更改了某个查询所返回列的类型 (例如从 integer 改为 bigint) ,该查询通过 PgBouncer 执行时就会报错 InvalidCachedStatementError: cached statement plan is invalid due to a database schema or configuration change,即使使用新连接也不例外,直到 PgBouncer 替换其到 Postgres 的连接为止。要解决此问题,请通过直连、以应用所用的同一用户身份,关闭 PgBouncer 到该数据库的 Postgres 连接。由于 PgBouncer 在本地连接 Postgres,其连接没有 client_addr,因此该查询不会影响直连。之后 PgBouncer 会建立新连接;在 SQLAlchemy 刷新缓存之前,每个池化的应用连接可能还会再失败一次:
添加列不会引发此问题,因为 SQLAlchemy 会显式列出所查询的列。

后续步骤

  • 连接:连接字符串、PgBouncer 和 TLS
  • 设置:Postgres 和 PgBouncer 参数,例如 max_connections
  • 只读副本:通过第二个引擎将只读查询路由到副本
  • 安全:IP 访问列表和私网连接
最后修改于 2026年10月14日