一、NoSQL数据库的默认选型逻辑

刚接触NoSQL的朋友们经常会问:这么多数据库该怎么选?其实就像选手机一样,不同场景需要不同特性。我们以MongoDB为例,看看它最适合什么场景。

先看一个电商商品管理的例子:

// MongoDB示例:商品文档结构
{
  _id: ObjectId("5f8d8a7b2f4a7d1e2c3b4c5d"),
  name: "智能手表",
  sku: "SM-WATCH-2023",
  price: 1299,
  inventory: 150,
  attributes: {
    color: ["黑色", "银色"],
    size: ["38mm", "42mm"]
  },
  reviews: [
    {
      user: "张三",
      rating: 5,
      comment: "续航很棒!"
    },
    {
      user: "李四",
      rating: 4,
      comment: "表带有点硬"
    }
  ]
}

这种嵌套文档结构特别适合商品这类具有复杂属性的数据,传统关系型数据库需要拆分成多表,而MongoDB可以直接存储。

二、五大常见使用问题解决方案

1. 事务支持不足怎么办?

虽然MongoDB 4.0+已经支持多文档事务,但性能会受影响。我们来看个库存扣减的替代方案:

// MongoDB乐观锁实现库存扣减
const session = db.getMongo().startSession();
try {
    session.startTransaction();
    const product = db.products.findOne(
        { _id: productId, inventory: { $gte: quantity } },
        { session }
    );
    
    if (!product) throw "库存不足";
    
    const result = db.products.updateOne(
        { _id: productId, version: product.version },
        { 
            $inc: { inventory: -quantity, version: 1 },
            $push: { sales: { date: new Date(), qty: quantity } }
        },
        { session }
    );
    
    if (result.modifiedCount === 0) throw "并发冲突";
    session.commitTransaction();
} catch (e) {
    session.abortTransaction();
    throw e;
}

2. 复杂查询性能优化

给经常查询的字段加索引是基本操作,但要注意:

// 创建复合索引示例
db.orders.createIndex({
    userId: 1,         // 第一排序条件
    createDate: -1,    // 第二排序条件
    status: 1          // 第三排序条件
});

// 查询示例:获取用户最近10个待发货订单
db.orders.find({
    userId: "user123",
    status: "pending"
}).sort({ createDate: -1 }).limit(10);

3. 数据结构变更管理

NoSQL虽然schema-less,但也要考虑变更。比如要给所有用户加积分字段:

// 批量更新文档结构
db.users.updateMany(
    { points: { $exists: false } },  // 筛选没有points字段的文档
    { $set: { points: 0 } },        // 设置默认值
    { multi: true }                  // 更新多个文档
);

三、关联技术的巧妙组合

单独使用NoSQL有时会力不从心,这时候需要组合技:

1. MongoDB + Redis缓存

// Node.js示例:缓存商品详情
async function getProduct(id) {
    const cacheKey = `product:${id}`;
    let product = await redis.get(cacheKey);
    
    if (!product) {
        product = await db.products.findOne({ _id: id });
        await redis.setex(cacheKey, 3600, JSON.stringify(product)); // 缓存1小时
    } else {
        product = JSON.parse(product);
    }
    
    return product;
}

2. MongoDB + Elasticsearch搜索

// 商品搜索实现
const { body } = await elasticsearch.search({
    index: 'products',
    body: {
        query: {
            multi_match: {
                query: '智能 防水',
                fields: ['name^3', 'description', 'tags']
            }
        }
    }
});

四、避坑指南与最佳实践

  1. 连接池配置:默认连接数往往不够
// Node.js MongoDB驱动配置
const client = new MongoClient(uri, {
    poolSize: 50,               // 连接池大小
    connectTimeoutMS: 30000,    // 连接超时
    socketTimeoutMS: 45000      // 操作超时
});
  1. 批量操作优化:避免N+1查询
// 批量插入比单条插入快10倍以上
const bulkOps = products.map(product => ({
    insertOne: { document: product }
}));

await db.products.bulkWrite(bulkOps, { ordered: false });
  1. 监控关键指标:这些指标要盯紧
  • 内存使用率(常驻集大小)
  • 页面错误率
  • 队列中的操作数
  • 复制延迟(如果用了副本集)

五、场景选择的黄金法则

  1. 适合NoSQL的场景
  • 需要灵活模式的用户画像
  • 物联网设备的海量时序数据
  • 内容管理系统的嵌套评论
  • 实时分析的热点数据
  1. 不适合的场景
  • 需要复杂事务的金融核心系统
  • 需要多表关联的报表系统
  • 需要严格一致性的库存系统

记住:没有银弹,只有合适的工具。就像你不能用螺丝刀切菜,虽然理论上也能做到。