MongoDB是否重复存储数组元素键?海量关联数据存储方案咨询
关于MongoDB数组存储与数据库选型的疑问
我计划在MongoDB中创建名为users的集合,文档结构如下:
{ "name": "Example", "age": 100, "data": [ { "key1": value1, "key2": value2, "key3": value3 }, { "key1": value1, "key2": value2, "key3": value3 }, { "key1": value1, "key2": value2, "key3": value3 } ] }
其中data数组包含数万个结构完全相同的对象,所有对象的键一致。我想确认MongoDB是否会为数组中的每个元素重复存储这些键字符串,由于值是小数字,重复键可能比值占用更多存储空间。
另外,我不确定该选择Schemaful(有模式)还是Schemaless(无模式)数据库:data元素每秒新增一个,按顺序写入;访问时总是需要获取某用户的全部data条目。若用Schemaful数据库,需建立关联userId的大表存储data条目,但逐个写入再批量访问的方式感觉不合理。想了解是否有在MongoDB文档内实现无关联类Schemaful表的方法,或适合分组存储海量数据的Schemaful数据库,希望得到相关建议。
一、MongoDB数组键的存储问题
MongoDB采用BSON格式存储数据,默认情况下,数组中每个子文档的键会重复存储。比如你示例里的key1、key2、key3,每个数组元素都会单独保存这些键的字符串,当数组元素达到数万个时,这些重复键确实会占用大量额外空间——尤其是值都是小数字的情况下,键的存储开销会远超值本身。
不过你可以通过两种方式优化这个问题:
- 改用数组嵌套数组的结构:把每个子文档转化为固定顺序的数组,比如把
{"key1":v1,"key2":v2,"key3":v3}改成[v1, v2, v3],然后在应用层维护键与数组索引的映射(比如约定索引0对应key1,索引1对应key2)。这种方式完全消除了键的重复存储,空间利用率最高,但需要应用层处理字段映射,灵活性稍差。 - 启用MongoDB的压缩功能:MongoDB支持多种压缩算法(如Snappy、Zstandard),开启后会自动对重复的键字符串进行压缩,能大幅减少重复键的存储开销。这种方式不需要修改数据结构,对应用层透明,但压缩效果取决于数据重复度,相比第一种方式可能还是有少量额外开销。
二、数据库选型与存储方案建议
1. MongoDB内的优化方案
你的场景是按用户分组批量写入、批量读取全部数据,完全可以在MongoDB内优化存储,不需要切换到Schemaful数据库:
- 避免无限膨胀的单文档:MongoDB单文档最大限制是16MB,如果每秒写入1条,按每条3个小数字计算,大概几个月就会达到这个上限。所以不要把所有
data都存在同一个用户文档里,而是采用按时间分片的文档存储:比如每个用户按天/小时创建一个文档,文档里包含该时间段的data数组,字段为userId、timeRange、data。这样既保持了分组存储的特性,又避免单文档过大,写入时只需要找到对应时间段的文档追加data,读取时按userId批量查询所有时间段的文档再合并数据。 - 使用预定义的Schema验证:MongoDB从3.6开始支持文档级的Schema验证,你可以给集合设置严格的Schema,强制
data数组元素的结构一致,实现类Schemaful的效果。比如通过以下命令创建带验证规则的集合:
这样既利用了MongoDB的灵活写入特性,又保证了数据结构的一致性,符合你需要的“无关联类Schemaful表”的需求。db.createCollection("user_data", { validator: { $jsonSchema: { bsonType: "object", required: ["userId", "timeRange", "data"], properties: { userId: { bsonType: "objectId" }, timeRange: { bsonType: "object", required: ["start", "end"], properties: { start: { bsonType: "date" }, end: { bsonType: "date" } } }, data: { bsonType: "array", items: { bsonType: "object", required: ["key1", "key2", "key3"], properties: { key1: { bsonType: "int" }, key2: { bsonType: "int" }, key3: { bsonType: "int" } } } } } } } })
2. 适合的Schemaful数据库选型
如果一定要用Schemaful数据库,以下几种方案更适合你的分组批量读写场景:
- TimescaleDB:基于PostgreSQL的时序数据库,专门处理时间序列数据。你可以创建带
userId和时间戳的表,用**hypertables(超表)**自动按时间分片,写入时单条插入,读取时按userId批量查询,TimescaleDB会自动合并分片数据,性能比普通PostgreSQL好很多,适合每秒一条的时序写入场景。 - InfluxDB:时序数据库,天生适合按标签(比如
userId)分组存储时间序列数据。写入时按userId作为标签,每条数据包含时间戳和三个字段,读取时直接按userId查询所有数据,不需要关联操作,写入和批量读取的性能都很出色。 - ClickHouse:列式数据库,适合海量数据的批量查询。你可以创建包含
userId、timestamp、key1、key2、key3的表,按userId和timestamp分区,写入时可以攒几秒批量插入(适配每秒一条的频率),读取时按userId查询,列式存储的特性会让批量读取的速度非常快。
内容的提问来源于stack exchange,提问作者Palm
相关产品推荐
相关产品推荐

