This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/MS/03-数据一致性/01-数据库拆分.md
T
2026-05-17 22:00:24 +08:00

199 lines
5.9 KiB
Markdown

---
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 限界上下文与数据库拆分的对应关系