news 2026/9/10 17:05:36

PostgREST 事务模型全解析:访问模式、隔离级别与事务级设置实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgREST 事务模型全解析:访问模式、隔离级别与事务级设置实战指南

PostgREST 事务模型全解析:访问模式、隔离级别与事务级设置实战指南

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

本文以 PostgREST 官方参考文档 transactions.rst 为骨架,结合仓库源码中 MainTx.hs、PreQuery.hs 与 Config.hs 的实现细节,系统讲解 PostgREST 每个 HTTP 请求背后的事务生命周期:访问模式(Access Mode)如何强制执行 HTTP 语义、隔离级别如何按角色或函数定制、事务级设置(Transaction-Scoped Settings)如何让你在数据库中读取请求信息与改写 HTTP 响应,以及db-pre-requestdb-tx-end等配置的实战用法。读完本文,你将能利用 GUC 在数据库函数中读取请求头、Cookie、JWT 声明,动态注入响应头与状态码,并为测试场景安全地控制事务回滚。

从一次请求看事务生命周期

在 用户角色模拟(user impersonation)完成之后,每一个对 API 资源的请求都会运行在一个数据库事务内。PostgREST 官方文档给出的事务序列如下:

START TRANSACTION; -- <Access Mode> <Isolation Level> -- <Transaction-scoped settings> -- <Main Query> END; -- <Transaction End>

这一序列在源码中可以逐段对应。核心事务执行器位于 MainTx.hs:mainTx通过SQL.transactionNoRetry isoLvl txMode开启事务,其中隔离级别由planIsoLvl计算、访问模式由planTxMode计算;随后依次执行:

  1. 事务级设置SQL.statement mempty $ SQL.dynamicallyParameterized mqTxVars ...,即由 PreQuery.hs 的txVarQuery生成的SELECT set_config(...)语句;
  2. pre-request 函数(若配置了db-pre-request);
  3. 主查询mqMain
  4. 事务结束:默认 COMMIT,或根据Prefer: tx=rollbackdb-tx-end配置回滚。

Access Mode:用只读事务强制 HTTP 语义

访问模式决定事务能否修改数据库,只有两个值:READ ONLYREAD WRITE。PostgREST 利用"在 READ ONLY 事务中无法修改数据库"这一事实,来强制执行 GET 与 HEAD 请求的 HTTP 语义。

官方文档给出了一个直观的验证示例:创建一个会修改序列的视图——

CREATE SEQUENCE callcounter_count START 1; CREATE VIEW callcounter AS SELECT nextval('callcounter_count');

callcounter发起 GET 请求,会因nextval()在只读事务中被禁止而报错:

curl "http://localhost:3000/callcounter"
HTTP/1.1 405 Method Not Allowed {"code":"25006","details":null,"hint":null,"message":"cannot execute nextval() in a read-only transaction"}

错误码25006read_only_sql_transaction)在 Error.hs 中被映射为 HTTP 405,正是访问模式被用于强制 HTTP 语义的源码级证据。

表与视图的访问模式

对 表与视图,访问模式完全由 HTTP 方法决定:

HTTP MethodAccess Mode
GET, HEADREAD ONLY
POST, PATCH, PUT, DELETEREAD WRITE

函数的访问模式

对 函数,除了 HTTP 方法,还要看函数的易变性(volatility)声明:

HTTP MethodVOLATILESTABLEIMMUTABLE
GET, HEADREAD ONLYREAD ONLYREAD ONLY
POSTREAD WRITEREAD ONLYREAD ONLY

两个重要的注意事项:

  • volatility 只是一种"承诺":PostgreSQL 允许你把一个修改数据库的函数标记为IMMUTABLESTABLE而不会报错,但在 PostgREST 下,由于事务是 READ ONLY,该函数会在运行时失败。
  • OPTIONS 请求 不会开启事务,因此与访问模式无关。

Isolation Level:默认 READ COMMITTED,可按角色或函数定制

每个事务默认使用 PostgreSQL 的默认隔离级别READ COMMITTED。除非你为被模拟的角色或某个函数修改了default_transaction_isolation

按角色修改(例如让webuser的所有查询都使用可重复读):

ALTER ROLE webuser SET default_transaction_isolation TO 'repeatable read';

按函数调用修改(例如某个函数调用时使用串行化):

CREATE OR REPLACE FUNCTION myfunc() RETURNS text as $$ SELECT 'hello'; $$ LANGUAGE SQL SET default_transaction_isolation TO 'serializable';

