数据库分片 database-sharding

数据库分片是一种数据库水平扩展技术,用于处理海量数据和高并发访问,通过将数据分布到多个分片上提高性能和可扩展性。它包括哈希分片、范围分片和目录分片等策略,适用于多租户隔离、数十亿记录、避免热点问题和跨分片事务处理。关键词:数据库分片、水平扩展、分片键、一致性哈希、多租户隔离、分布式数据库、架构设计。

架构设计 0 次安装 0 次浏览 更新于 3/7/2026

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个文档化错误已预防:

  1. ✅ 错误分片键(热点) → 首先测试分布
  2. ✅ 查询中缺失分片键 → 代码审查、linting
  3. ✅ 跨分片事务 → 使用2PC或Saga模式
  4. ✅ 顺序ID热点 → 使用基于哈希的分片
  5. ✅ 无重新平衡策略 → 从一开始使用一致性哈希
  6. ✅ 时间戳分片热点 → 混合哈希+范围方法
  7. ✅ 可变分片键 → 选择不可变键(user_id)
  8. ✅ 无路由层 → 从开始抽象路由器
  9. ✅ 无监控 → 跟踪每个分片指标
  10. ✅ 弱哈希函数 → 使用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许可证