仅追加事件表
记录发生过的事情,永不覆盖。
仅追加事件表 是 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 反馈 — 无需本地设置。