一键导入
consistency-multitenancy
设计事务边界、一致性策略、多租户隔离和数据生命周期时使用。适用于高并发写入、幂等、多租户 SaaS、软删归档和审计字段设计。融合 ACID + Saga + 乐观锁 + RLS + Tenant Per Schema。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
设计事务边界、一致性策略、多租户隔离和数据生命周期时使用。适用于高并发写入、幂等、多租户 SaaS、软删归档和审计字段设计。融合 ACID + Saga + 乐观锁 + RLS + Tenant Per Schema。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
设计 API 认证鉴权和权限矩阵时使用。适用于多角色系统、租户隔离、字段级权限。优先使用 OAuth 2.0 / JWT + RBAC + 资源归属检查。
设计具体 API 端点时使用。适用于资源建模后的下一步、列端点清单、HTTP 方法和状态码选择。优先使用 RFC 7231 HTTP 语义 + GitHub REST 命名规范。
设计 API 错误码和错误结构时使用。适用于错误响应规范、调用方错误处理、调试可观测。优先使用 RFC 7807 Problem Details + 业务错误码 + 调用方处理建议。
设计幂等接口和重试策略时使用。适用于支付、扣减、订单、关键写操作。优先使用 Idempotency-Key + 业务去重键 + 并发冲突处理(ETag/版本号)。
输出 OpenAPI 契约和 Mock 服务时使用。适用于 API 设计的最后一步、给前端/后端/QA 的交付。优先使用 OpenAPI 3.1 + Mock 数据覆盖所有路径 + 详细的下游交接清单。
设计列表接口的分页、筛选、排序、搜索时使用。适用于所有列表 API。优先使用 cursor 分页(大数据)或 offset 分页(小数据)+ 统一筛选/排序规范。
| name | consistency-multitenancy |
| description | 设计事务边界、一致性策略、多租户隔离和数据生命周期时使用。适用于高并发写入、幂等、多租户 SaaS、软删归档和审计字段设计。融合 ACID + Saga + 乐观锁 + RLS + Tenant Per Schema。 |
参考来源:Pat Helland《Life beyond Distributed Transactions》、Martin Kleppmann《Designing Data-Intensive Applications》、AWS SaaS 多租户白皮书、Stripe / Slack / Notion 多租户实践。
1. 把隔离边界和一致性要求显式写入 schema 与约束
"tenant_id 字段" ≠ "多租户隔离"
还需要:查询过滤 + 唯一约束 + 权限边界 + 测试
2. 事务不应无限扩大
边界应等于业务不变量边界
3. 幂等和唯一性优先由数据库约束兜底
不靠应用代码判重
4. 不在事务里做外部调用
HTTP / 邮件 / 第三方 API 都不能放事务
5. 强一致 vs 最终一致:业务驱动
涉及钱:强一致
不影响财务:最终一致 + 补偿
6. 多租户三层防御:应用层 + 数据库层 + 测试层
单层防御必定漏
7. 软删除是甜蜜陷阱
破坏唯一约束、查询要带 WHERE deleted_at IS NULL
A - Atomicity(原子性):要么全成,要么全败
C - Consistency(一致性):事务前后业务规则一致
I - Isolation(隔离性):并发事务互不干扰
D - Durability(持久性):提交后数据不丢
| 级别 | 脏读 | 不可重复读 | 幻读 | 写偏斜 |
|---|---|---|---|---|
| Read Uncommitted | ✓ | ✓ | ✓ | ✓ |
| Read Committed(默认) | ✗ | ✓ | ✓ | ✓ |
| Repeatable Read(PG/MySQL 默认变体) | ✗ | ✗ | ✗ | ✓ |
| Serializable | ✗ | ✗ | ✗ | ✗ |
-- PostgreSQL
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- MySQL
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
建议:
- 默认 Read Committed 已能满足大多数业务
- 关键金融场景用 Serializable + 重试
- 慎用 Read Uncommitted(一般不需要)
# 反模式:事务包含外部调用
@transaction.atomic
def create_order(user_id, items):
order = Order.objects.create(...)
OrderItem.objects.bulk_create(...)
# 外部 HTTP 调用 → 事务持续 30 秒
payment_result = call_payment_gateway(order)
if payment_result.success:
order.status = 'paid'
order.save()
# 阶段 1:本地事务(仅 DB 操作)
@transaction.atomic
def create_order(user_id, items):
order = Order.objects.create(status='pending', ...)
OrderItem.objects.bulk_create(...)
return order
# 阶段 2:异步外部调用
def process_payment(order_id):
order = Order.objects.get(id=order_id)
result = call_payment_gateway(order)
# 阶段 3:本地事务更新状态
if result.success:
order.update(status='paid')
else:
order.update(status='failed')
# 补偿动作(释放库存、退款)
compensate(order)
-- 幂等性表
CREATE TABLE idempotency_keys (
key varchar(128) PRIMARY KEY,
request_hash varchar(64) NOT NULL,
response_body jsonb,
status varchar(32) NOT NULL, -- 'processing' / 'completed' / 'failed'
created_at timestamptz DEFAULT now(),
expires_at timestamptz NOT NULL DEFAULT (now() + INTERVAL '24 hours')
);
CREATE INDEX idx_idempotency_expires ON idempotency_keys(expires_at);
-- 同一订单不能支付两次
CREATE TABLE payments (
id bigserial PRIMARY KEY,
order_id bigint NOT NULL,
amount_cents bigint NOT NULL,
status varchar(32) NOT NULL,
created_at timestamptz DEFAULT now(),
-- 业务唯一:每个订单只能有一条成功的支付
CONSTRAINT uq_order_payment UNIQUE (order_id, status)
DEFERRABLE INITIALLY DEFERRED
);
-- 表设计
CREATE TABLE orders (
id bigserial PRIMARY KEY,
status varchar(32) NOT NULL,
version integer NOT NULL DEFAULT 1,
...
);
-- 更新时校验版本
UPDATE orders
SET status = 'paid', version = version + 1
WHERE id = ? AND version = ?;
-- 影响 0 行 → 版本冲突 → 应用层重试或返回 409
-- 高冲突场景:直接锁行
BEGIN;
SELECT * FROM accounts WHERE id = ? FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = ?;
COMMIT;
何时用:
乐观锁:冲突率低(< 5%),重试代价小
悲观锁:冲突率高,必须串行
# 中间件:每个查询都带 tenant_id
class TenantMiddleware:
def process_request(self, request):
request.tenant_id = get_tenant_from_token(request)
# ORM 自动过滤
class TenantAwareManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(tenant_id=current_tenant_id())
-- PostgreSQL Row Level Security(强烈推荐)
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant_id')::bigint);
-- 应用每次连接时设置
SET LOCAL app.current_tenant_id = '123';
SELECT * FROM orders; -- 自动过滤
# 跨租户越权测试用例
def test_cross_tenant_isolation():
user_a = create_user(tenant=1)
user_b = create_user(tenant=2)
order_a = create_order(user=user_a) # tenant 1 的订单
# 用 user_b 的身份尝试访问 user_a 的订单
response = client.get(f'/orders/{order_a.id}', as_user=user_b)
assert response.status_code == 404 # 或 403
-- 所有租户共享表,靠 tenant_id 区分
CREATE TABLE orders (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL REFERENCES tenants(id),
...
);
-- 复合唯一(必须含 tenant_id)
CREATE UNIQUE INDEX uq_orders_number
ON orders(tenant_id, order_number);
优势:
劣势:
适合:SaaS 中小租户、共用功能
-- 每个租户一个 schema
CREATE SCHEMA tenant_acme;
CREATE TABLE tenant_acme.orders (...);
CREATE SCHEMA tenant_globex;
CREATE TABLE tenant_globex.orders (...);
优势:
劣势:
适合:百级租户、企业版
每个租户独立数据库实例
优势:
- 完全隔离
- 性能 / 备份 / 安全独立
劣势:
- 资源消耗大
- 运维复杂
适合:大型企业客户、合规要求强(PII / 医疗)
-- 错:用户邮箱全局唯一
CREATE UNIQUE INDEX uq_users_email ON users(email);
-- 不同租户不能有同名邮箱
-- 对:租户内唯一
CREATE UNIQUE INDEX uq_users_email_tenant
ON users(tenant_id, email);
-- 错:软删后无法重新注册
CREATE UNIQUE INDEX uq_users_email
ON users(tenant_id, email);
-- 同 email 的删除用户阻止新注册
-- 对:部分索引
CREATE UNIQUE INDEX uq_users_email_active
ON users(tenant_id, email)
WHERE deleted_at IS NULL;
软删除(deleted_at):
✅ 用户可恢复
✅ 关联数据完整
✅ 审计需要
❌ 唯一约束复杂
❌ 查询都要带 WHERE
❌ 数据膨胀
硬删除:
✅ 简单
✅ 性能好
❌ 不可恢复
❌ 关联数据连锁删除
归档(删除前移到归档表):
✅ 主表干净
✅ 历史数据可查
❌ 跨表查询复杂
推荐:
- 用户数据:软删除(GDPR 30 天后硬删)
- 业务数据:软删除 + 6 个月归档
- 临时数据(缓存 / 会话):硬删除
- 审计日志:永不删除
-- 推荐字段
CREATE TABLE orders (
id bigserial PRIMARY KEY,
-- ... 业务字段 ...
-- 生命周期字段
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz, -- 软删
archived_at timestamptz, -- 归档
retention_until timestamptz, -- 保留期限
-- 审计字段
created_by bigint REFERENCES users(id),
updated_by bigint REFERENCES users(id),
deleted_by bigint REFERENCES users(id),
-- 乐观锁
version integer NOT NULL DEFAULT 1
);
1. 识别业务不变量和并发写入场景
- 哪些状态不能同时存在
- 哪些操作不能重复执行
- 哪些字段必须唯一
↓
2. 判断强一致、最终一致或补偿机制
- 涉及钱 → 强一致
- 通知 / 报表 → 最终一致
- 跨服务 → Saga
↓
3. 设计事务边界、锁策略、唯一约束、版本字段
- 事务最小化
- 数据库约束兜底
- 乐观锁优先
↓
4. 设计租户字段、复合约束、默认过滤规则
- 三层防御
- 复合唯一含 tenant_id
- RLS 兜底
↓
5. 设计删除、归档、审计和恢复策略
- 软删 + 部分索引
- 归档表
- 审计日志
↓
6. 输出一致性和隔离检查清单
| 项目 | 检查点 |
|---|---|
| 租户键 | 每张租户数据表是否有明确 tenant_id 字段 |
| 查询 | 是否默认带 tenant_id 过滤(中间件 / RLS) |
| 唯一性 | 唯一约束是否包含租户维度 |
| 后台操作 | 跨租户操作是否有审计和授权 |
| 测试 | 是否有跨租户越权回归用例 |
| 备份 | 单租户备份恢复是否可行 |
| 监控 | 单租户性能 / 错误是否可观测 |
| 删除 | 租户注销时数据如何处理(GDPR) |
templates/consistency-checklist-template.md — 一致性 + 多租户 + 软删除 + 审计 + RLS 配置完整清单□ 列出业务不变量
□ 说明事务边界和失败补偿
□ 幂等键、唯一约束、版本字段明确
□ 多租户三层防御(应用 + 数据库 + 测试)
□ 唯一约束包含 tenant_id
□ 软删除有部分唯一索引
□ 删除、归档、审计、恢复策略明确
□ 事务不包含外部调用
□ 隔离级别选择有依据
□ 跨服务一致性方案(Saga / 事件)
□ RLS 配置(PG)兜底
□ 与安全工作流协作评审
上游:
schema-design → 提供表结构基础
api-designer → 提供权限边界
下游:
index-access-pattern → 设计租户维度索引
data-operations-safety → 评估跨租户操作和数据修复风险
security-engineer → 评审敏感数据和权限边界
backend-engineer → 实现租户中间件
qa-engineer → 跨租户越权回归用例
references/consistency-multitenancy-guide.md — ACID 深度、Saga 模式、SaaS 多租户三种架构对比、RLS 实战、PostgreSQL 隔离级别真相、Slack / Notion 真实案例