MongoDB: $or VS $in — что работает быстрее?

По катом будет совсем небольшое сравнение производительности MongoDB в случаях использования $or и $in логических операций в запросах. Надеюсь, что данная заметка сэкономит кому-нибудь рабочее время.Тесты запускались на MongoDB 2.4.9Допустим в MongoDB есть коллекция документов. Для упрощения понимания сути — пусть это будут документы именно с двумя полями. $m = new MongoClient ('mongodb://mongodb01, mongodb02, mongodba/? replicaSet=pkrs'); $mdb = $m→selectDB ('test'); $collection = $mdb→selectCollection ('test'); $collection→drop (); $collection→ensureIndex (array ('i' => 1, 'j' => 1)); for ($i = 0; $i < 100; ++$i) { for ($j = 0; $j < 100; ++$j) { $collection->insert (array ('i' => $i, 'j' => $j)); } } В коллекции будет всего 10К документов. Да, тут можно было использовать batchInsert, но не хочу усложнять понимание основной сути заметки.Необходимо регулярно (несколько раз в секунду) делать выборку до 1000 документов. Условием выборки является набор несвязанных пар i и j.Т.к. я с MongoDB начал работать меньше месяца назад, то первое, что пришло в голову — это запрос вот такого вида:

$orArray = array (); for ($i = 0; $i < 10; ++$i) { for ($j = 0; $j < 100; ++$j) { $orArray[] = array('i' => $i, 'j' => $j); } }

$query = array ('$or' => $orArray); То, что тут данные идут по порядку — это только для примера, чтобы не забивать голову бизнес логикой. В реальности, как я выше отметил, пары i и j друг с другом никак не связаны и идут в хаотичном порядке.Попробовав выполнить этот запрос у меня широко раскрылись глаза от неприятного удивления — запрос выполнялся больше 2-х секунд! Выше в коде видно, что индекс при этом создан.Это было вообще неприемлемо.Я решил убедиться, что тут не сеть тормозит, а дело именно в запросе.Для теста сделает вот такой запрос: $query = array ('i' => array ('$lt' => 10), 'j' => array ('$lt' => 100)); Результат по объему данных тот же самый, но запрос уже начинает выполняться 0.01 секунды.Стало понятно, что нужно искать обходной путь. И он был найден. По логике запроса напрашивалось использование $in вместо $or. Но я не смог найти как использовать $in сразу по парам значений. Если такой способ есть, то буду очень благодарен за подсказку.Раз я не знаю как сделать $in по двум полям, то введем искусственное поле следующим образом (слепим значения i и j через подчеркивание '_'): $collection→ensureIndex (array ('ij' => 1)); for ($i = 0; $i < 100; ++$i) { for ($j = 0; $j < 100; ++$j) { $collection->insert (array ('i' => $i, 'j' => $j, 'ij' => $i.'_'.$j)); } } И тогда запрос наш становится следующим: $inArray = array (); for ($i = 0; $i < 10; ++$i) { for ($j = 0; $j < 100; ++$j) { $inArray[] = $i.'_'.$j; } } $query = array('ij' => array ('$in' => $inArray)); И «о чудо!» мы получаем наши данные всего за 0.01 секунды (а ведь все начиналось с «больше 2-х секунд»).Погуглив немного я нашел следующее объяснение данного феномена: при запросе с конструкцией $or MongoDB якобы делает несколько запросов и потом «мёржит» результаты. Не уверен в правоте данного утверждения, но другого пока не нашел.P.S. Вывод: не злоупотребляйте $orP.P. S. В коде ниже видно как я замерял время. Если кто-то не в курсе, то уточню, что при вызове find () запрос не выполняется! Создается только объект MongoCursor. И только уже при запросе первого документа идет сам запрос. Поэтому отсечки времени и снимаются на первой итерации цикла получения документов.

P.S. Если кому-то будет интересно погонять тесты у себя, то вот весь исходник: selectDB ('test'); $collection = $mdb→selectCollection ('test'); $collection→drop (); $collection→ensureIndex (array ('i' => 1, 'j' => 1)); for ($i = 0; $i < 100; ++$i) { for ($j = 0; $j < 100; ++$j) { $collection->insert (array ('i' => $i, 'j' => $j)); } } $orArray = array (); for ($i = 0; $i < 10; ++$i) { for ($j = 0; $j < 100; ++$j) { $orArray[] = array('i' => $i, 'j' => $j); } }

$query = array ('$or' => $orArray); testQuery ('OR Query', $collection, $query);

$query = array ('i' => array ('$lt' => 10), 'j' => array ('$lt' => 100)); testQuery ('Range Query', $collection, $query);

$collection→drop (); $collection→ensureIndex (array ('ij' => 1)); for ($i = 0; $i < 100; ++$i) { for ($j = 0; $j < 100; ++$j) { $collection->insert (array ('i' => $i, 'j' => $j, 'ij' => $i.'_'.$j)); } }

$inArray = array (); for ($i = 0; $i < 10; ++$i) { for ($j = 0; $j < 100; ++$j) { $inArray[] = $i.'_'.$j; } } $query = array('ij' => array ('$in' => $inArray)); testQuery ('IN Query', $collection, $query);

function testQuery ($testName, $collection, $query) { $cursor = $collection→find ($query); $cursor→batchSize (1000); $start = microtime (true); $first = true; foreach ($cursor as $doc) { if ($first) { $time1 = microtime (true); $first = false; } } $time2 = microtime (true); $resultFirst = $time1 — $start; $resultOther = $time2 — $time1; echo »{$testName} — First: {$resultFirst} Other: {$resultOther}
\n»;

}

© Habrahabr.ru