mongodb 中的"坑"系列 --- 单个文档16M大小限制

本文探讨了MongoDB中单个文档不能超过16M的限制,指出这种限制可能导致隐形错误,尤其是在无返回码的插入操作中。通过启用安全验证可以及时发现此类错误,但这会牺牲性能。建议的解决方案包括调整数据库结构以避免单文档过大,或使用GridFS存储大数据量信息。

   习惯了使用mongodb中文档(document)存储方式, 可以灵活的将大量数据存入一个集合中的一条文档中, 这样可以减少大量的数据冗余, 不会出现关系性数据库, 如myslq中表的某一列的数据冗余. 不过这样存储虽好, 但其实也会存在一定的问题, 也就是mongodb中的大小限制, 即单个文档大小不能超过16M. 


    对遇到过这个问题的人来说, 这个16M的'概念'很好理解, 而对于还未意识到这个问题的人来说, 这个'坑' 可能会让你花时间都难以发现, 因为这又要牵扯到mongodb的另一个存储机制 ---- 无返回码. 在 < mongodb 权威指南> 一书中, 作者称之为离弦之箭. 什么意思呢, 就是mongodb的插入,删除等操作, 客户端向数据库发出请求之后, 是不需要等待数据库返回操作是否成功的返回结果. 这也是mongodb插入,更新等操作速度快的原因. 这就导致, 当单个文件超过16M之后, 程序并不会报错, 但此时, 数据已经无法插入数据库了.

上代码说明问题:

<span style="font-size:18px;">__author__ = 'ray'

import pymongo
import codecs

def mongo_test():

    fin = codecs.open('test_data.txt', 'r', encoding='UTF-8')   # test_data是一个测试文件, 文件大小为1.1M
    count = 0  
    test.insert({'name': 'test', 'max': 0})   
    line = fin.readline()
    try:
        while count < 20:
            test.update({'name': 'test'}, {'$set': {str(count): line}})   # 循环中每次插入1.1M的数据
            test.update({'name': 'test'}, {'$inc': {'max': 1}}, True)    
            count += 1
    except Exception as e:
        print count, e
    fin.close()


if __name__ == '__main__':
    client = pymongo.Connection('localhost', 27017)
    conn = client.db_wallpaper
    test = conn.test
    mongo_test()</span>
程序输出结果为: 
/usr/bin/python2.7 /home/ray/test/mongo_test.py
Process finished with exit code 0

程序并无异常, 此时查看数据库结果如下:


其实只插入了14条记录, 这种隐形的错误很难被发现. 


有一种策略可以及时发现这个错误, 那就是使用安全验证, 代码很简单, 把

client = pymongo.Connection('localhost', 27017)  ==>> client = pymongo.Connection('localhost', 27017,safe=True)

这样就会有安全验证, 每一次插入会等待上一次的返回结果, 后果就是牺牲mongodb的操作性能. 更新后的结果如下:


而在数据库中, max的值显示为15, 也就是后续的操作就终止了. 


===========================分割线==================================

 很多使用了mongodb的程序员会对此表示不满, 认为这样的设计非常不方便, 因为我们早已经进入了用G, T来衡量数据的时代, 这16M能干什么.  不过按照mongodb的设计思路来看, 这样的限制其实是有助于我们更改不良的数据库结构设计, 因为笔者在shell中做查询时, 面对16M的文档, 查询起来相当慢, 这样的速度是不能够忍受的. 

解决办法:

1: 更改数据表结构, 避免单个文档超过16M, 将数据分开存储, 反正mongodb也比较适合用冗余换取效率. 不妨在同一个集合中多建立几个文档, 分开存储信息

2: 使用GridFS存储大数据量信息, 具体怎么做, 下次再说.


如有问题, 欢迎讨论指正.