从源码看,隔离级别的解析逻辑位于 MainTx.hs 的planIsoLvl:它先从configRoleIsoLvl(按角色存储的隔离级别表)中按当前角色查找,默认回退到SQL.ReadCommitted;如果计划是函数调用(CallReadPlan),则优先采用函数自身的隔离级别设置pdIsoLvl。值得注意的是,Config/Database.hs 在读取角色设置时会专门过滤default_transaction_isolation键,单独提取后用于构建configRoleIsoLvl,其余设置则作为普通角色设置应用。

Transaction-Scoped Settings:数据库与 HTTP 之间的桥梁

PostgREST 使用与事务生命周期绑定的设置(GUC),这些设置有两个用途:获取 HTTP 请求的信息,或修改 HTTP 响应

  • 读取请求设置:使用request.前缀,通过current_setting获取:
-- request settings use the request. prefix. SELECT current_setting('request.<setting>', true);
  • 写入响应设置:使用response.前缀,通过set_config设置:
-- response settings use the response. prefix. SELECT set_config('response.<setting>', 'value1', true);

这些set_config调用正是 PreQuery.hs 中txVarQuery生成的语句,其底层实现位于 SqlFragment.hs:setConfigWithConstantName生成set_config('key', value, true),而请求头、Cookie 则通过setConfigWithConstantNameJSON以 JSON 数组形式写入。

请求头、Cookie 与 JWT 声明

PostgREST 将请求头、Cookie 和 JWT 声明以 JSON 形式存储,可这样读取:

-- 获取请求中发送的所有请求头 SELECT current_setting('request.headers', true)::json; -- 获取单个请求头,可使用 JSON 箭头运算符 SELECT current_setting('request.headers', true)::json->>'user-agent'; -- 获取某个 Cookie 中 sessionId 的值 SELECT current_setting('request.cookies', true)::json->>'sessionId'; -- 获取 JWT 中 email 声明的值 SELECT current_setting('request.jwt.claims', true)::json->>'email';

需要注意的关键行为:

  • 请求头名称会被小写化:例如请求发送User-Agent: x,只能通过current_setting('request.headers', true)::json->>'user-agent'获取。
  • request.jwt.claims中的role默认为db-anon-role配置的值。
  • 设置不会在事务提交后变为 NULL,而是被设置为空字符串''。这是 PostgreSQL 的预期行为(详见社区讨论)。要规避这种不一致,可以创建包装函数:
CREATE FUNCTION my_current_setting(text) RETURNS text LANGUAGE SQL AS $$ SELECT nullif(current_setting($1, true), ''); $$;

从源码看,这些 GUC 的设置顺序(PreQuery.hs)依次为:search_path、角色设置(roleSettingsSql)、role、JWT 声明(claimsSql,且会把role插入到 claims 中)、request.methodrequest.pathrequest.headersrequest.cookies、时区(timezoneSql,由Prefer: timezone控制)、函数设置与db-app-settings中的应用设置。

请求路径与方法

路径和方法以text存储:

SELECT current_setting('request.path', true); SELECT current_setting('request.method', true);

对应源码中methodSqlpathSql分别写入request.methodrequest.path

请求角色与搜索路径

由于用户角色模拟,PostgREST 会设置标准的role,有多种读取方式:

SELECT current_role; SELECT current_user; SELECT current_setting('role', true);

此外,PostgREST 还会基于db-schemasdb-extra-search-path设置search_path。源码中searchPathSql将当前 schema(iSchema)与configDbExtraSearchPath拼接后写入search_path(PreQuery.hs)。

响应头:动态注入缓存、Set-Cookie 等

可以设置response.headers来为 HTTP 响应添加请求头。例如为响应添加两天的缓存头:

-- tell client to cache response for two days SELECT set_config('response.headers', '[{"Cache-Control": "public"}, {"Cache-Control": "max-age=259200"}]', true);
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Cache-Control: no-cache, no-store, must-revalidate

关键细节response.headers必须设置为单键对象的数组,而不是多键对象。因为Cache-ControlSet-Cookie这类请求头需要重复出现才能设置多个值,而 JSON 对象不允许重复键。这也是为什么示例中是[{"Cache-Control": "public"}, {"Cache-Control": "max-age=259200"}]而不是{"Cache-Control": "public", "Cache-Control": "max-age=259200"}

另外需要注意:PostgREST 自带的Content-TypeLocation等请求头也可以被这种方式覆盖。但无论Content-Type被覆盖成什么,响应内容仍会被转换为 JSON,除非使用 自定义媒体类型(custom media)。

这些响应头在事务执行结束后,通过rsGucHeaders字段(MainTx.hs)从结果集中解码并应用到 HTTP 响应。

