MongoDB的选择片键 - 完整知识点
一、评估使用情况
1.1 工作负载分析
语法知识点:
- 读写比例分析
- 操作类型分布(增删改查)
- 查询模式识别
- 数据增长速率评估
案例代码:
javascript
// 1. 使用数据库分析命令查看集合统计信息
use sales_db
// 获取集合的详细统计信息
db.sales.aggregate([
{
$collStats: {
storageStats: {}
}
}
]).pretty()
// 2. 分析查询模式 - 查看慢查询日志
db.setProfilingLevel(2) // 开启慢查询分析,记录所有操作
// 查看最近的慢查询记录
db.system.profile.find().limit(5).pretty()
// 3. 分析读写比例 - 使用mongostat工具
// 在命令行执行:mongostat --port 27017 5
// 4. 查看集合索引使用情况
db.sales.aggregate([
{ $indexStats: {} }
]).pretty()
// 5. 分析数据增长趋势
db.sales.aggregate([
{
$group: {
_id: {
year: { $year: "$orderDate" },
month: { $month: "$orderDate" }
},
count: { $sum: 1 },
totalAmount: { $sum: "$amount" }
}
},
{ $sort: { "_id.year": 1, "_id.month": 1 } }
])
1.2 查询模式识别
语法知识点:
- 高频查询字段识别
- 范围查询 vs 精确匹配
- 排序和聚合操作需求
案例代码:
javascript
// 1. 分析最常见的查询条件
db.sales.aggregate([
{ $match: { "query": { $exists: true } } },
{ $group: {
_id: "$query",
count: { $sum: 1 }
}},
{ $sort: { count: -1 } },
{ $limit: 10 }
])
// 2. 创建示例数据
for(let i = 0; i < 10000; i++) {
db.orders.insertOne({
orderId: i,
userId: Math.floor(Math.random() * 1000),
productId: Math.floor(Math.random() * 500),
orderDate: new Date(2024, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28)),
amount: Math.random() * 1000,
status: ['pending', 'completed', 'cancelled'][Math.floor(Math.random() * 3)],
region: ['North', 'South', 'East', 'West'][Math.floor(Math.random() * 4)]
})
}
// 3. 分析查询模式 - 查找常见查询
db.orders.getPlanCache().listQueryShapes()
// 4. 测试不同查询模式的性能
// 精确匹配查询
db.orders.find({ userId: 500 }).explain("executionStats")
// 范围查询
db.orders.find({
orderDate: {
$gte: new Date(2024, 0, 1),
$lte: new Date(2024, 5, 30)
}
}).explain("executionStats")
// 复合查询
db.orders.find({
region: "North",
status: "completed",
amount: { $gt: 500 }
}).explain("executionStats")
二、描绘分发情况
2.1 数据分布分析
语法知识点:
- 数据均匀性检查
- 片键基数评估
- 数据热点检测
案例代码:
javascript
// 1. 分析字段值分布情况
// 检查userId字段的分布
db.orders.aggregate([
{ $group: {
_id: "$userId",
count: { $sum: 1 }
}},
{ $sort: { count: -1 } },
{ $limit: 20 }
])
// 2. 计算字段的基数(唯一值数量)
db.orders.aggregate([
{ $group: {
_id: null,
uniqueUserIds: { $addToSet: "$userId" },
uniqueProductIds: { $addToSet: "$productId" },
uniqueRegions: { $addToSet: "$region" }
}},
{ $project: {
userIdCardinality: { $size: "$uniqueUserIds" },
productIdCardinality: { $size: "$uniqueProductIds" },
regionCardinality: { $size: "$uniqueRegions" }
}}
])
// 3. 检测数据热点 - 找出高频值
db.orders.aggregate([
{ $group: {
_id: "$userId",
count: { $sum: 1 },
totalAmount: { $sum: "$amount" }
}},
{ $match: { count: { $gt: 100 } } }, // 高频用户
{ $sort: { count: -1 } }
])
// 4. 可视化数据分布 - 创建分布直方图
db.orders.aggregate([
{
$bucket: {
groupBy: "$amount",
boundaries: [0, 100, 200, 500, 1000, 5000],
default: "Other",
output: {
count: { $sum: 1 },
orders: { $push: "$$ROOT" }
}
}
}
])
// 5. 检查时间序列数据分布
db.orders.aggregate([
{
$group: {
_id: {
date: { $dateToString: { format: "%Y-%m-%d", date: "$orderDate" } }
},
orderCount: { $sum: 1 },
totalSales: { $sum: "$amount" }
}
},
{ $sort: { "_id.date": 1 } }
])
2.2 片键基数评估
语法知识点:
- 高基数 vs 低基数片键
- 单调递增/递减片键影响
- 哈希索引的使用
案例代码:
javascript
// 1. 评估不同字段的基数
function evaluateCardinality(collection, field) {
return collection.aggregate([
{ $group: {
_id: `$${field}`,
count: { $sum: 1 }
}},
{ $group: {
_id: null,
uniqueValues: { $sum: 1 },
totalDocs: { $sum: "$count" }
}},
{ $project: {
cardinality: "$uniqueValues",
uniqueness: {
$multiply: [
{ $divide: ["$uniqueValues", "$totalDocs"] },
100
]
}
}}
]).next()
}
// 测试不同字段的基数
print("UserId cardinality:", evaluateCardinality(db.orders, "userId"))
print("Region cardinality:", evaluateCardinality(db.orders, "region"))
print("Status cardinality:", evaluateCardinality(db.orders, "status"))
// 2. 创建哈希索引示例
// 对于单调递增的字段,使用哈希分片
db.orders.createIndex({ orderId: "hashed" })
// 3. 复合片键的基数评估
db.orders.aggregate([
{ $group: {
_id: {
userId: "$userId",
region: "$region"
},
count: { $sum: 1 }
}},
{ $group: {
_id: null,
uniqueCombinations: { $sum: 1 }
}}
])
// 4. 分析写入分布模式
// 检查是否使用了单调递增的ObjectId
db.orders.aggregate([
{ $project: {
timestamp: { $toLong: "$_id" }
}},
{ $sort: { timestamp: 1 } },
{ $limit: 100 }
])
三、片键策略
3.1 哈希分片策略
语法知识点:
- sh.shardCollection() 语法
- hashed索引创建
- 数据均匀分布保证
案例代码:
javascript
// 1. 启用分片集群
sh.enableSharding("sales_db")
// 2. 哈希分片 - 使用哈希片键
// 创建哈希索引
db.orders.createIndex({ userId: "hashed" })
// 配置哈希分片
sh.shardCollection("sales_db.orders", { userId: "hashed" })
// 3. 复合哈希分片(MongoDB 4.4+)
db.orders.createIndex({ userId: 1, orderDate: "hashed" })
sh.shardCollection("sales_db.orders", { userId: 1, orderDate: "hashed" })
// 4. 验证哈希分片的数据分布
db.orders.getShardDistribution()
// 5. 查看分片键的哈希值
db.orders.aggregate([
{ $project: {
userId: 1,
userIdHash: { $toHashedIndexKey: "$userId" }
}},
{ $limit: 10 }
])
// 6. 监控哈希分片的均衡情况
sh.status(true) // 查看分片状态
// 7. 在哈希分片上执行查询
// 精确查询 - 高效
db.orders.find({ userId: 500 })
// 范围查询 - 效率较低(会广播到所有分片)
db.orders.find({ userId: { $gt: 400, $lt: 600 } })
3.2 范围分片策略
语法知识点:
- 范围片键配置
- 查询隔离性
- 数据局部性保证
案例代码:
javascript
// 1. 范围分片 - 基于时间戳
db.logs.createIndex({ timestamp: 1 })
sh.shardCollection("sales_db.logs", { timestamp: 1 })
// 2. 范围分片 - 复合片键
db.transactions.createIndex({ region: 1, userId: 1, transactionDate: 1 })
sh.shardCollection("sales_db.transactions", { region: 1, userId: 1, transactionDate: 1 })
// 3. 预分片 - 手动创建chunks
for(let i = 0; i < 100; i++) {
sh.splitAt("sales_db.transactions", {
region: i < 25 ? "North" : (i < 50 ? "South" : (i < 75 ? "East" : "West")),
userId: i * 1000,
transactionDate: new Date(2024, i % 12, 1)
})
}
// 4. 范围查询示例
// 高效的范围查询(包含片键)
db.transactions.find({
region: "North",
userId: { $gte: 500, $lte: 1000 },
transactionDate: { $gte: new Date(2024, 0, 1) }
}).explain("executionStats")
// 5. 监控范围分片的数据分布
db.printShardingStatus()
// 6. 查看chunk分布详情
use config
db.chunks.find({ ns: "sales_db.transactions" }).pretty()
3.3 区域分片策略
语法知识点:
- addShardTag / addShardToZone
- updateZoneKeyRange
- 数据本地化
案例代码:
javascript
// 1. 配置区域分片
// 为分片添加区域标签
sh.addShardTag("shard0000", "US_East")
sh.addShardTag("shard0001", "US_West")
sh.addShardTag("shard0002", "EU_Central")
// 2. 创建区域范围
sh.addTagRange(
"sales_db.user_data",
{ region: "East" },
{ region: "East_US" },
"US_East"
)
sh.addTagRange(
"sales_db.user_data",
{ region: "West" },
{ region: "West_US" },
"US_West"
)
sh.addTagRange(
"sales_db.user_data",
{ region: "Europe" },
{ region: "Europe" },
"EU_Central"
)
// 3. 使用区域分片创建集合
sh.shardCollection("sales_db.user_data", { region: 1, userId: 1 })
// 4. 高级区域配置 - 复合键区域
sh.addTagRange(
"sales_db.global_orders",
{ country: "US", state: "CA" },
{ country: "US", state: "CA\u0000" },
"US_West"
)
// 5. 查看区域配置
sh.status()
// 6. 动态调整区域范围
sh.removeTagRange(
"sales_db.global_orders",
{ country: "US", state: "CA" },
{ country: "US", state: "CA\u0000" }
)
// 7. 验证区域数据分布
db.user_data.getShardDistribution()
四、片键规则和指导方针
4.1 片键选择原则
语法知识点:
- 查询隔离原则
- 写分布原则
- 片键不可变性
案例代码:
javascript
// 1. 好的片键示例 - 高基数、查询模式匹配
// 场景:电商系统,经常按用户ID查询订单
db.orders.createIndex({ userId: 1, orderDate: -1 })
sh.shardCollection("sales_db.orders", { userId: 1, orderDate: -1 })
// 2. 避免的片键模式
// ❌ 低基数片键
db.products.createIndex({ category: 1 })
sh.shardCollection("sales_db.products", { category: 1 }) // 只有少数几个类别
// ❌ 单调递增片键
db.logs.createIndex({ timestamp: 1 })
sh.shardCollection("sales_db.logs", { timestamp: 1 }) // 所有写入集中在最后一个chunk
// 3. 使用复合片键解决低基数问题
db.products.createIndex({ category: 1, productId: 1 })
sh.shardCollection("sales_db.products", { category: 1, productId: 1 })
// 4. 使用哈希片键解决单调递增问题
db.logs.createIndex({ timestamp: "hashed" })
sh.shardCollection("sales_db.logs", { timestamp: "hashed" })
// 5. 片键不可变性演示
// 创建集合并配置片键
sh.shardCollection("sales_db.orders", { userId: 1 })
// 尝试更新片键值 - 会失败
try {
db.orders.updateOne(
{ userId: 100 },
{ $set: { userId: 200 } } // 不允许修改片键值
)
} catch(e) {
print("Error:", e.message)
}
// 正确方式:删除后重新插入
db.orders.deleteOne({ userId: 100 })
db.orders.insertOne({ userId: 200, amount: 500 })
4.2 片键性能优化
语法知识点:
- 索引优化
- 查询路由优化
- 批量操作优化
案例代码:
javascript
// 1. 确保片键上有索引
// 查看现有索引
db.orders.getIndexes()
// 创建覆盖查询索引
db.orders.createIndex({ userId: 1, orderDate: 1, amount: 1 })
// 2. 优化查询 - 始终包含片键
// ✅ 好的查询 - 包含片键
db.orders.find({ userId: 500, status: "completed" })
// ❌ 坏的查询 - 缺少片键(会广播到所有分片)
db.orders.find({ status: "completed" })
// 3. 批量操作优化
// 错误的批量插入 - 跨多个分片
for(let i = 0; i < 1000; i++) {
db.orders.insertOne({ userId: i, amount: i * 10 })
}
// 正确的批量插入 - 按片键分组
const bulkOps = []
for(let i = 0; i < 1000; i++) {
bulkOps.push({
insertOne: {
document: { userId: i % 100, amount: i * 10 }
}
})
if(bulkOps.length === 100) {
db.orders.bulkWrite(bulkOps)
bulkOps.length = 0
}
}
// 4. 使用hint强制使用片键索引
db.orders.find({ userId: 500, amount: { $gt: 100 } })
.hint({ userId: 1, orderDate: 1 })
// 5. 分析查询性能
db.orders.find({ userId: 500 })
.explain("executionStats")
.queryPlanner.winningPlan
五、控制数据分发
5.1 数据均衡管理
语法知识点:
- 均衡器配置
- chunk大小调整
- 手动均衡操作
案例代码:
javascript
// 1. 查看均衡器状态
sh.getBalancerState()
sh.isBalancerRunning()
// 2. 配置均衡器窗口
use config
db.settings.updateOne(
{ _id: "balancer" },
{ $set: {
activeWindow: {
start: "01:00", // 凌晨1点开始
stop: "05:00" // 凌晨5点结束
}
}},
{ upsert: true }
)
// 3. 禁用/启用均衡器
sh.stopBalancer()
sh.startBalancer()
// 4. 调整chunk大小(默认64MB)
use config
db.settings.save({
_id: "chunksize",
value: 128 // 修改为128MB
})
// 5. 手动均衡chunk
// 查看chunk分布
sh.status()
// 手动迁移chunk
sh.moveChunk(
"sales_db.orders",
{ userId: 500 }, // chunk的片键值
"shard0002" // 目标分片
)
// 6. 检查chunk均衡状态
db.printShardingStatus(true)
// 7. 监控均衡过程
use admin
db.runCommand({ balancerStatus: 1 })
// 8. 查看chunk分裂信息
use config
db.chunks.find({ ns: "sales_db.orders" })
.sort({ min: 1 })
.pretty()
5.2 数据本地化控制
语法知识点:
- 区域(Zone)配置
- 标签(Tag)使用
- 数据亲和性设置
案例代码:
javascript
// 1. 创建区域配置
// 添加分片到区域
sh.addShardToZone("shard0000", "HOT_ZONE")
sh.addShardToZone("shard0001", "WARM_ZONE")
sh.addShardToZone("shard0002", "COLD_ZONE")
// 2. 配置区域范围
// 热数据区域 - 最近7天的订单
sh.updateZoneKeyRange(
"sales_db.orders",
{ orderDate: new Date(2024, 5, 1) },
{ orderDate: new Date(2024, 5, 8) },
"HOT_ZONE"
)
// 温数据区域 - 30天前的订单
sh.updateZoneKeyRange(
"sales_db.orders",
{ orderDate: new Date(2024, 4, 1) },
{ orderDate: new Date(2024, 5, 1) },
"WARM_ZONE"
)
// 3. 创建分层存储策略
// 为不同区域设置不同的存储引擎
use admin
db.runCommand({
setParameter: 1,
"shard0000": { storageEngine: "wiredTiger", cacheSizeGB: 10 },
"shard0002": { storageEngine: "wiredTiger", cacheSizeGB: 1 }
})
// 4. 查看区域映射
sh.status({ verbose: true })
// 5. 清理区域配置
sh.removeRangeFromZone(
"sales_db.orders",
{ orderDate: new Date(2024, 5, 1) },
{ orderDate: new Date(2024, 5, 8) }
)
// 6. 基于应用的区域分配
// 为特定客户分配专属分片
sh.addShardTag("shard0000", "VIP_CUSTOMERS")
sh.addTagRange(
"sales_db.customer_data",
{ customerTier: "VIP" },
{ customerTier: "VIP\u0000" },
"VIP_CUSTOMERS"
)
5.3 监控和调优
语法知识点:
- 分片统计信息查询
- 性能监控命令
- 告警阈值设置
案例代码:
javascript
// 1. 查看分片分布统计
db.orders.getShardDistribution()
// 2. 详细的chunk统计
db.orders.aggregate([
{ $collStats: { shard: true } },
{ $project: {
shardName: 1,
numChunks: "$shardStats.numChunks",
docsCount: "$shardStats.docsCount",
dataSizeMB: { $divide: ["$shardStats.dataSize", 1048576] }
}}
])
// 3. 监控分片性能
use admin
db.runCommand({ serverStatus: 1, sharding: 1 })
// 4. 查看慢查询统计
db.orders.aggregate([
{ $match: { "query": { $exists: true } } },
{ $group: {
_id: "$shard",
avgTime: { $avg: "$millis" },
maxTime: { $max: "$millis" },
count: { $sum: 1 }
}}
])
// 5. 设置性能告警阈值
use config
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: {
value: 64,
warningThreshold: 80 // 当chunk大小达到80%时告警
}},
{ upsert: true }
)
// 6. 创建监控脚本
function monitorShardingHealth() {
const stats = db.orders.getShardDistribution()
stats.shards.forEach(shard => {
const chunkImbalance = Math.abs(shard.dataSize - stats.avgSize) / stats.avgSize
if(chunkImbalance > 0.2) {
print(`Warning: Shard ${shard.shardId} has ${chunkImbalance * 100}% data imbalance`)
}
if(shard.dataSize > 100 * 1024 * 1024) { // 100MB
print(`Alert: Shard ${shard.shardId} exceeds 100MB`)
}
})
}
// 定期执行监控
setInterval(monitorShardingHealth, 300000) // 每5分钟执行一次
5.4 故障处理和恢复
语法知识点:
- 分片故障恢复
- chunk修复
- 数据一致性检查
案例代码:
javascript
// 1. 检查分片集群健康状态
sh.status()
db.adminCommand({ listShards: 1 })
// 2. 修复损坏的chunk
use admin
db.runCommand({
cleanupOrphaned: "sales_db.orders",
startingFromKey: { userId: 0 }
})
// 3. 重新平衡chunk
sh.balancerCollectionStatus("sales_db.orders")
// 4. 修复分片元数据
use config
db.chunks.find({ ns: "sales_db.orders", shard: "shard0000" }).forEach(function(chunk) {
// 验证chunk数据完整性
const count = db.getSiblingDB("sales_db").orders.count({
$and: [
{ userId: { $gte: chunk.min.userId } },
{ userId: { $lt: chunk.max.userId } }
]
})
if(count !== chunk.count) {
print(`Chunk inconsistency detected for chunk with min: ${chunk.min.userId}`)
// 修复chunk计数
db.chunks.updateOne(
{ _id: chunk._id },
{ $set: { count: count } }
)
}
})
// 5. 数据一致性验证
db.orders.aggregate([
{ $group: {
_id: "$userId",
shard: { $first: "$_id" }
}},
{ $group: {
_id: "$shard",
count: { $sum: 1 }
}}
])
// 6. 处理分片故障
// 移除故障分片
db.adminCommand({ removeShard: "shard0000" })
// 迁移数据到新分片
sh.moveChunk("sales_db.orders", { userId: 500 }, "shard0001", {
waitForDelete: true
})
这份详细的知识点涵盖了MongoDB选择片键的所有核心内容,包括评估使用情况、描绘分发情况、片键策略、规则指导方针以及数据分发控制。