<think>由于MongoDBBSON文档大小限制16MB,当文档大小超过这个限制时,MongoDB不会自动管理,而是会抛出异常(通常是BSONObjectTooLarge异常)。因此,需要程序控制以避免产生过大的文档。 解决方案: 1. 设计数据模型时,避免单个文档超过16MB。可以通过以下方式: - 使用引用(References)代替内嵌(Embedding):将可能增长的部分存储在单独的集合中,并通过引用(如ObjectId)关联。 - 使用GridFS:对于大文件(如图片、视频、大型文本等),MongoDB提供了GridFS规范,它将大文件分割成多个小的文档(默认每个chunk为255KB)进行存储。 2. 在程序逻辑中,检查文档大小。在插入或更新文档之前,可以估算文档大小(例如,将文档序列化为BSON并检查其大小)。如果超过16MB,则采取上述措施。 基于C++和Qt5的解读: 在C++中,我们可以使用MongoDB C++驱动程序。在Qt5中,由于Qt有自己的数据类型(如QString,QJson等),我们需要将Qt的数据类型转换为BSON支持的类型(如std::string等)。 以下是一个示例,展示如何在插入文档前检查文档大小: 步骤: a. 构建一个BSON文档(使用Qt的数据结构,然后转换为BSON)。 b. 计算BSON文档大小。 c. 如果超过16MB(16*1024*1024字节),则采取相应措施(如使用GridFS或拆分文档)。 注意:MongoDB C++驱动程序使用bsoncxx库来构建和操作BSON文档。 示例代码(使用Qt5和MongoDB C++驱动程序):</think>### MongoDB文档大小超过16MB的处理机制 在MongoDB中,单个文档大小限制是**16MB**(BSON格式)。当文档超过此大小时,MongoDB不会自动管理,而是会**抛出异常**。需要程序主动控制以避免数据写入失败。 #### 核心机制: 1. **写入时检查**:在插入或更新文档时,MongoDB驱动会验证文档大小。 2. **异常类型**:操作会触发`BSONObjectTooLarge`异常(错误代码10334)。 3. **无自动分片**:MongoDB不会自动拆分大文档,需要开发者设计解决方案。 --- ### C++/Qt5 解决方案 以下是使用C++(MongoDB C++驱动)和Qt5处理大文档的完整方案: #### 1. 检查文档大小(预防性措施) ```cpp #include <bsoncxx/builder/stream/document.hpp> #include <bsoncxx/json.hpp> #include <mongocxx/client.hpp> // Qt5辅助函数:计算QJsonDocument的BSON大小 size_t calculateBsonSize(const QJsonDocument& doc) { // 将QJsonDocument转为BSON auto bson = bsoncxx::from_json(doc.toJson().toStdString()); return bson.length(); // 返回BSON字节数 } // 检查文档是否超过16MB bool isDocumentValid(const QJsonDocument& doc) { const size_t MAX_SIZE = 16 * 1024 * 1024; // 16MB return calculateBsonSize(doc) <= MAX_SIZE; } ``` #### 2. 处理大文档的两种策略 **策略1: 文档拆分(推荐)** ```cpp // 拆分大文档为子文档数组 std::vector<QJsonDocument> splitLargeDocument(const QJsonDocument& doc) { std::vector<QJsonDocument> chunks; QJsonObject root = doc.object(); // 示例:按属性拆分(实际需根据业务逻辑) QJsonObject chunk1; chunk1["_id"] = root["_id"]; chunk1["part1"] = root["largeArray1"]; QJsonObject chunk2; chunk2["_id"] = root["_id"]; chunk2["part2"] = root["largeArray2"]; chunks.push_back(QJsonDocument(chunk1)); chunks.push_back(QJsonDocument(chunk2)); return chunks; } // 插入拆分后的文档 void insertChunks(mongocxx::collection& coll, const std::vector<QJsonDocument>& chunks) { for (const auto& chunk : chunks) { auto bson = bsoncxx::from_json(chunk.toJson().toStdString()); coll.insert_one(bson.view()); } } ``` **策略2: 使用GridFS(适用于文件/二进制数据)** ```cpp #include <mongocxx/gridfs/bucket.hpp> void storeWithGridFS(mongocxx::database& db, const QByteArray& data) { auto bucket = db.gridfs_bucket(); // 创建上传流 auto uploader = bucket.open_upload_stream("large_file"); uploader.write(reinterpret_cast<const uint8_t*>(data.constData()), data.size()); uploader.close(); // 实际写入GridFS qDebug() << "Stored with GridFS. File ID:" << uploader.id().value.get_oid().value.to_string().c_str(); } ``` #### 3. 完整写入流程(含异常处理) ```cpp void safeInsertDocument(mongocxx::collection& coll, const QJsonDocument& doc) { try { if (isDocumentValid(doc)) { auto bson = bsoncxx::from_json(doc.toJson().toStdString()); coll.insert_one(bson.view()); } else { // 策略选择:此处使用拆分方案 auto chunks = splitLargeDocument(doc); insertChunks(coll, chunks); } } catch (const mongocxx::exception& e) { qCritical() << "MongoDB Error:" << e.what(); // 特殊处理BSON过大异常(错误码10334) if (strstr(e.what(), "10334")) { qWarning() << "Using GridFS fallback..."; storeWithGridFS(coll.database(), doc.toJson().toUtf8()); } } } ``` --- ### 关键解释 1. **文档大小计算** `bsoncxx::from_json()`将JSON转为BSON后,通过`.length()`获取精确字节数,确保与MongoDB服务端校验一致。 2. **拆分策略优势** - 保持数据逻辑关联性(通过公共`_id`关联子文档- 避免GridFS的额外开销(适合结构化数据) 3. **GridFS适用场景** - 二进制文件(图片/视频) - 非结构化大数据(如日志文本) - 需要流式读写的场景 4. **Qt5集成要点** - 使用`QJsonDocument`作为数据载体 - `toJson().toStdString()`实现Qt到BSON的转换 - 异常处理兼容Qt的日志系统(`qCritical()`) --- ### 最佳实践建议 1. **设计阶段预防** - 避免在单个文档中存储无限增长的数组 - 使用`{ type: "reference", id: ObjectId(...) }`关联数据 2. **监控与告警** ```cpp // 监控文档增长趋势 void checkCollectionGrowth(mongocxx::collection& coll) { auto stats = coll.stats(); double avgDocSize = stats.avg_document_size(); if (avgDocSize > 10 * 1024 * 1024) { // 超过10MB预警 emit logWarning("Collection approaching 16MB limit"); } } ``` 3. **索引优化** 拆分文档后,确保子文档通过`_id`有效索引: ```cpp coll.create_index(bsoncxx::builder::stream::document{} << "parentId" << 1 << bsoncxx::builder::stream::finalize); ``` ---
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值