Erdmates Logo
Erdmates
返回博客
微服务CQRSSaga分布式事务事件溯源

微服务数据库模式:独立性与一致性的平衡艺术

深入探讨微服务架构下的数据库设计模式,包括每服务数据库、CQRS、Saga事务和事件溯源,帮助您在服务独立性与数据一致性之间找到平衡。

2026年4月7日9 分钟阅读

微服务数据库的核心矛盾

单体应用可以通过数据库事务轻松保证数据一致性。而在微服务架构中,每个服务拥有独立的数据库,传统的ACID事务跨越服务边界时变得不可行。这是微服务数据库设计最核心的挑战:如何在保证服务独立部署的同时,维护跨服务数据的最终一致性。

模式1:每服务独立数据库

每个微服务拥有自己的数据库,其他服务只能通过API访问数据,绝不直接查询其他服务的数据库。

-- 订单服务数据库(orders_db)
CREATE TABLE orders (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id       UUID NOT NULL,      -- 不是外键!只是存储用户ID
  status        VARCHAR(20) NOT NULL,
  total_amount  DECIMAL(12,2) NOT NULL,
  created_at    TIMESTAMP NOT NULL DEFAULT NOW()
);

-- 用户服务数据库(users_db)
CREATE TABLE users (
  id    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  name  VARCHAR(200) NOT NULL
);

-- 订单服务需要用户姓名时,调用用户服务API
-- 而非执行跨库JOIN:SELECT u.name FROM users_db.users u ...

优点:服务可以独立扩展和部署,可以为不同服务选择最适合的数据库类型。 挑战:跨服务查询需要API调用,报表类查询复杂度上升。

模式2:CQRS(命令查询职责分离)

CQRS将读操作(Query)和写操作(Command)分离到不同的数据模型,解决读写需求差异大的问题:

-- 写模型(规范化,保证写入一致性)
CREATE TABLE orders (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  status VARCHAR(20) NOT NULL,
  created_at TIMESTAMP NOT NULL
);

CREATE TABLE order_items (
  id         UUID PRIMARY KEY,
  order_id   UUID REFERENCES orders(id),
  product_id UUID NOT NULL,
  quantity   INT NOT NULL,
  unit_price DECIMAL(10,2) NOT NULL
);

-- 读模型(反规范化,为查询优化的物化视图)
CREATE TABLE order_summaries (
  order_id       UUID PRIMARY KEY,
  user_id        UUID NOT NULL,
  user_name      VARCHAR(200) NOT NULL,  -- 冗余存储
  user_email     VARCHAR(255) NOT NULL,  -- 冗余存储
  status         VARCHAR(20) NOT NULL,
  item_count     INT NOT NULL,
  total_amount   DECIMAL(12,2) NOT NULL,
  created_at     TIMESTAMP NOT NULL,
  last_updated   TIMESTAMP NOT NULL
);

-- 通过事件异步更新读模型
CREATE OR REPLACE FUNCTION refresh_order_summary(p_order_id UUID)
RETURNS VOID AS $$
BEGIN
  INSERT INTO order_summaries
  SELECT
    o.id, o.user_id, u.name, u.email, o.status,
    COUNT(oi.id), SUM(oi.quantity * oi.unit_price), o.created_at, NOW()
  FROM orders o
  JOIN order_items oi ON o.id = oi.order_id
  JOIN users u ON o.user_id = u.id  -- 此处读模型可合并数据
  WHERE o.id = p_order_id
  GROUP BY o.id, u.name, u.email
  ON CONFLICT (order_id) DO UPDATE SET
    status       = EXCLUDED.status,
    total_amount = EXCLUDED.total_amount,
    last_updated = EXCLUDED.last_updated;
END;
$$ LANGUAGE plpgsql;

模式3:Saga模式处理分布式事务

当一个业务操作需要跨多个服务保证一致性时(如下单需要扣减库存、创建订单、扣减积分),Saga模式将其拆分为一系列本地事务,每步失败时执行补偿操作:

-- Saga协调器状态表
CREATE TABLE saga_instances (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  saga_type    VARCHAR(100) NOT NULL,
  current_step VARCHAR(100) NOT NULL,
  status       VARCHAR(20) NOT NULL,  -- running/completed/compensating/failed
  payload      JSONB NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT NOW(),
  updated_at   TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE saga_events (
  id           BIGSERIAL PRIMARY KEY,
  saga_id      UUID REFERENCES saga_instances(id),
  step         VARCHAR(100) NOT NULL,
  event_type   VARCHAR(50) NOT NULL,  -- started/completed/failed/compensated
  event_data   JSONB,
  occurred_at  TIMESTAMP NOT NULL DEFAULT NOW()
);

-- 示例:创建订单Saga步骤
-- Step1: 锁定库存 → 失败: 释放库存
-- Step2: 创建订单 → 失败: 取消订单 + 释放库存
-- Step3: 扣减积分 → 失败: 恢复积分 + 取消订单 + 释放库存
-- Step4: 发送通知 → 失败: 仅记录日志(通知可重试,不需要回滚前序步骤)

模式4:发件箱模式(Outbox Pattern)

确保本地数据库操作与消息发布的原子性,防止"数据已写入但消息未发出"的不一致:

-- 在业务表所在同一数据库创建发件箱表
CREATE TABLE outbox_events (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  aggregate_id UUID NOT NULL,
  event_type   VARCHAR(100) NOT NULL,
  payload      JSONB NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT NOW(),
  published_at TIMESTAMP  -- NULL=待发布
);

-- 在同一事务中写入业务数据和发件箱事件
BEGIN;
  UPDATE orders SET status = 'confirmed' WHERE id = $1;
  INSERT INTO outbox_events (aggregate_id, event_type, payload)
  VALUES ($1, 'OrderConfirmed', '{"orderId": "...", "userId": "..."}');
COMMIT;

-- 独立进程轮询发件箱并发布到消息队列
-- SELECT * FROM outbox_events WHERE published_at IS NULL ORDER BY created_at LIMIT 100;

使用Erdmates设计微服务数据库边界

在微服务架构中,正确划定服务边界(即有界上下文)是成功的关键。Erdmates帮助团队可视化每个服务的数据模型,清晰标注服务间的数据所有权,以及事件流向关系。在架构评审时,通过ERD快速发现服务间的数据耦合问题,提前调整边界划分。

微服务数据库设计没有"正确答案",只有"当前场景下的最优权衡"。从最简单的每服务独立数据库开始,在实际遇到性能瓶颈或一致性问题时,再逐步引入CQRS、Saga等复杂模式。

立即使用Erdmates开始

使用AI驱动的ERD编辑器开始数据库设计。免费使用。

免费试用演示免费开始

相关文章

bigint、UUID v4 还是 UUID v7?主键选择指南(含 PostgreSQL 18 的 uuidv7())

比较 bigint、UUID v4 和 UUID v7 主键的优缺点,介绍 PostgreSQL 18 内置的 uuidv7() 带来的变化,以及不同场景下的选择方法。

被AI代理读取的数据库:MCP时代的模式设计原则

AI代理通过MCP直接连接数据库的情况越来越多。本文整理如何设计名称、注释和约束,让代理不会误解模式,以及如何安全地划分权限。

PostgreSQL 19预览:影响模式设计与运维的变化(基于Beta 4)

以官方发布说明(Beta 4)为准,整理即将正式发布的PostgreSQL 19的变更中直接影响表设计、迁移和运维的内容。同时介绍在Beta阶段被撤回的功能,以及升级前需要确认的行为变化。

Erdmates Logo
Erdmates

使用自然语言设计数据库,通过AI驱动的ERD编辑器与团队实时协作。

服务

  • 功能介绍
  • 价格方案
  • 演示
  • SQL 转 ERD

资源

  • 博客
  • 帮助
  • 联系我们

法律声明

  • 服务条款
  • 隐私政策
  • 第三方提供
公司名称: Produx|代表人: 최정회
地址: 강원특별자치도 원주시 솔우물2길 4-1 (무실동)
电子邮件: master@erdmates.com
隐私保护负责人: 최정회|托管服务提供商: Amazon Web Services

© 2026 Produx. 版权所有。