响应状态码:自定义 HTTP 状态

可以设置response.status来覆盖 PostgREST 默认提供的状态码。例如下面的函数会把默认的 200 替换成 418:

create or replace function teapot() returns json as $$ begin perform set_config('response.status', '418', true); return json_build_object('message', 'The requested entity body is short and stout.', 'hint', 'Tip it over and pour it out.'); end; $$ language plpgsql;
curl "http://localhost:3000/rpc/teapot" -i
HTTP/1.1 418 I'm a teapot { "message" : "The requested entity body is short and stout.", "hint" : "Tip it over and pour it out." }

如果状态码是标准的,PostgREST 会补全状态消息(本例中的I'm a teapot)。源码中该状态通过rsGucStatus :: Maybe Text(MainTx.hs)从数据库结果中携带回响应层。

模拟角色的设置(Impersonated Role Settings)

PostgreSQL 会应用连接角色(authenticator)的设置;此外,PostgREST 还会把被模拟角色的设置作为事务级设置应用,从而实现更细粒度的角色控制。

例如用statement_timeout限制语句执行时间(默认禁用):

ALTER ROLE authenticator SET statement_timeout TO '10s'; ALTER ROLE anonymous SET statement_timeout TO '1s';

以上设置的效果:所有用户获得 10 秒的全局语句超时,匿名用户获得 1 秒的超时。源码中对应roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings)(PreQuery.hs),即按当前模拟角色从其设置表中取出对应项,以动态 GUC 名写入事务。

需要特权的设置(Settings with privileged context):上下文需要特权的设置默认不会被应用,以免产生权限错误。从 PostgreSQL 15 开始,可以为这些设置授予权限:

GRANT SET ON PARAMETER <setting> TO <authenticator>;

