#mongodb #date #mongodb-query #aggregation-framework #query-optimization
#mongodb #Дата #mongodb-запрос #aggregation-framework #оптимизация запроса
Вопрос:
У меня есть коллекция, которая содержит документы с вложенными массивами. В subfield
массиве содержится 1 миллион документов с тысячами объектов. Документы довольно большие, но для пояснения рассмотрим следующие два документа:
[
{
"id": "myid",
"field": {
"total": 1,
"subfield": [
{
"somefield": "1000",
"time": "2020-08-06T08:33:57.977 0530",
"val": [
{
"x": "someval",
"a": "val1",
"b": "val2"
}
]
},
{
"somefield": "2000",
"time": "2020-05-08T04:13:27.977 0530",
"val": [
{
"x": "someval2",
"a": "val1",
"b": "val2"
}
]
}
]
}
},
{
"id": "myid2",
"field": {
"total": 1,
"subfield": [
{
"somefield": "1001",
"time": "2020-07-31T10:15:50.184 0530",
"val": [
{
"x": "someval2",
"a": "val1",
"b": "val2"
},
{
"x": "someval2",
"a": "val1",
"b": "val2"
}
]
}
]
}
}
]
Пример использования:
Мне нужно спроецировать только id
документы с time
датой (сгруппированные по дате), превышающей значение, и field. subfield.val.b
или field. subfield.val.a
с определенным значением.
У меня есть запрос для достижения моего варианта использования с использованием операторов $unwind
, $toDate
$dateToString
.,,,,,.
Но использование $unwind
для больших массивов приводит к тому, что общий набор использует много памяти и замедляет работу. Теперь это занимает более 15 минут.(Я не добавил никаких индексов, потому что даже если я создаю индексы для created
, когда я запускаю explain для агрегации, выигрышный запрос не использует предоставленный индекс)
Мой текущий запрос:
db.collection.aggregate([
{
$unwind: {
path: "$field.subfield",
}
},
{
$unwind: {
path: "$field.subfield.val",
}
},
{
$addFields: {
created_at: {
$toDate: "$field.subfield.time"
}
}
},
{
$match: {
$and: [
{
$expr: {
$gt: [
{
"$dateToString": {
"date": "$created_at",
"format": "%Y-%m-%d"
}
},
"2020-04-28"
]
}
},
{
$or: [
{
"field.subfield.val.a": {
"$eq": "val1"
}
},
{
"field.subfield.val.b": {
"$eq": "val1"
}
}
]
}
]
}
},
{
$group: {
_id: "$id"
}
}
])
Мне нужно ограничить время выполнения запроса менее чем 30 секундами. Я надеюсь, что процесс можно ускорить, если обойтись без $unwind
.
Моя версия сервера MongoDB — 4.0.3
Какие другие возможные оптимизации можно выполнить?
Спасибо!
Комментарии:
1. Мое предложение состоит в том, чтобы сделать что-то вроде этого , вы можете запрашивать напрямую в элементах массива, нет необходимости разматывать массив, поэтому для
to
иfrom
вы можете сначала напрямую сопоставить / запросить. второе предложение заключается в том, почему вы не можете преобразовать эту дату"2020-04-28"
в дату iso на стороне клиента, а затем сопоставить.2. @ turivishal Спасибо за ваше предложение! У меня также есть другие запросы для запуска в той же коллекции, которые сравнивают строку даты в формате % Y:% m:% d:% H:% M:% S. Вот почему я решил преобразовать его на стороне сервера.
Ответ №1:
Вы пробовали использовать $ elemMatch? Это работает действительно похоже на функцию js Array.some()
Дополнительная информация в разделе https://docs.mongodb.com/manual/reference/operator/query/elemMatch
{
$match:{
histories:{
$elemMatch:{
created_at:{
$gt:'2020-04-28'
}
}
}
}
}
Ответ №2:
Возможные оптимизации, которые можно выполнить:
- Поскольку
$unwind
замедляет запрос,$filter
может использоваться для получения результатов сопоставления из вложенного массива. - Сравнения строк даты являются тяжелыми, поэтому лучше хранить даты как объект даты MongoDB и выполнять все сравнения с самим типом даты, а не с типом строки даты.
db.collection.aggregate([
{
"$project": {
"obj1": {
"$filter": {
"input": "$field.subfield",
"as": "el",
"cond": {
"$and": [
{
"$gt": [
"$$el.time",
new Date("2020-04-29")
]
}
]
}
}
},
id: 1,
}
},
{
$match: {
$or: [
{
"obj1.val.a": {
"$eq": "val1"
}
},
{
"obj1.val.b": {
"$eq": "val1"
}
}
]
}
},
{
$project: {
id: 1,
_id: 0
}
}
])