你只需要PostgreSQL

Lobsters Hottest 工具

摘要

一份详细指南,介绍如何使用PostgreSQL作为单一数据库来处理金融应用的方方面面,包括模式设计、状态机、触发器和性能优化。

<p><a href="https://lobste.rs/s/yvvhve/all_you_need_is_postgresql">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/26 10:07

# 你只需要 PostgreSQL 来源:https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/ ## 你只需要 PostgreSQL2026 年 6 月 25 日作者:Eduardo Bellani - 引言 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#introduction) - 基本设置 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#the-setup) - 打好基础 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#laying-the-foundation)- 基础:用于模块化的模式与用户角色 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#the-foundation-schemas-and-user-roles-for-modularity) - 域 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#domains) - 账户:托管账户与外部账户 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#accounts-managed-and-external) - 转账:受状态机和时段约束 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#transfers-constrained-by-a-state-machine-and-temporal-periods) - 转账状态历史 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#transfer-state-history) - 账户审计 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#account-auditing) - 交易:不可变的事件 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#transactions-the-immutable-events) - 通过有意义的约束维护业务规则 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#on-maintaining-business-rules-via-meaningful-constraints)- 转账状态机 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#the-transfer-state-machine) - 交易必须在转账期限内 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#transactions-must-fall-within-the-transfer-period) - 待处理交易需要待处理转账 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#pending-transactions-require-a-pending-transfer) - 关闭转账时不允许存在未来交易 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#no-future-transactions-when-closing-a-transfer) - 关于容量规划 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#on-capacity-planning)- 工作集估算 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#working-set-estimation) - 关于写入吞吐量 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#on-write-throughput)- 为转账启用 HOT 更新 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#enabling-hot-updates-for-transfers) - 确保没有未使用的索引 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#making-sure-there-are-no-unused-indexes) - OLTP (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#oltp)- 列表 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#listing) - 转账历史 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#the-history-of-a-transfer) - OLAP (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#olap)- 余额分类账 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#balance-ledger) - 通过触发器进行增量维护 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#incremental-maintenance-via-triggers) - 关于可序列化隔离 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#on-serializable-isolation) - 关于解耦 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#on-decoupling) - 对启动场景进行基准测试 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#benchmarking)- 种子数据 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#seed-data) - 写入脚本:完整的转账生命周期 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#write-script-full-transfer-lifecycle) - 读取脚本:活动流和余额 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#read-script-activity-stream-and-balance) - 运行基准测试 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#running-the-benchmark) - 结果 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#results) - 结论 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#conclusion) - 附录 A:完整代码套件 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#appendix-a-full-code-suite) ## 引言 现代工程中存在一种根深蒂固的文化反射:每当遇到问题,就直奔打包解决方案,而不是从第一性原理思考。结果是架构上的装腔作势,以及大量错失的机会。以下是一些故意有些荒谬但耳熟能详的例子: > 我们需要审计追踪,那就用 \{时序/事件溯源 DBMS\} 吧。我们的应用太慢了,那就用 \{内存键值数据库\} 来缓存吧。 由于像 PostgreSQL 这样的关系型数据库因其无与伦比的声誉仍被认为是必需的,公司最终在 PostgreSQL 之上层层堆叠产品。它们增加了运动部件的数量、运营风险、人员需求以及整个系统的熵。复杂性1 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#fn:1) 增长,不是因为问题本身要求,而是因为有人拿起了他们在大会演讲中看到的工具。 在这篇文章中,我将介绍一系列常见的误解,这些误解促使团队在不需要时引入新的基础设施。所有这些都可以用纯 PostgreSQL 18 配合 RDS 上可用的标准扩展来解决,无需特殊基础设施,也无需分布式系统扮演。 本文的目的不是论证专用系统永远不合适,而是表明处理数据问题的默认假设应该是:**我的公司只用 PostgreSQL 就足够了**。 ## 基本设置 以下是我根据经验总结出的人们为了使用 PostgreSQL 以外的工具而提出的一些论点: - 我需要审计和重构状态 - 写入吞吐量太低 - 事务查询太慢 - 分析查询太慢 - 我的应用会与数据库耦合 为了解决这些问题,我将使用数据库领域的“果蝇”——经典的供应商与零件 (https://en.wikipedia.org/wiki/Suppliers_and_Parts_database) 数据库 (Date 2003 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_6)) 的一个变体。我将其更新为更符合常见问题表:金融交易及其发起的转账。 在本文的剩余部分,我们将基于现代 PostgreSQL 构建一个数据库设计,以实现上述通用目标以及特定的业务需求。以下是一家非常流行的银行 API 公司的需求片段: > 交易:是对 Increase 财务活动的不可变记录。你可以把它们想象成银行对账单上的明细项。金额为正的交易意味着账户里钱多了。金额为负的交易意味着账户里钱少了。你不能直接创建交易,而且它们一旦创建就永远不会改变。任何导致资金在您的 Increase 账户内移动的操作都会产生一笔交易——包括发起或接收的转账、卡支付、赚取的利息等。转账:包括 ACH 转账、电汇等——是通过 Increase 在外部网络上发起资金移动的最常见方式。转账与交易是一对多的关系,交易作为副作用产生。与交易不同,转账是有状态的,并会在网络中移动时经历不同状态的生命周期。待处理交易:代表账户将来可能的贷记或借记,是与交易不同的独立资源(尽管名称相似)。值得注意的是,虽然交易是不可变的,但待处理交易不是,因为它们不保证资金移动。例如,卡授权(可以变更或超时)以及账户冻结(可以解除)时会创建待处理交易。待处理交易不影响您的当前余额(您赚取利息的余额),但会影响您的可用余额(您可以从 Increase 转出的金额)。(Increase, Inc. 2025 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_12)) 下面是从 increase 的沙箱仪表板截取的两张截图,展示了这些需求: 图 1:Increase 账户仪表板图 1:Increase 账户仪表板 图 2:Increase 详细信息图 2:Increase 详细信息 从这两张图中,我提取了以下需求(功能性和非功能性),我认为这些在像 increase 这样的金融系统中很常见: 1. 账户由不可变的路由号码和账户号码定义,并且具有可变的状态。 2. 账户分为外部账户和托管账户,一个账户必须是且仅是其中一种。转账仅在外部账户和托管账户之间进行。 3. 交易和转账按各自的创建时间列出并进行分页。 4. 显示当前余额和可用余额,包括其当前值和每日历史值。 5. 转账的行为类似于状态机,用户可以看到状态之间的进展。用户可以查看转账的完整状态历史,其中一些状态与待处理/已结算交易相关联。 6. 用户还可以查看交易的详细信息,并查看生成该交易的转账。 7. 我们应该最大化交易和转账的写入吞吐量。转账是可编辑的,因此我们也应该能够快速更新它们。 ## 打好基础 在本节中,我们构建核心表2 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#fn:2) 以及必要的角色,以限制更新并实现需求中提到的不可变性(例如需求1 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#org-target--act))。 ### 基础:用于模块化的模式与用户角色 模块化,定义为实现与接口之间多对多关系的能力 (Koppel 2023 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_13)),对软件开发至关重要 (Yourdon and Constantine 1979 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_30))。我们在 SQL 中实现这一点的一些基本工具是模式(schema)和角色(role)。特别是,适当的角色3 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#fn:3) 可用于在数据库对象之上定义非常精确的接口 (Swart 2019 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_23))。 ``` create schema finance; create role finance; grant usage on schema finance to finance; alter default privileges in schema finance grant select, insert, update, delete on tables to finance; alter default privileges in schema finance grant usage, select on sequences to finance; ``` ### 域 数据库域通常被从业者嗤之以鼻,但这是一个重大错误。正确理解的话,它们是 > 将抽象数据类型应用于数据库管理。(Pascal 2019 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_18)) 因此,域是逻辑设计的核心构建块。 ``` create domain finance.routing_number as text check (value ~ '^[0-9]{9}$'); create domain finance.account_number as text check (value ~ '^[0-9]{12}$'); create domain finance.transfer_status as text check(value in ('pending', 'returned', 'completed')); ``` `transfer_status` 尤其重要,因为它代表了状态机可以拥有的有效状态。 ### 账户:托管账户与外部账户 托管账户是我们的系统所拥有的账户。接收转账时,我们只控制转账的一方,即托管账户一方。 托管账户可以停用和重新激活。这非常适合 SQL 2011 (Kulkarni and Michels 2012 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_14)) 引入的时态特性,特别是应用时间,该特性最近在 PostgreSQL 18 (PostgreSQL Wiki Contributors 2024 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#citeproc_bib_item_21)) 中引入。这个特性允许我们表示账户在不重叠的时间段内进出活动状态。4 (https://ebellani.github.io/blog/2026/all-you-need-is-postgresql/#fn:4) ``` -- 要使用时态约束,需要安装 btree_gist 扩展,它提供了在标量数据类型上创建 GiST 索引所需的操作符类: create extension if not exists btree_gist; create table finance.managed_active_account( routing_number finance.routing_number not null, account_number finance.account_number not null, account_name text not null, account_active_period tstzrange not null default tstzrange(now(), 'infinity', ')'), primary key (routing_number, account_number, account_active_period without overlaps) ); comment on table finance.managed_active_account is '托管账户是执行交易的对象。可以把它想象成你的银行账户。它们存储资金、接收转账、发送付款。它们赚取利息并拥有存款保险。此关系保存当前处于活动状态的账户。在账户处于非活动状态的期间内,不能为其创建转账。'; create table finance.external_account( routing_number finance.routing_number not null, account_number finance.account_number not null, account_name text not null, primary key (routing_number, account_number) ); comment on table finance.external_account is '外部账户代表其他机构的对手方账户。它们是转账的另一端。与托管账户不同,它们没有时态活动期,因为我们不控制其生命周期。'; ``` 下面我们通过确保托管账户和外部账户不能相同来完成账户的定义。由于循环依赖(一个表依赖于另一个表,反之亦然),我们需要使用 `alter table` 而不是在表定义中添加这些 `check` 约束。 ``` -- 确保托管账户和外部账户永远不会共享相同的身份 create or replace function finance.not_external_account( p_routing_number finance.routing_number, p_account_number finance.account_number ) returns boolean language sql stable as $$ select not exists ( select 1 from finance.external_account where routing_number = p_routing_number and account_number = p_account_number ); $$; create or replace function finance.not_managed_account( p_routing_number finance.routing_number, p_account_number finance.account_number ) returns boolean language sql stable as $$ select not exists ( select 1 from finance.managed_active_account where routing_number = p_routing_number and account_number = p_account_number ); $$; alter table finance.managed_active_account add constraint managed_not_external check (finance.not_external_account(routing_number, account_number)); alter table finance.external_account add constraint external_not_managed check (finance.not_managed_account(routing_number, account_number)); ```

相似文章

初创公司的PostgreSQL生存指南

Hacker News Top

一份面向在生产环境中运行PostgreSQL的初创公司的全面指南,涵盖模式设计、查询优化、索引、迁移、连接管理,以及查询计划与分区等高级主题。

Postgres by Example

Hacker News Top

一份使用带注释的SQL示例的PostgreSQL实践入门,涵盖从基础到高级主题。

PGSimCity - PostgreSQL 工作原理

Hacker News Top

PGSimCity 是一个 3D 交互模型,可直观展示 PostgreSQL 数据库引擎的内部运作,为理解其架构提供教育工具。

扩展PostgreSQL以支持8亿ChatGPT用户

OpenAI Blog

OpenAI分享了扩展PostgreSQL以支持8亿ChatGPT用户及每秒数百万查询的技术见解,采用了单主架构搭配50个只读副本,同时通过分片和优化策略管理写入密集型工作负载带来的挑战。