0Pricing
SQL Academy · 课时

仅追加事件表

记录发生过的事情,永不覆盖。

仅追加事件表 是 CoddyKit 上的免费 SQL Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Academy 课程共包含 4 节课。

什么是仅追加表

仅追加事件表是一种只插入行、永不更新或删除行的表。每一行代表在特定时间点发生的一件事。

这种模式是事件溯源的基础。您不存储当前状态,而是将每次变更都存储为不可变事件,从而获得完整且可审计的历史记录。

创建事件表

设计良好的事件表会记录谁在何时对哪个资源做了什么。occurred_at列记录精确的时间戳,而 DEFAULT NOW() 确保该列始终自动填充。

请注意,这种设计中没有 UPDATE 或 DELETE,行一旦写入便永久存在。

CREATE TABLE account_events (
  id           BIGSERIAL PRIMARY KEY,
  account_id   BIGINT      NOT NULL,
  event_type   TEXT        NOT NULL,
  payload      JSONB,
  occurred_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

插入事件

用户执行的每个操作——登录、存款、修改电子邮件地址——都会成为一个新行。您绝不能返回去编辑之前的事件。如果需要更正,就改为插入一个补偿事件。

这样可以按顺序保留所发生事情的完整过程。

INSERT INTO account_events (account_id, event_type, payload)
VALUES
  (42, 'account_opened',  '{"plan": "free"}'),
  (42, 'email_verified',  '{"email": "alice@example.com"}'),
  (42, 'plan_upgraded',   '{"from": "free", "to": "pro"}');

读取完整历史记录

由于每次状态变更都存储为一行,查询某个账户的完整历史记录只需执行按时间排序的 SELECT。您可以从第一个事件开始,一直重放某条记录的完整历程,直到最新事件。

SELECT
  id,
  event_type,
  payload,
  occurred_at
FROM account_events
WHERE account_id = 42
ORDER BY occurred_at ASC;

推导当前状态

使用仅追加表时,您不会直接存储当前状态,而是通过读取最新的相关事件来推导当前状态。在这里,账户 42 的当前套餐以最近的 plan_upgraded 或 account_opened 事件中的内容为准。

使用 ORDER BY occurred_at DESC LIMIT 1 可以高效获取最新快照。

SELECT payload->>'to'  AS current_plan
FROM   account_events
WHERE  account_id = 42
  AND  event_type IN ('account_opened', 'plan_upgraded')
ORDER BY occurred_at DESC
LIMIT 1;

使用规则强制实现不可变性

您可以在数据库层面使用 RULE 强制执行仅追加保证,让表上的任何 UPDATE 或 DELETE 都被静默忽略。这样可以防止任何拥有写入权限的应用程序意外修改数据。

引发异常的触发器是更强的替代方案,因为它会主动拒绝操作并返回错误。

CREATE RULE no_update_events AS
  ON UPDATE TO account_events
  DO INSTEAD NOTHING;

CREATE RULE no_delete_events AS
  ON DELETE TO account_events
  DO INSTEAD NOTHING;

通过触发器实现不可变性

引发异常的触发器比静默规则更加严格:如果应用程序尝试修改过去的事件,应用程序会立即收到错误。这样可以让错误显现出来,而不是默默吞掉错误。

CREATE OR REPLACE FUNCTION deny_event_mutation()
RETURNS TRIGGER AS $$
BEGIN
  RAISE EXCEPTION 'Event table is append-only: % is not allowed', TG_OP;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_deny_event_mutation
BEFORE UPDATE OR DELETE ON account_events
FOR EACH ROW EXECUTE FUNCTION deny_event_mutation();

按时间统计事件

仅追加表让基于时间的分析变得简单明了。由于每个事件都有时间戳,您无需添加额外列即可按日、周或月分组。下面的示例统计每天发生的各类事件数量。

SELECT
  DATE_TRUNC('day', occurred_at) AS day,
  event_type,
  COUNT(*)                        AS total
FROM account_events
GROUP BY 1, 2
ORDER BY 1, 2;

时点状态重建

事件日志最强大的特性之一,是能够重建任意记录在过去某个时刻的状态。只需筛选出不晚于目标时间戳的事件,无需任何时间旅行扩展。

这对调试、审计和监管合规都极其有价值。

-- What plan was account 42 on at the end of last month?
SELECT payload->>'to' AS plan_at_snapshot
FROM   account_events
WHERE  account_id = 42
  AND  event_type IN ('account_opened', 'plan_upgraded')
  AND  occurred_at <= DATE_TRUNC('month', NOW()) - INTERVAL '1 second'
ORDER BY occurred_at DESC
LIMIT 1;

使用补偿事件而不是更正

发现错误时——例如收取了错误的费用——您不要删除错误事件。相反,您应插入一个用于取消或逆转该操作的补偿事件。两个事件都会保留在日志中,准确显示发生了什么,以及何时进行了更正。

这样可以保持审计轨迹完整,并确保任何篡改都能被发现。

-- A charge was applied by mistake; record a reversal
INSERT INTO account_events (account_id, event_type, payload)
VALUES (
  42,
  'charge_reversed',
  '{"reason": "billing_error", "reverses_event_id": 17}'
);

对大型事件表进行分区

事件表增长很快。按时间范围进行分区可以保持各个分区较小、加快范围查询,还可以在不影响近期数据的情况下归档或删除旧分区。

PostgreSQL 的声明式分区使这一过程十分简单:在 occurred_at 上定义一个 RANGE 分区,然后让数据库自动路由插入操作。

CREATE TABLE account_events_2025
  PARTITION OF account_events
  FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');

CREATE TABLE account_events_2026
  PARTITION OF account_events
  FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');

仅追加表知识检验

测试您对仅追加事件表设计的理解。

回顾:仅追加事件表

在本课中,您学习了如何设计和使用仅追加事件表:

  • 不可变性——行只插入一次,之后永不修改。过去发生的事件就是事实。
  • 完整历史记录——保留每次状态变更,从而支持完整的审计轨迹和时点查询。
  • 补偿事件——通过添加新的逆转事件来更正错误,而不是删除旧事件。
  • 强制执行——数据库层面的规则或触发器可以防止意外修改。
  • 可扩展性——范围分区可以让大型事件日志长期保持良好性能。

仅追加表是事件溯源、CQRS 架构以及任何重视可审计性和历史准确性的系统的支柱。

常见问题解答

「仅追加事件表」课时是免费的吗?

是的 — 「仅追加事件表」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Academy 课程的其余内容,请升级到 CoddyKit PRO。 SQL Academy 课程共包含 4 节课。

「仅追加事件表」这节课中我会学到什么?

记录发生过的事情,永不覆盖。 你通过在浏览器中直接运行的动手代码来练习 SQL Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 SQL Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「仅追加事件表」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 SQL Academy 课中编写并运行代码吗?

能。每节 SQL Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么要保留历史记录
  2. 仅追加事件表
  3. 时态行与版本化行
  4. 从事件重建状态
← 返回 SQL Academy