我需要了解如何使用mongo来表现更好。我有两个使用mongo的项目,其中一个有1.4亿行,每个查询都在附近运行,数据以小块显示,所以有几个索引mongo能够过滤99%的数据并快速返回选定的数据。 Mongo在这类项目上运作良好。
另一方面,我有另一个项目,如谷歌分析跟踪访问,点击等。目标是根据某些标准(使用表单)计算时间范围内的点击次数。我在同一个任务中挑战mysql。
我逐行使用传统的数据模式,如:
{
'user':'abc',
'date':'2015-07-20',
'hour':02,
[....]
'clicks':30
}
拥有200多万行(即使按照你所看到的每小时点击次数),每个字段都有索引,而且查询最多的组有一些复合索引。试图通过某些$ match来进行agregate和$ sum点击真是非常慢,如果产生的行数足够大,甚至更糟的是总行数,索引会占用服务器中32gb的ram。
使用mongo的架构优势,设计了一个分组架构以尽可能减少重复数据,这是一种架构,其中每种类型的点击属性由唯一的字段组合(具有唯一索引)确定,然后单击分组在按日期分配的树上,行示例:
{
"user" : "asd",
[....]
"date" : {
"total" : 5,
"years" : {
"2015" : {
"total" : 5,
"months" : {
"06" : {
"total" : 5,
"days" : {
"30" : {
"total" : 2,
"hours" : {
"16" : 1,
"22" : 1
}
},
"28" : {
"total" : 1,
"hours" : {
"6" : 1
}
},
"29" : {
"total" : 2,
"hours" : {
"14" : 1,
"20" : 1
}
}
}
}
}
}
}
}
}
感谢这个策略,2亿多行减少了10倍并且索引适合内存然后,插入速度减慢因为在插入新的“行”之前你必须检查是否有一个如果之前存在,则找到相同的特征并合并应用于日期数组的点击。
当我需要对行进行计数时,速度已经针对传统模式进行了验证,但我需要做一些模糊的聚合事件来计算数据:
[ '$总和'=>'。个月。'。天。 '总' '$ date.years' $每年$每月$日]
这是执行一般下来的mysql的平均速度有点下降,但差异是如此紧张,即使在某些条件下mysql赢得了太多的战斗,考虑到mysql计数2亿行和mongo 2000万,这是不可接受的因为很多时候mysql在16s内进行查询,而mongo在120s内解析它。我想击败mysql(myIsam)使用mongo作为替换。我已经尝试了很多东西,从日期树上的稀疏索引到二级缓存,保存了一些预处理结果并混合它们。它无法在某一天缓存所有数据排列,因为[...]字段很多。碎片可能是一个解决方案但我不认为会神奇地提高速度2。
请给我一些提示
让某些国家搜索某个国家:
Mongodb计数country ='AD'的行:11389
骨料:
Array
(
[0] => Array
(
[$match] => Array
(
[country] => AD
)
)
[1] => Array
(
[$group] => Array
(
[_id] => Array
(
[country] => $country
)
[2015-07-01] => Array
(
[$sum] => $date.years.2015.months.07.days.01.total
)
[2015-07-02] => Array
(
[$sum] => $date.years.2015.months.07.days.02.total
)
[2015-07-03] => Array
(
[$sum] => $date.years.2015.months.07.days.03.total
)
[2015-07-04] => Array
(
[$sum] => $date.years.2015.months.07.days.04.total
)
[2015-07-05] => Array
(
[$sum] => $date.years.2015.months.07.days.05.total
)
[2015-07-06] => Array
(
[$sum] => $date.years.2015.months.07.days.06.total
)
[2015-07-07] => Array
(
[$sum] => $date.years.2015.months.07.days.07.total
)
[2015-07-08] => Array
(
[$sum] => $date.years.2015.months.07.days.08.total
)
[2015-07-09] => Array
(
[$sum] => $date.years.2015.months.07.days.09.total
)
[2015-07-10] => Array
(
[$sum] => $date.years.2015.months.07.days.10.total
)
[2015-07-11] => Array
(
[$sum] => $date.years.2015.months.07.days.11.total
)
[2015-07-12] => Array
(
[$sum] => $date.years.2015.months.07.days.12.total
)
)
)
[2] => Array
(
[$project] => Array
(
[_id] => $_id
[dates] => Array
(
[2015-07-01] => $2015-07-01
[2015-07-02] => $2015-07-02
[2015-07-03] => $2015-07-03
[2015-07-04] => $2015-07-04
[2015-07-05] => $2015-07-05
[2015-07-06] => $2015-07-06
[2015-07-07] => $2015-07-07
[2015-07-08] => $2015-07-08
[2015-07-09] => $2015-07-09
[2015-07-10] => $2015-07-10
[2015-07-11] => $2015-07-11
[2015-07-12] => $2015-07-12
)
)
)
)
结果:
Array
(
[data] => Array
(
[AD] => Array
(
[_id] => Array
(
[country] => AD
)
[dates] => Array
(
[2015-07-01] => 6080
[2015-07-02] => 6580
[2015-07-03] => 6178
[2015-07-04] => 6084
[2015-07-05] => 7085
[2015-07-06] => 7192
[2015-07-07] => 5672
[2015-07-08] => 6769
[2015-07-09] => 6370
[2015-07-10] => 6035
[2015-07-11] => 5513
[2015-07-12] => 6941
)
)
)
[time] => 17.0764780045
)
Mysql计数行:38515
Mysql查询:
SELECT date,sum(clicks) as clicks FROM table WHERE ( country = "AD" AND ( date > 20150700 AND date < 20150712 ) ) GROUP BY country,date;
结果:
Array
(
[0] => Array
(
[date] => 20150701
[clicks] => 6080
)
[1] => Array
(
[date] => 20150702
[clicks] => 6580
)
[2] => Array
(
[date] => 20150703
[clicks] => 6178
)
[3] => Array
(
[date] => 20150704
[clicks] => 6084
)
[4] => Array
(
[date] => 20150705
[clicks] => 7085
)
[5] => Array
(
[date] => 20150706
[clicks] => 7192
)
[6] => Array
(
[date] => 20150707
[clicks] => 5672
)
[7] => Array
(
[date] => 20150708
[clicks] => 6769
)
[8] => Array
(
[date] => 20150709
[clicks] => 6370
)
[9] => Array
(
[date] => 20150710
[clicks] => 6035
)
[10] => Array
(
[date] => 20150711
[clicks] => 5513
)
)
time: 0.25689506530762
项目数:
骨料:
Array
(
[0] => Array
(
[$match] => Array
(
[country] => AD
[date] => Array
(
[$in] => Array
(
[0] => 20150701
[1] => 20150702
[2] => 20150703
[3] => 20150704
[4] => 20150705
[5] => 20150706
[6] => 20150707
[7] => 20150708
[8] => 20150709
[9] => 20150710
[10] => 20150711
[11] => 20150712
)
)
)
)
[1] => Array
(
[$group] => Array
(
[_id] => Array
(
[country] => $country
)
[count] => Array
(
[$sum] => $clicks
)
)
)
)
结果:
Array
(
[result] => Array
(
[0] => Array
(
[_id] => Array
(
[country] => AD
)
[clicks] => 76499
)
)
[ok] => 1
)
time: 27.8900089264
答案 0 :(得分:1)
我没有回答,因为我确信一些MongoDB专家会回答。然而,由于没有人给出答案,我会给出一些提示。也许某些东西可以帮助。但话又说回来 - 我不是MongoDB专家。拿一小块盐就可以了。
1)您使用的是哪个版本?如果你仍然在2.6 - 使用WiredTiger引擎试用3.0.x(或更新版本) 2)如果你有很多数据分片可以大大帮助。这会增加设置的复杂性,但是由于您将能够以并行方式处理部分数据集,因此可以获得显着的速度提升。但是要小心选择合适的分片键 3)考虑创建几个可以作为较小视图的集合。示例:如果[...]目前有15个字段,很多查询很可能只使用1或2。像国家一样。再创建一个集合,在其中使用国家/地区数据并跳过休息。如果查询仅使用国家/地区字段而不使用其他15个字段,则使用小集合。如果查询使用更多字段,请使用大字段。这样,对国家/地区的查询会更快,因为您可以更多地对数据进行分组。然而,并非总是如此,因为它在构建这样的小集合时增加了额外的复杂性。如果您在某个队列中处理数据(以大插入),您也可以插入小。或者你可以使用一些聚合查询和$ out来每隔X分钟构建一次更小的表 4)想出第3个架构。您的第二个架构很容易放入数据,但很难获取数据。您可以更多地使用数组。这样,获取数据将更加困难,但查询数据会更容易,更快捷。请记住,在您的第二个架构和我的第三个架构文档的示例中,正在增长,并且可能需要MongoDB在磁盘上移动它们,这实际上是非常慢的操作。测试是否会影响您的设置。潜在收集模式的一个小例子:
{
"user": "asd",
[...],
"date": ISODate("2015-07-01T00:00:00Z"), // first date of the month
"total": 2222,
"daily": [
{"date": ISODate("2015-07-01T00:00:00Z"), "total": 22},
{"date": ISODate("2015-07-11T00:00:00Z"), "total": 200},
{"date": ISODate("2015-07-20T00:00:00Z"), "total": 2000},
]
}
插入数据时,您可以使用带标准的更新(如果您使用的是PHP):$criteria = ["user": "asd", "daily.date": new MongoDate("...."), // other fields]
和更新子句$update = ['$inc': ["total: 1, 'daily.$.total': 1]]
。检查已更新的行数。如果为0,则从相同数据创建插入。即取消设置$criteria['daily.date']
并将更新更改为$update = ['$inc' => ['total' => 1], '$push' => ['daily' => ['date' => new MonoDate('..'), 'total': 1]]]
。请记住,如果您有多个插入数据的脚本,则可能会遇到问题。最好一个一个地排队。或者你并行确保$ push不会导致添加几个具有相同日期的daily.date。所以 - 你尝试更新,如果不能更新,插入。当您使用数组和可能的运算符时,您不能使用upsert。这就是为什么需要额外插入的原因。正如我所说,获取数据更复杂。但是更容易获取数据。确保设置正确的索引。例如在'daily.date'等上。因此更新查询不需要检查大量文档。甚至更多 - 您可以创建一些哈希字段来放置[...]字段,这些字段将保存所有字段的哈希值。并在更新中使用它。这样就可以更容易地创建小索引以精确定位特定文档(您放入索引'daily.date',哈希字段等等,但不需要放置15 [..]字段)。
当你有这样的结构时,你可以用查询做很多事情。例如 - 如果您需要整整几个月,只需查询您需要的日期和字段,总计就可以了。如果您需要一些日期范围(如1月1日至10日),您可以通过[...]字段和日期进行查询,项目可以删除不必要的字段,$每天放松,再次匹配,但这次是在daily.date字段,然后投影重命名字段,然后分组和求和。它比使用$ date.years.2015.months.07.days.03.total更灵活。
请记住,所有这些只是提示。自己测试一切。也许有1到5个提示可行。但这可以产生重大影响。