MongoDB入门学习教程,从入门到精通,MongoDB的选择片键 - 完整知识点(16)

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选择片键的所有核心内容,包括评估使用情况、描绘分发情况、片键策略、规则指导方针以及数据分发控制。

相关推荐
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB分片配置完全指南(15)
数据库·学习·mongodb
y = xⁿ2 小时前
【MySQL】数据库的脏读,不可重复读和幻读,覆盖索引是什么,索引类型有哪些
数据库·mysql
小冷coding2 小时前
【面试】结合项目整理的场景面试题,覆盖 Java 基础、锁、多线程、数据库、分布式锁 / 事务、消息中间件等核心维度
java·数据库·面试
kcuwu.2 小时前
Python 正则表达式从入门到实战
数据库·python·正则表达式
卓怡学长3 小时前
m319个人网站的设计与实现
java·数据库·spring·tomcat·maven·intellij-idea
Dyanic3 小时前
AMSFusion:一种基于注意力机制的自适应多尺度红外与可见光图像融合网络
图像处理·人工智能·学习
羊小蜜.3 小时前
Mysql 07: 正则表达式查询(REGEXP)全解
数据库·mysql·正则表达式
Dxy12393102163 小时前
正则表达式如何匹配提取文章日期
数据库·mysql·正则表达式
少许极端3 小时前
算法奇妙屋(四十三)-贪心算法学习之路10
学习·算法·贪心算法