Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

MongoDB: $or VS $in — что работает стремительней?

Anna | 29.05.2014 | нет комментариев

По катом будет вовсе малое сопоставление продуктивности 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. Итог: не злоупотребляйте $or
P.P.S. В коде ниже видно как я замерял время. Если кто-то не в курсе, то уточню, что при вызове find() запрос не выполняется! Создается только объект MongoCursor. И только теснее при запросе первого документа идет сам запрос. Следственно отсечки времени и снимаются на первой итерации цикла приобретения документов.

P.S. Если кому-то будет увлекательно погонять тесты у себя, то вот каждый исходник:

<?php
$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));
    }
}
$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}<br />n";

}

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB