--- tags: [microservice, database, sharding, replication] create time: 2026-05-05 --- # 数据库拆分 ## 概述 微服务的核心设计原则是 **"每个服务拥有独立数据库"**,这意味着每个服务的表结构、数据存储、甚至数据库类型都可以不同。但当单表数据量持续增长时,就面临拆分的需求。 ```mermaid graph LR S1[订单服务 DB] S2[库存服务 DB] subgraph BAD["反模式:共享数据库"] S1 --- SHARED[(共享 DB)] S2 --- SHARED end style SHARED fill:#ffebee,stroke:#ef5350 ``` > [!failure] 反模式警告 > 如果两个服务连接同一个数据库的同一张表,它们就不再是独立的微服务——你得到的是**分布式单体**。服务可以随意互相查询彼此的数据,失去了边界和自治性。 ## 垂直拆分 vs 水平拆分 ### 垂直拆分(按业务域) 按 **微服务边界** 拆库——这是微服务的标配。 ```mermaid graph TB subgraph "DB-Order" orders[orders] order_items[order_items] order_status[order_status] end subgraph "DB-User" users[users] user_profiles[user_profiles] user_addresses[user_addresses] end subgraph "DB-Product" products[products] categories[categories] product_images[product_images] end ``` | 特点 | 说明 | |------|------| | 每个服务独占一个数据库 | 物理隔离,互不干扰 | | 可异构选型 | 订单用 MySQL,用户用 PostgreSQL,搜索用 Elasticsearch | | 天然解耦 | 服务间不能直接查对方库 | ### 水平拆分(分库分表) 当单个表的记录量达到千万级以上,需要进一步拆分。 ```mermaid graph TB subgraph "分库策略" DB1[(DB-01)] DB2[(DB-02)] DB3[(DB-03)] end subgraph "order_0001 表" Row1[userId=1 → order_0001] Row2[userId=4 → order_0001] end subgraph "order_0002 表" Row3[userId=2 → order_0002] Row4[userId=5 → order_0002] end userId_mod["ORDER BY user_id % 2"] --> DB1 userId_mod --> DB2 ``` ### 常见分片策略 | 策略 | 哈希公式 | 优点 | 缺点 | |------|---------|------|------| | **Hash Mod** | `user_id % N` | 简单高效,路由确定 | 扩缩容困难,数据迁移成本高 | | **Range** | `user_id BETWEEN x AND y` | 范围查询友好 | 热点用户集中到单分片 | | **Time-based** | `year_month` | 按生命周期管理 | 新分片写入压力大 | | **Geo-based** | 按地域分片 | 本地化访问,延迟低 | 跨区域操作复杂 | ### ShardingSphere / MyCat > [!tip] 推荐中间件 > > **Apache ShardingSphere** 是国内使用最广泛的分库分表方案: > - 支持 JDBC / Proxy / Sidecar 三种部署模式 > - 内置分片算法:Mod、Range、Hash、Tag > - 分布式主键生成器(Snowflake)原生集成 > - 读写分离、强制路由、广播表等高级特性 #### ShardingSphere 配置示例 ```yaml # sharding-jdbc 配置 sharding jdbc: data-sources: ds0: { type: HikariCP, ... } ds1: { type: HikariCP, ... } sharding: tables: orders: actual-data-nodes: ds$->{0..1}.orders$->{0..1} table-strategy: standard: sharding-column: user_id sharding-algorithm-name: user-id-mod key-generate-strategy: column: order_id key-generator-name: snowflake sharding-algorithms: user-id-mod: type: MOD props: sharding-count: 2 ``` ## 跨库查询方案 > [!question] 经典难题 > 订单服务需要展示用户的姓名和手机号来做收货地址。但用户信息在用户库,订单数据在订单库——怎么办? ### 方案对比 | 方案 | 描述 | 性能 | 复杂度 | 适用场景 | |------|------|------|--------|---------| | **冗余字段** | 订单表存用户名字段 | ⭐⭐⭐⭐⭐ | 低 | 只读字段,变更频率低 | | **接口组装** | 先查订单,再调用户服务补全 | ⭐⭐⭐ | 中 | 偶尔需要关联的场景 | | **CQRS / 宽表** | 异步同步一份宽表用于查询 | ⭐⭐⭐⭐ | 高 | 高频关联查询 | | **搜索引擎** | ES/Kibana 做关联查询 | ⭐⭐⭐⭐ | 中高 | 复杂搜索 + 聚合 | ### 冗余字段实践 ```sql -- 订单表冗余关键字段 CREATE TABLE orders ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, username VARCHAR(64), -- 冗余用户名(快照,不参与编辑) phone VARCHAR(20), -- 冗余手机号(脱敏存储) created_at TIMESTAMP DEFAULT NOW(), INDEX idx_user (user_id) ); ``` > [!note] 冗余数据的维护 > > 用户改名字了怎么办?**不改订单表**。订单上的 username 是该时刻的"快照"——它反映的是下单时的状态,不是当前状态。这符合业务语义。 > > 如果需要批量更新冗余字段(如用户头像),通过消息队列异步通知订单服务更新。 ## 数据库迁移工具 ```mermaid graph LR Dev["开发环境"] -->|"flyway migrate"| Stage["Staging"] Stage -->|"人工审批"| Prod["Production"] Prod -->|"flyway validate"| Check["校验版本一致性"] ``` | 工具 | 语言 | 特点 | |------|------|------| | **Flyway** | Java | 基于文件命名,SQL 脚本方式,简单直观 | | **Liquibase** | Java | XML/YAML/JSON 格式,支持回滚生成 | | **golang-migrate** | Go | CLI 工具,轻量,适合 Go 项目 | ### Flyway 迁移流程 ``` db/migration/ ├── V1__create_users_table.sql ├── V2__add_user_phone.sql ├── V3__create_orders_table.sql └── V4__add_order_status_enum.sql ``` 执行顺序:V1 → V2 → V3 → V4。Flyway 内部维护 `_schema_version` 表追踪已执行的迁移。 ## 关联笔记 - [[03-数据一致性/02-分布式事务]] — 拆分后的数据一致性问题 - [[01-基础概念]] — DDD 限界上下文与数据库拆分的对应关系