超过 500 万条记录的 MongoDB 查询性能
Posted
技术标签:
【中文标题】超过 500 万条记录的 MongoDB 查询性能【英文标题】:MongoDB querying performance for over 5 million records 【发布时间】:2013-11-02 18:36:13 【问题描述】:我们最近为我们的一个主要系列创造了超过 200 万个记录,现在我们开始为该系列的主要性能问题而苦恼。
集合中的文档有大约 8 个字段,您可以使用 UI 过滤这些字段,并且结果应该按处理记录的时间戳字段排序。
我添加了几个带有过滤字段和时间戳的复合索引 例如:
db.events.ensureIndex(somefield: 1, timestamp:-1)
我还添加了几个索引,以便一次使用多个过滤器,以期获得更好的性能。但是有些过滤器仍然需要很长时间才能执行。
我已确保使用说明查询确实使用了我创建的索引,但性能仍然不够好。
我想知道分片是不是现在要走的路.. 但我们很快就会开始在该集合中每天有大约 100 万条新记录.. 所以我不确定它是否会很好地扩展..
编辑:查询示例:
> db.audit.find('userAgent.deviceType': 'MOBILE', 'user.userName': $in: ['nickey@acme.com']).sort(timestamp: -1).limit(25).explain()
"cursor" : "BtreeCursor user.userName_1_timestamp_-1",
"isMultiKey" : false,
"n" : 0,
"nscannedObjects" : 30060,
"nscanned" : 30060,
"nscannedObjectsAllPlans" : 120241,
"nscannedAllPlans" : 120241,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 26495,
"indexBounds" :
"user.userName" : [
[
"nickey@acme.com",
"nickey@acme.com"
]
],
"timestamp" : [
[
"$maxElement" : 1
,
"$minElement" : 1
]
]
,
"server" : "yarin:27017"
请注意 deviceType 在我的集合中只有 2 个值。
【问题讨论】:
你在使用limit
参数吗?
不错的一个!将来我可能会遇到类似的情况,答案也可能对我有帮助。你的数据库有多大的集合?在达到 200 万之前查询这 8 个 KV 对需要多长时间?现在需要多长时间? (只是好奇)
乔,是的,当然我正在使用限制,目前我将结果限制为 25 个文档。我什至不想谈论跳过,因为我将在不久的将来用范围查询替换它们。
Enver,当收集大约 1-2 百万条记录时,我开始感觉到一些性能问题(5-50 秒的查询时间)。然后我添加了索引,我得到了合理的查询性能
哪些查询很慢?没有过滤的简单查询已经很慢了吗?还是只有一个字段过滤的查询很慢?还是按两个字段?
【参考方案1】:
Mongo 每个查询只使用 1 个索引。 因此,如果您想过滤 2 个字段,mongo 将使用其中一个字段的索引,但仍需要扫描整个子集。
这意味着基本上您需要为每种类型的查询创建一个索引才能获得最佳性能。
根据您的数据,每个字段有一个查询并在您的应用程序中处理结果可能不是一个坏主意。 这样,您只需要每个字段的索引,但可能需要处理的数据太多。
【讨论】:
【参考方案2】:这是大海捞针。对于那些表现不佳的查询,我们需要一些explain()
的输出。不幸的是,即使这样也只能解决该特定查询的问题,所以这里有一个关于如何解决这个问题的策略:
-
确保不是因为 RAM 不足和分页过多
启用数据库探查器(使用
db.setProfilingLevel(1, timeout)
,其中timeout
是查询或命令所用毫秒数的阈值,任何较慢的都将被记录)
检查db.system.profile
中的慢查询并使用explain()
手动运行查询
尝试识别explain()
输出中的慢操作,如scanAndOrder
或大nscanned
等。
关于查询的选择性以及是否可以使用索引改进查询的原因。如果不是,请考虑禁止最终用户的过滤器设置,或者给他一个操作可能很慢的警告对话框。
一个关键问题是您显然允许您的用户随意组合过滤器。如果没有索引交叉,这将大大增加所需索引的数量。
此外,在每个可能的查询中盲目地抛出索引是一种非常糟糕的策略。结构化查询并确保索引字段具有足够的选择性非常重要。
假设您要查询所有具有status
“活动”和其他条件的用户。但是在这 500 万用户中,有 300 万是活跃的,200 万是不活跃的,所以超过 500 万的条目只有两个不同的值。这样的索引通常没有帮助。最好先搜索其他条件,然后扫描结果。平均而言,当返回 100 个文档时,您必须扫描 167 个文档,这不会对性能造成太大影响。但这并不是那么简单。如果主要标准是用户的joined_at
日期,并且用户随着时间的推移而停止使用的可能性很高,那么您最终可能需要扫描数千个文档才能找到一百个匹配项。 p>
因此优化很大程度上取决于数据(不仅是其结构,还包括数据本身)、其内部相关性和您的查询模式。
当数据对于 RAM 来说太大时,情况会变得更糟,因为这样的话,有一个索引是很棒的,但是扫描(甚至只是简单地返回)结果可能需要从磁盘随机获取大量数据,这需要很多时间时间。
控制这种情况的最佳方法是限制不同查询类型的数量,禁止对低选择性信息的查询,并尽量防止对旧数据的随机访问。
如果所有其他方法都失败了,并且如果您确实需要过滤器具有如此大的灵活性,那么可能值得考虑一个支持索引交叉的单独搜索数据库,从那里获取 mongo id,然后使用 $in
从 mongo 获取结果.但这也有其自身的危险。
-- 编辑--
您发布的说明是扫描低选择性字段问题的一个很好的例子。显然,“nickey@acme.com”有很多文件。现在,查找这些文档并按时间戳降序排序非常快,因为它受到高选择性索引的支持。不幸的是,由于只有两种设备类型,mongo 需要扫描 30060 个文档才能找到第一个匹配 'mobile' 的。
我假设这是某种网络跟踪,而用户的使用模式使查询变慢(如果他每天切换移动和网络,查询会很快)。
可以使用包含设备类型的复合索引来加快此特定查询的速度,例如使用
a) ensureIndex('username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1)
或
b) ensureIndex('userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1)
不幸的是,这意味着像 find("username" : "foo").sort("timestamp" : -1);
can't use the same index anymore 这样的查询,因此,如上所述,索引的数量会增长得非常快。
目前使用 mongodb 恐怕没有很好的解决方案。
【讨论】:
感谢您的回复!我们遇到的另一个问题是,在我们的 mongo 上有几个客户端数据库,每个数据库都有大量的集合。我们担心为所有这些集合编制索引会严重影响性能,因为我们需要拥有大量 RAM 才能支持来自不同用户的同时查询。您对此有什么好的搜索数据库建议吗? 我想这取决于您需要的搜索功能。对于基础知识,任何支持索引交集的数据库都应该这样做。如果您需要全文搜索、分面搜索甚至切片和切块,事情会变得很棘手,并且有从 SolR、Elastic Search 到 OLAP 多维数据集的一整套工具。当您使用它时,您还可以为 MongoDB Jira 中的索引交集投票:jira.mongodb.org/browse/SERVER-3071 我想我们会为这个特定的表使用 ElasticSearch。你怎么看? 很好的答案。我很想知道过去 4.5 年在这方面发生了什么变化。 我很想知道过去 8 年在这方面发生了什么变化。【参考方案3】:如果你使用 $in,mongodb 永远不会使用 INDEX。通过删除此 $in 来更改您的查询。它应该使用索引,它会提供比你之前得到的更好的性能。
http://docs.mongodb.org/manual/core/query-optimization/
【讨论】:
仅供参考, $in 确实使用索引,它是 $nin 不使用索引。根据我们的经验,$in 中的问题是 mongo 对 $in 中的每个值执行查询。尽管对每个查询都使用了索引,但速度非常慢..以上是关于超过 500 万条记录的 MongoDB 查询性能的主要内容,如果未能解决你的问题,请参考以下文章
数据库缓慢检索/更新/插入问题,每个表中有超过 500 万条记录
性能 - 使用 Spring JPA Data 搜索具有 2000 万条记录的表
如何以超过 15 秒的速度对超过 300 万条记录的表进行此查询?
如果在单个请求中与 Oracle 和 PostgreSql 交互以处理超过 20 万条记录,如何提高 EF Core 性能