微服务架构下的数据库治理——从“一个库所有人用“到“独立自治“

📅 2026/7/31 9:52:27 👁️ 阅读次数 📝 编程学习
微服务架构下的数据库治理——从“一个库所有人用“到“独立自治“

喜欢把枯燥的技术文档变成"手把手教程",不讲空话,只讲怎么连、怎么写、怎么优化。


微服务拆分,最难的往往不是服务本身,是数据库。

代码拆成十个服务很简单,把 controller 移过去、改个包名、跑通就行。但数据库怎么拆?每个服务一个独立库还是共享?跨库 JOIN 怎么做?一个服务改了表结构,其他五个服务跟着报错,这怎么解?

今天把微服务架构下的数据库治理问题讲透。从拆分策略到跨库一致性到版本管理,跟着我走一遍,你也能搭一套合理的微服务数据库架构。


一、共享库的代价——为什么不能"一个库所有人用"

先说说最常见的起步方式:微服务拆了,数据库不动。五个服务连同一个库,大家共用同一套表。

刚起步的时候没问题。三个服务、几十个表、两个开发,共享一个库效率最高。

但规模上来之后,问题就来了:

问题一:schema 变更互相影响。服务 A 给 orders 表加了一个字段,服务 B 的 ORM 映射没更新,直接报错。更麻烦的是,服务 C 依赖那个字段做查询,但服务 A 还没发布,导致服务 C 上线后查不到数据。

问题二:连接数爆炸。每个服务都有自己的连接池,假设每个服务 20 个连接,10 个服务就是 200 个连接。数据库的 max_connections 是有限的,每个连接还占用内存。服务越多,连接压力越大。

问题三:性能干扰。服务 A 跑了一个大报表查询,把数据库 CPU 打满了。服务 B 的在线交易跟着变慢——明明两个服务完全无关,却因为共享数据库互相拖累。

问题四:权限无法隔离。服务 A 只需要读用户表,但因为连的是同一个库,它理论上可以访问所有表。权限粒度做不到服务级别。

关键判断点:如果你的服务数量超过 5 个、开发团队超过两个、schema 变更开始出现跨服务影响,共享库的模式就该结束了。


二、数据库拆分策略——按什么规则拆?

共享库不行,那就拆。但怎么拆?有三种主流策略。

策略 A:一个服务一个数据库

每个微服务拥有自己独立的数据库实例。服务之间不共享任何数据,只能通过 API 交互。

优点:隔离性最好。schema 变更互不影响、连接数可控、权限天然隔离。

缺点:跨服务查询困难。原来一个 JOIN 能搞定的事,现在得调 API。数据库实例多,运维成本上升。

适合:服务边界清晰、跨服务查询少的场景。

策略 B:按业务域拆分

不是每个服务一个库,而是按业务域分组。比如"订单域"包含订单服务、支付服务、物流服务,它们共享一个订单域数据库。"用户域"包含用户服务、权限服务、消息服务,共享一个用户域数据库。域与域之间不共享数据。

优点:平衡了隔离性和复杂度。域内服务可以共享数据库,跨域服务通过 API 或数据同步交互。

缺点:域边界需要仔细设计。分错了,域内服务还是会有 schema 冲突。

适合:服务数量多、但业务域清晰的场景。

策略 C:核心/边缘分离

核心业务(交易、支付、用户)用独立数据库,边缘业务(日志、统计、通知)共享一个数据库。核心严格隔离,边缘适度共享。

优点:资源集中在核心系统,边缘系统降低运维成本。

缺点:边缘服务之间仍有共享库的问题,只是范围缩小了。

适合:团队规模有限、但核心系统对稳定性和性能要求高的场景。


三、跨库数据一致性——不能 JOIN 了,怎么保证数据对得上?

拆了库之后,最大的挑战来了:原来在一个库里用事务 + JOIN 就能保证一致性的操作,现在跨库了,怎么做?

方案一:Saga 模式——用业务补偿代替技术回滚

Saga 的思路是:把一个跨服务的长事务拆成多个本地短事务,每个服务执行自己的事务,如果中间某一步失败了,就依次执行前面各步的"补偿操作"来撤销。

举例:电商下单流程涉及三个服务:订单服务(创建订单)、库存服务(扣减库存)、支付服务(扣款)。

  • 第一步:订单服务创建订单,状态为"待支付"。
  • 第二步:库存服务扣减库存。如果库存不足,调用订单服务的补偿接口,把订单状态改为"已取消"。
  • 第三步:支付服务扣款。如果扣款失败,调用库存服务的补偿接口(恢复库存),再调用订单服务的补偿接口(取消订单)。

关键:每个服务必须实现正向操作和补偿操作。补偿操作不是"删除数据",是"把状态恢复到操作前"。

方案二:TCC 模式——Try-Confirm-Cancel

TCC 比 Saga 更精细,分三个阶段:

  • Try:各服务预留资源(比如库存服务冻结库存,支付服务冻结额度),但不真正提交。
  • Confirm:所有服务 Try 都成功后,统一 Confirm,真正提交。
  • Cancel:任一服务 Try 失败,统一 Cancel,释放预留资源。

TCC vs Saga 的区别:TCC 在 Try 阶段就锁定了资源,保证了更强的隔离性;Saga 的正向操作直接提交,靠补偿回滚,隔离性弱但实现简单。

方案三:本地消息表——最朴素但最可靠

本地消息表的思路是:每个服务在执行本地事务的同时,往自己的"消息表"里插入一条记录。这条记录记录了"我做了什么,需要通知谁"。然后有一个独立的消费者扫描消息表,把消息推给目标服务。

举例:订单服务创建订单后,在本地事务中同时插入一条消息记录{target: "inventory", action: "deduct", orderId: "xxx"}。消费者扫描到这条消息,调用库存服务扣减库存。库存服务处理完后,回传确认。如果库存服务处理失败,消息状态保持"待处理",消费者会重试。

为什么可靠:消息的插入和业务操作在同一个本地事务中,要么都成功要么都失败,不会出现"业务做了但消息没发"的情况。

为什么不推荐用分布式事务框架直接上 2PC:两阶段提交在高并发场景下性能很差。持有锁的时间长、容易死锁、一个节点慢全体等。生产环境里,2PC 用得很少。


四、跨库查询怎么解决——不 JOIN 了,数据怎么拼?

拆了库之后,原来一条 SQL 就能搞定的跨表查询,现在数据分散在不同库里了。怎么办?

方法一:API 聚合

查询时,应用层分别调用各服务的 API,拿到数据后在内存中拼接。

优点:实现简单,不需要额外的数据同步。

缺点:多次网络调用,延迟高。不适合需要频繁跨库查询的场景。

适合:低频查询、数据量小的场景。

方法二:CDC 数据同步 + 宽表

用 CDC(Change Data Capture)工具实时捕获各服务的数据库变更,同步到一个集中的查询库(或宽表)中。查询时只读这个集中库,不需要跨库。

优点:查询性能好,一次查询搞定。

缺点:需要维护 CDC 管道,数据有延迟(通常秒级)。

适合:高频查询、报表类场景。

方法三:CQRS 读写分离

写操作走各服务的独立数据库,读操作走一个专门构建的查询库(读模型)。读模型通过事件驱动从各服务同步数据。

优点:读写彻底解耦,读模型可以按查询需求自由设计。

缺点:架构复杂度最高,需要完整的事件体系。

适合:读写比例悬殊、查询模式复杂的场景。


五、数据库版本管理——每个服务独立演进,怎么管?

拆了库之后,每个服务有自己的数据库 schema。版本管理怎么做?不能靠手动跑 SQL 脚本。

工具选择

Flyway:基于 SQL 脚本的版本管理工具。每个变更是一个独立的 SQL 文件,按版本号顺序执行。简单、可靠、学习成本低。

Liquibase:基于 XML/YAML/JSON 的变更日志管理。支持回滚脚本、差异生成。功能更强但学习成本高。

微服务环境下的实践

原则一:每个服务自带数据库版本管理。服务启动时自动检查并执行未应用的变更脚本。不要把版本管理放到 CI/CD 流水线里做,它应该是服务的一部分。

原则二:变更脚本只改自己服务的库。跨库变更通过数据同步或 API 协调,不要在一个服务的变更脚本里改其他服务的表。

原则三:向后兼容。改 schema 时,保证旧版本服务也能正常运行。比如加字段而不是改字段,旧代码不会用到新字段,所以不会报错。

# Flyway 脚本命名规范 V1__create_orders.sql -- 创建订单表 V2__add_order_status.sql -- 增加订单状态字段 V3__create_order_items.sql -- 创建订单项表

注意:不要跳过版本号。Flyway 严格按版本号顺序执行,跳过会导致版本不一致。


六、公共数据的处理——用户、权限、字典表放哪?

有些数据是多个服务都需要用的:用户信息、权限数据、字典/配置表。放哪个库?

方案一:归口服务管理。用户数据归"用户服务"管,权限数据归"权限服务"管。其他服务需要这些数据时,通过 API 查询或 CDC 同步。

方案二:独立基础库。建一个"基础数据"库,存放用户、权限、字典等公共服务数据。所有服务只读不写这个库,写操作只能通过对应的管理服务。

方案三:各服务各自冗余。每个服务缓存自己需要的公共数据(比如订单服务缓存用户昵称)。通过消息机制保持缓存更新。适合读多写少的场景。

我的建议:用户和权限用方案一(归口服务管理),字典和配置用方案三(各服务缓存)。基础库方案增加了架构复杂度,除非你的公共数据量很大。


对比:五种微服务数据库模式

模式隔离性复杂度跨库查询适用阶段
共享库一个 JOIN 搞定起步期(< 5 服务)
一服务一库API 聚合或 CDC成长期(5-20 服务)
按域拆分域内 JOIN,域间 CDC成熟期(20+ 服务)
核心/边缘分离核心高、边缘低低-中核心库独立,边缘可 JOIN团队规模有限
CQRS 读写分离读模型一次查询读写比例悬殊

决策框架:按三个维度选择拆分策略

维度一:服务数量

  • < 5 个:共享库即可,先跑起来再说
  • 5-20 个:一服务一库或按域拆分
  • 20+ 个:按域拆分 + 域内共享

维度二:团队规模

  • 1-2 个开发团队:核心/边缘分离,降低运维负担
  • 3-5 个开发团队:按域拆分,每个团队负责一个域
  • 5+ 个团队:一服务一库 + CQRS

维度三:数据耦合度

  • 低(服务间数据交互少):一服务一库
  • 中(有跨服务查询但不频繁):按域拆分 + 域内共享
  • 高(频繁跨服务查询):CDC 宽表 + CQRS 读模型

微服务数据库治理检查清单

拆分前评估

  • 梳理现有共享库的表,标记哪些表被哪些服务使用
  • 识别跨服务的数据依赖关系(A 服务写、B 服务读的表)
  • 评估拆分后的跨库查询需求,提前规划解决方案

拆分后验证

  • 确认每个服务的数据库连接独立,连接数在合理范围
  • 确认 schema 变更不会影响到其他服务
  • 确认跨库一致性方案(Saga/TCC/本地消息表)的补偿逻辑正确
  • 确认公共数据的归口管理方案已落实

日常运维

  • 每个服务的数据库版本管理脚本已纳入代码仓库
  • 跨库数据同步的延迟在监控范围内(CDC 延迟 < 5 秒)
  • 定期演练补偿逻辑,确保异常场景下能正确回滚
  • 新增服务时,按拆分策略分配数据库资源,不要走共享库的老路

总结

微服务数据库治理的核心就三条:

拆要拆到位——按服务或按业务域隔离,不要共享库走到黑。

一致性选对方案——不要一上来就搞 2PC,Saga 或本地消息表在大部分场景下够用。

版本管理自动化——每个服务自带数据库版本管理,变更脚本向后兼容。

实际项目里,我见过最惨的情况是 15 个服务共享一个库,改一个字段要拉五个团队开会。拆了之后,每个团队管自己的库,效率高了很多,跨库数据用 CDC 同步,延迟在秒级可接受。

这条路每个做微服务的团队都要走,早点规划比后期重构好。

后续我会继续分享分布式事务落地实战、CDC 数据同步方案对比这些话题,跟着我一篇篇学,数据库这块就没问题了。

有问题评论区见。


喜欢把枯燥的技术文档变成"手把手教程"。关注我,数据库这块我们一起搞定。