name: database-sharding description: PostgreSQL/MySQL 数据库分片,使用哈希/范围/目录策略。用于水平扩展、多租户隔离、数十亿记录,或处理错误的分片键、热点、跨分片事务、重新平衡问题。 keywords: 数据库分片, 水平分区, 分片键, 一致性哈希, 哈希分片, 范围分片, 目录分片, 跨分片查询, 分片重新平衡, 分散-聚集模式, 多租户分片, 分片路由器, 分布式数据库, 数据库可扩展性, 分片迁移, 热点分片, 分片键选择, 跨分片聚合, 两阶段提交, Saga 模式, 虚拟分片 license: MIT
database-sharding
全面的数据库分片模式,用于水平扩展,包括哈希、范围和基于目录的策略。
快速入门(10分钟)
步骤1:从模板中选择分片策略:
# 基于哈希(均匀分布)
cat templates/hash-router.ts
# 基于范围(时间序列数据)
cat templates/range-router.ts
# 基于目录(多租户)
cat templates/directory-router.ts
步骤2:选择分片键标准:
- ✅ 高基数性(数百万唯一值)
- ✅ 均匀分布(无单个值超过5%)
- ✅ 不可变性(永不更改)
- ✅ 查询对齐(包含在80%以上的WHERE子句中)
步骤3:实现路由器:
import { HashRouter } from './hash-router';
const router = new HashRouter([
{ id: 'shard_0', connection: { host: 'db0.example.com' } },
{ id: 'shard_1', connection: { host: 'db1.example.com' } },
{ id: 'shard_2', connection: { host: 'db2.example.com' } },
{ id: 'shard_3', connection: { host: 'db3.example.com' } },
]);
// 查询单个分片
const user = await router.query('user_123', 'SELECT * FROM users WHERE id = $1', ['user_123']);
关键规则
✓ 务必做到
| 规则 | 原因 |
|---|---|
| 在查询中包含分片键 | 避免扫描所有分片(速度慢100倍) |
| 监控分片分布 | 在导致故障前检测热点 |
| 预先计划重新平衡 | 后期无法轻松添加分片 |
| 选择不可变分片键 | 更改键 = 数据迁移噩梦 |
| 用生产数据测试分布 | 合成数据隐藏真实热点 |
| 反规范化以实现数据本地性 | 将相关数据保留在同一分片 |
✗ 切勿做
| 反模式 | 为什么不好 |
|---|---|
| 使用范围分片的顺序ID | 最新分片获得所有写入(热点) |
| 使用时间戳作为分片键 | 最近分片过载 |
| 不使用2PC的跨分片事务 | 数据损坏、不一致 |
| 不使用一致性哈希的简单取模 | 无法添加分片而无需完全重新分片 |
| 可为空的分片键 | 特殊NULL处理创建热点 |
| 无分片路由层 | 硬编码分片 = 无法重新平衡 |
前7个关键错误
错误1:错误的分片键选择(热点)
症状:一个分片接收80%以上的流量 修复:
// ❌ 错误:低基数性(状态字段)
shard_key = order.status; // 90%为'pending' → shard_0过载
// ✅ 正确:高基数性(用户ID)
shard_key = order.user_id; // 数百万用户,均匀分布
错误2:查询中缺失分片键
症状:查询扫描所有分片(极慢) 修复:
// ❌ 错误:无分片键
SELECT * FROM orders WHERE status = 'shipped'; // 扫描所有100个分片!
// ✅ 正确:包含分片键
SELECT * FROM orders WHERE user_id = ? AND status = 'shipped'; // 目标1个分片
错误3:使用范围分片的顺序ID
症状:最新分片获得所有写入 修复:
// ❌ 错误:使用自增的范围分片
// 分片0: 1-1M, 分片1: 1M-2M, 分片2: 2M+ → 所有新写入到分片2!
// ✅ 正确:基于哈希的分片
const shardId = hash(id) % shardCount; // 均匀分布
错误4:无重新平衡策略
症状:受限于初始分片数,无法扩展 修复:
// ❌ 错误:简单取模
const shardId = hash(key) % shardCount; // 添加第5个分片会破坏所有键
// ✅ 正确:一致性哈希
const ring = new ConsistentHashRing(shards);
const shardId = ring.getNode(key); // 添加分片时仅约25%的键移动
错误5:跨分片事务
症状:数据不一致、部分写入 修复:
// ❌ 错误:跨分片事务(会损坏)
BEGIN;
UPDATE shard_1.accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE shard_2.accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT; // 如果分片2失败,分片1已提交!
// ✅ 正确:两阶段提交或Saga模式
const txn = new TwoPhaseCommitTransaction();
txn.addOperation(shard_1, 'UPDATE accounts SET balance = balance - 100 WHERE id = ?', ['A']);
txn.addOperation(shard_2, 'UPDATE accounts SET balance = balance + 100 WHERE id = ?', ['B']);
await txn.execute(); // 跨分片原子性
错误6:可变分片键
症状:记录移动分片,导致重复 修复:
// ❌ 错误:按国家分片(用户搬迁)
shard_key = user.country; // 用户从美国移动到加拿大,现在在不同分片!
// ✅ 正确:按不可变用户ID分片
shard_key = user.id; // 永不更改
错误7:无监控
症状:静默热点、突然性能下降 修复:
// ✅ 必需的指标
- 每个分片的记录数(应在20%以内)
- 查询分布(无分片超过40%的查询)
- 每个分片的存储(在80%时警报)
- 每个分片的延迟p99
加载 references/error-catalog.md 获取所有10个错误及详细修复。
分片策略
| 策略 | 最适合 | 优点 | 缺点 |
|---|---|---|---|
| 哈希 | 用户数据、均匀负载关键 | 无热点、可预测 | 范围查询分散 |
| 范围 | 时间序列、日志、仅追加 | 范围查询高效、归档 | 最近分片热点 |
| 目录 | 多租户、复杂路由 | 灵活、易于重新平衡 | 查找开销、单点故障 |
加载 references/sharding-strategies.md 获取详细比较及生产示例(Instagram、Discord、Salesforce)。
分片键选择标准
| 标准 | 重要性 | 检查方法 |
|---|---|---|
| 高基数性 | 关键 | COUNT(DISTINCT shard_key) > 分片数 × 100 |
| 均匀分布 | 关键 | 无值超过总数的5% |
| 不可变性 | 关键 | 值永不更改 |
| 查询对齐 | 高 | 80%以上的查询包含它 |
| 数据本地性 | 中 | 相关记录在一起 |
决策树:
- 用户中心应用 →
user_id - 多租户SaaS →
tenant_id - 时间序列/日志 →
timestamp(范围分片) - 产品目录 →
product_id
加载 references/shard-key-selection.md 获取全面决策树和测试策略。
配置摘要
基于哈希的路由器
import { HashRouter } from './templates/hash-router';
const router = new HashRouter([
{ id: 'shard_0', connection: { /* PostgreSQL 配置 */ } },
{ id: 'shard_1', connection: { /* PostgreSQL 配置 */ } },
]);
// 自动路由到正确分片
const user = await router.query('user_123', 'SELECT * FROM users WHERE id = $1', ['user_123']);
基于范围的路由器
import { RangeRouter } from './templates/range-router';
const router = new RangeRouter(shardConfigs, [
{ start: Date.parse('2024-01-01'), end: Date.parse('2024-04-01'), shardId: 'shard_q1' },
{ start: Date.parse('2024-04-01'), end: Date.parse('2024-07-01'), shardId: 'shard_q2' },
{ start: Date.parse('2024-07-01'), end: Infinity, shardId: 'shard_q3' },
]);
// 范围查询目标特定分片
const janEvents = await router.queryRange(
Date.parse('2024-01-01'),
Date.parse('2024-02-01'),
'SELECT * FROM events WHERE created_at BETWEEN $1 AND $2'
);
基于目录的路由器
import { DirectoryRouter } from './templates/directory-router';
const router = new DirectoryRouter(directoryDBConfig, shardConfigs);
// 分配租户到特定分片
await router.assignShard('tenant_acme', 'shard_enterprise');
// 自动路由
const users = await router.query('tenant_acme', 'SELECT * FROM users');
何时加载参考
选择策略
加载 references/sharding-strategies.md 当:
- 决定哈希、范围、目录之间选择
- 需要生产示例(Instagram、Discord)
- 计划混合方法
选择分片键
加载 references/shard-key-selection.md 当:
- 为新项目选择分片键
- 评估现有分片键
- 用生产数据测试分布
实现
加载 references/implementation-patterns.md 当:
- 从零开始构建分片路由器
- 实现一致性哈希
- 需要事务处理(2PC、Saga)
- 设置监控/指标
跨分片操作
加载 references/cross-shard-queries.md 当:
- 需要跨分片聚合(COUNT、SUM、AVG)
- 实现跨分片连接
- 构建跨分片分页
- 优化分散-聚集模式
重新平衡
加载 references/rebalancing-guide.md 当:
- 添加新分片
- 在分片间迁移数据
- 计划零停机迁移
- 平衡不均匀负载
错误预防
加载 references/error-catalog.md 当:
- 故障排除性能问题
- 审查分片架构
- 所有10个文档化错误及修复
完整设置清单
分片前:
- [ ] 用生产数据测试分片键分布
- [ ] 分片键包含在80%以上的查询中
- [ ] 监控基础设施就绪
- [ ] 重新平衡策略已计划
路由器实现:
- [ ] 分片路由层(非硬编码分片)
- [ ] 每个分片的连接池
- [ ] 错误处理和重试
- [ ] 指标收集(查询/分片、延迟)
分片配置:
- [ ] 初始4-8个分片(留有增长空间)
- [ ] 一致性哈希或虚拟分片
- [ ] 每个分片的副本(高可用)
- [ ] 每个分片的备份策略
应用更改:
- [ ] 所有查询包含分片键
- [ ] 消除跨分片连接(反规范化)
- [ ] 尊重事务边界
- [ ] 连接池配置
生产示例
之前(单数据库过载):
// 单个PostgreSQL实例
const db = new Pool({ host: 'db.example.com' });
// 所有1000万用户在单个服务器上
const users = await db.query('SELECT * FROM users WHERE status = $1', ['active']);
// 查询时间:5000ms(慢!)
// 数据库CPU:95%
// 磁盘:500GB,增长中
之后(分片到8个服务器):
// 基于哈希的分片,8个分片
const router = new HashRouter([
{ id: 'shard_0', connection: { host: 'db0.example.com' } },
{ id: 'shard_1', connection: { host: 'db1.example.com' } },
// ... 6个更多分片
]);
// 查询单个用户(目标1个分片)
const user = await router.query('user_123', 'SELECT * FROM users WHERE id = $1', ['user_123']);
// 查询时间:10ms(快500倍!)
// 查询所有分片(分散-聚集)
const allActive = await router.queryAll('SELECT * FROM users WHERE status = $1', ['active']);
// 查询时间:800ms(并行化跨8个分片,比单个快6倍)
// 结果:每个分片处理约125万用户
// 每个分片数据库CPU:20%
// 每个分片磁盘:65GB
// 可轻松扩展到16个分片(一致性哈希)
已知问题预防
所有10个文档化错误已预防:
- ✅ 错误分片键(热点) → 首先测试分布
- ✅ 查询中缺失分片键 → 代码审查、linting
- ✅ 跨分片事务 → 使用2PC或Saga模式
- ✅ 顺序ID热点 → 使用基于哈希的分片
- ✅ 无重新平衡策略 → 从一开始使用一致性哈希
- ✅ 时间戳分片热点 → 混合哈希+范围方法
- ✅ 可变分片键 → 选择不可变键(user_id)
- ✅ 无路由层 → 从开始抽象路由器
- ✅ 无监控 → 跟踪每个分片指标
- ✅ 弱哈希函数 → 使用MD5、MurmurHash3、xxHash
参见:references/error-catalog.md 获取详细修复
资源
模板:
templates/hash-router.ts- 基于哈希的分片templates/range-router.ts- 基于范围的分片templates/directory-router.ts- 基于目录的分片templates/cross-shard-aggregation.ts- 聚合模式
参考:
references/sharding-strategies.md- 策略比较references/shard-key-selection.md- 键选择指南references/implementation-patterns.md- 路由器实现references/cross-shard-queries.md- 查询模式references/rebalancing-guide.md- 迁移策略references/error-catalog.md- 所有10个错误文档化
生产示例:
- Instagram:媒体范围分片
- Discord:消息哈希分片
- Salesforce:组织目录分片
生产测试 | 10个错误预防 | MIT许可证