对应的源码逻辑见 PreQuery.hs 中的注释:为保证GRANT SET ON PARAMETER <superuser_setting> TO authenticator生效,角色设置必须在模拟角色之前设置,否则该 GRANT 就必须授予被模拟角色(见 PostgREST issue #3045 的相关讨论)。

提升的函数设置(Hoisted Function Settings)

PostgREST 可以把函数的设置"提升"为事务级设置,从而使函数设置覆盖模拟角色与连接角色的设置。

CREATE OR REPLACE FUNCTION myfunc() RETURNS void as $$ SELECT pg_sleep(3); -- simulating some long-running process $$ LANGUAGE SQL SET statement_timeout TO '4s';

当调用上述函数时,语句超时会是 4 秒。只有db-hoisted-tx-settings中列出的设置才会被提升,其默认白名单在 Config.hs 中定义:

defaultHoistedAllowList = ["statement_timeout","plan_filter.statement_cost_limit","default_transaction_isolation"]

注意:这个"提升"(hoist)机制与 Plan.hs 中用于查询计划的HoistedAgg(聚合字段提升)是完全不同的概念,后者仅与 SQL 查询构造相关。函数设置的提升对应 PreQuery.hs 中的funcSettingsSql,只有当计划是函数调用(CallReadPlan)时才生效。

Main Query:全部走预处理语句

主查询由请求表、视图或函数生成。所有生成的查询都使用预处理语句(受db-prepared-statements配置控制)。在 MainTx.hs 中,主查询通过SQL.dynamicallyParameterized mqMain ... configDbPreparedStatements执行,其结果被解码为ResultSet,其中包含表总数、查询总数、Location头、响应体、GUC 响应头与状态码等字段。

Transaction End:默认提交,可配置回滚

如果事务没有失败,它总是以 COMMIT 结束。除非将db-tx-end配置为无论如何都 ROLLBACK,或在特定条件下通过Prefer: tx=rollback回滚。这在测试场景中非常有用。

db-tx-end的四种取值及含义见 Config.hs 中的配置注释:

取值行为
commit(默认)事务总是提交,不可被覆盖
commit-allow-override事务提交,但可通过Prefer: tx=rollback头覆盖
rollback事务总是回滚,不可被覆盖
rollback-allow-override事务回滚,但可通过Prefer: tx=commit头覆盖

配置示例(postgrest.conf):

# 事务总是提交(默认) # db-tx-end = "commit" # 事务回滚但允许用 Prefer 头覆盖(适合测试) # db-tx-end = "rollback-allow-override"

源码解析逻辑位于 Config.hs 的parseTxEnd,任何其他取值都会报错 "Invalid transaction termination. Check your configuration."。运行时行为在 MainTx.hs 的optionalRollback中实现:当Prefer: tx=rollback或(配置了全部回滚且未请求 commit)时,先执行SET CONSTRAINTS ALL IMMEDIATE,再通过SQL.condemn强制事务回滚。PreferTransaction的两种取值(Commit/Rollback)在 Preferences.hs 中定义。

Aborting Transactions:失败即回滚

任何数据库失败(如约束冲突)都会导致事务回滚。也可以在函数内部RAISE一个错误来触发回滚。

Pre-Request:主查询前的拦截钩子

Pre-request 是一个在事务级设置设置完成之后、主查询执行之前运行的函数,通过db-pre-request配置启用:

# postgrest.conf # db-pre-request = "stored_proc_name"

它提供了修改设置或抛出异常来阻止请求完成的机会。源码中,PreQuery.hs 的preReqQuery生成select <func>()语句,在 MainTx.hs 中通过whenJust mqPreReq于主查询之前执行。

实战:通过 pre-request 设置请求头

官方文档示例——为所有来自 IE 6/7 浏览器的请求添加缓存头:

create or replace function custom_headers() returns void as $$ declare user_agent text := current_setting('request.headers', true)::json->>'user-agent'; begin if user_agent similar to '%MSIE (6.0|7.0)%' then perform set_config('response.headers', '[{"Cache-Control": "no-cache, no-store, must-revalidate"}]', false); end if; end; $$ language plpgsql; -- set this function on postgrest.conf -- db-pre-request = custom_headers

注意这里set_config的第三个参数传的是false(会话级)而不是true(事务级)。然后对表或视图发起 GET 请求,即可看到注入的缓存头:

curl "http://localhost:3000/people" -i \ -H "User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)"

测试事务行为的推荐实践

结合db-tx-endPrefer: tx=rollback,可以在不产生数据持久化影响的前提下测试 API 行为。官方在测试中大量使用这一机制——例如仓库中的 RollbackSpec.hs 就是专门验证tx=rollback场景的测试用例。把db-tx-end设为rollback-allow-override,配合Prefer: tx=commit,可以在默认回滚的测试环境中对个别需要持久化的场景放行;反之,commit-allow-override适合默认提交的生产环境,仅在特定请求上通过Prefer: tx=rollback验证事务行为。

小结

PostgREST 把数据库事务与 HTTP 请求深度绑定:访问模式将 GET/HEAD 强制为只读以维护 HTTP 语义,隔离级别可按角色与函数灵活定制,事务级 GUC 成为数据库感知 HTTP 请求、改写 HTTP 响应的桥梁,而 pre-request 与db-tx-end则为请求前拦截与测试提供了强大的控制力。理解这一模型,是编写安全、高效、可测试的 PostgREST 应用(尤其是复杂数据库函数与响应定制场景)的关键前提。相关配置项完整清单可查阅 postgrest.cabal 与 Config.hs,其中包含db-pre-requestdb-tx-enddb-hoisted-tx-settingsdb-prepared-statementsdb-schemasdb-extra-search-pathdb-anon-role等配置的默认值与解析逻辑。

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 17:04:41

SpringBoot构建高并发考研资讯平台架构实践

1. 项目概述&#xff1a;考研资讯平台的SpringBoot实现考研资讯平台是面向全国考研学子的一站式信息聚合系统&#xff0c;基于SpringBoot框架构建的后端服务能够高效处理每年数百万考生的实时查询需求。这个项目不同于普通的内容管理系统&#xff0c;它需要应对考研季爆发式的流…

作者头像 李华
网站建设 2026/9/10 17:04:23

当轮胎模型遇上Carsim:车辆动力学仿真与联合仿真实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 17:03:58

DAB变换器中EPS调制优化与Simulink建模实践

1. 项目概述&#xff1a;EPS调制在DAB变换器中的优化价值双有源桥(Dual Active Bridge, DAB)变换器作为双向DC-DC转换的明星拓扑&#xff0c;在新能源发电、电动汽车充电、储能系统等领域展现出独特优势。但在实际应用中&#xff0c;传统单移相(SPS)调制下的电流应力与软开关范…

作者头像 李华
网站建设 2026/9/10 17:03:48

TelegramSwift数据库升级终极指南:OpmizeDatabaseView与数据迁移策略详解

TelegramSwift数据库升级终极指南&#xff1a;OpmizeDatabaseView与数据迁移策略详解 TelegramSwift数据库升级是确保macOS版Telegram客户端稳定运行和数据安全的关键环节。在这篇完整指南中&#xff0c;我们将深入探讨TelegramSwift的数据库优化机制&#xff0c;特别是Opmize…

作者头像 李华
网站建设 2026/9/10 17:03:06

Excel实现P-III曲线适线:水文频率分析全流程拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华