Пара фокусов с xPDO

В ходе одного исследования выяснил несколько интересных вещей про xPDO, о которых раньше не задумывался или некогда было проверить.

Решил немедленно поделиться.

Выборка 1000 ресурсов


$q = $modx->newQuery('modResource', array('id:>' => 0));
$q->limit(1000);

$q->prepare();
$q->stmt->execute();
$res = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($res as $v) {
	//echo $v['modResource_pagetitle'];
}
Этот код работает за 0.042197227478 и занимает 33.3 Mb памяти.

$q = $modx->newQuery('modResource', array('id:>' => 0));
$q->limit(1000);

$res = $modx->getCollection('modResource', $q);
foreach ($res as $v) {
	//echo $v->get('pagetitle');
}
А этот уже за 2.15289998055 и занимает 78.3 Mb памяти.

В чем же разница?

Внимательный зритель уже догадался в чем трюк — в первом примере мы достаем не объекты, а чистые массивы данных, отсюда скорость и экономия памяти.

Если нужно просто вывести содержимое объектов на экран — то это способ гораздо быстрее.
Обратите внимание, что к полям приписывается имя класса — modResource_ в данном случае. Это можно изменить, используя $q->select('pagetitle');

А если нужно использовать $resource->get(), $resource->set(), $resource->getTVValue() и другие методы объектов — тут второй запрос, помедленнее.

Выборка 100 ресурсов


Те же 2 выборки, но с $q->limit(100):
Без объектов:
Память: 17.6 Mb
Время: 0.00191211700439

С объектами:
Память: 23.6 Mb
Время: 0.216797113419

Тут отличие уже не так значительно. Особенно учитывая, что в реальных выводах страниц используется пагинация, и разбивка результатов на 10 — 15 ресурсов за раз.

Выборка 1 ресурса


Те же 2 выборки, но с $q->limit(1); показывают закономерную картину:
Без объектов:
Память: 17.6 Mb
Время: 0.00191211700439

С объектами:
Память: 17.8 Mb
Время: 0.00573897361755

На малой выборке разницы особой нет.

Выводы


Тут все просто.

Когда вам нужна экономия памяти и высокая производительность для выгрузки товаров, например, или генерации карты солидного сайта — используйте первый метод.

Если же нужны удобства, или размер выборки небольшой — можно не заморачиваться и делать как обычно.

На закуску


А знаете ли вы, что можно составлять запросы вот так:
$sql = "SELECT * FROM {$modx->getTableName('modResource')} WHERE `id` > 0 LIMIT 1000";

$q = new xPDOCriteria($modx, $sql);

$res = $modx->getCollection('modResource', $q);
foreach ($res as $v) {
	//echo $v->get('pagetitle');
}
Это тот же второй вариант, с объектами, но SQL запрос составлен в ручную. С одной стороны — вы можете делать так любые выборки, с другой — таккой запрос не универсален, и на Microsoft SQL это работать не будет.
А при использовании $modx->newQuery() — будет, он позаботся о правильном составлении запроса согласно модели.
Плюс, newQuery еще дополнительно приведет значения к типам, указанным в модели. То есть, если у вас в модели id указан как int(10), а вы пихаете туда строку — newQuery превратит ее в 0. То есть, приведет к типу int.

Как видно, написание запроса вручную нисколько не экономит время. Но позволяет делать сложные выборки.
Память: 77.8 Mb
Время: 2.14725112915

Ну и самый легкий вариант:
$sql = "SELECT * FROM {$modx->getTableName('modResource')} WHERE `id` > 0 LIMIT 1000";
$q = $modx->prepare($sql);
$q->execute();
$res = $q->fetchAll(PDO::FETCH_ASSOC);

foreach ($res as $v) {
	//echo $v['pagetitle'];
}
Этот метод отрабатывает уже на чистом PDO, вообще без xPDO объектов.
Но разница с ног не сшибает:
Память: 32.7 Mb
Время: 0.0292019844055

Хотя в этом случае не создается здоровенный объект xPDOCriteria.
Видимо, кудесники из MODx что-там хорошо наоптимизировали в своем xPDO.

Вот такие приемы можно использовать в MODx Revolution.

Да, кстати, выборка 3000 ресурсов первым методом проходит за 0.0797028541565 и занимает 64.0.
Вторым — за 6.38202881813 и кушает 194.5 Mb.

Для замера времени использовалась функция microtime(true), для памяти — memory_get_usage(true).
Василий Наумкин
18 июня 2012, 10:12
modx.pro
32
21 186
0

Комментарии: 39

Иван Брежнев
07 июля 2012, 15:41
0
Это отличная статья, я ей постоянно пользуюсь =)
    KonstantinGreat
    06 октября 2012, 00:02
    0
    очень полезная инфа, скоро будет протестена, спасибо
      Иван Брежнев
      15 октября 2012, 00:54
      0
      Выборка самым последним способом 20к ресурсов

      Ресурсов: 20000
      Памяти скушано: 94.25Мб
      Время работы сниппета 0.58 сек
        Сергей Лелеко
        29 ноября 2012, 20:11
        0
        Василий, не могли бы Вы пояснить по поводу строки $q = $modx->newQuery('modResource', array('id:>' => 0));
        в частности id:>' => 0, тут имеется ввиду значения в столбце айди больше нуля?
        И с modResource не понятно — это системная таблица какая-то или что?
        Я новичек не могу пока до конца разобраться в xPDO по этому прошу не удивляться сильно моим вопросам))
          Иван Тимофеев
          29 ноября 2012, 21:18
          0
          в частности id:>' => 0, тут имеется ввиду значения в столбце айди больше нуля?
          верно

          И с modResource не понятно — это системная таблица какая-то или что?
          modResource это модель описывающая структуру таблицы. таблица *table_prefix*site_content
          Сергей Лелеко
          29 ноября 2012, 21:35
          0
          Иван, а если я уже сгенерировал модель с помощью скрипта для своих таблиц? тогда нужно что-то типа:
          <?php
          $modx->addPackage('leleko-cdb', $modx->getOption('core_path').'components/leleko-cdb/model/','leleko_cdb_');
          и уже тогда вместо modResource мою модель?
          Николай
          19 сентября 2013, 00:10
          0
          Василий, я только вникаю в суть работы PDO и xPDO. Прошу подсказать как вывести все данные из своей созданной таблички.
          $sql = "SELECT * FROM {$modx->getTableName('tablename')} LIMIT 1000";
          $q = $modx->prepare($sql);
          $q->execute();
          $res = $q->fetchAll(PDO::FETCH_ASSOC);
          
          foreach ($res as $v) {
              echo $v['createdon'];
          }
          Ничего не выводится… Я пытался и modx_tablename — тоже результата 0.

          Из предыдущей заметки

          $q = $modx->newQuery('modResource');
          $q->where(array('id:>' => 0));
          $q->limit('100');
          if ($q->prepare() && $q->stmt->execute()) {
              $arr = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
          }
          getStatus('Выборка '.count($arr).' ресурсов через xPDO');
          Это вообще кладет сайт с 500й ошибкой
            Николай
            19 сентября 2013, 00:30
            0
            {$modx->getTableName('tablename')}
            Ага, в этом была проблема, нафиг getTableName только чистое название бд… А на сколько этот метод правильный?
              Василий Наумкин
              19 сентября 2013, 03:36
              0
              Если делаешь для себя и переносить никуда не планируешь — то вполне правильный метод.
            Павел Левин
            02 февраля 2014, 22:10
            0
            Всё пытаюсь исключить из выдачи ненужные поля, но никак не могу понять как это сделать, к примеру мне нужны «id» и «parent», а мне выплёвываются в массив все поля всех найденных документов, что и увеличивает работу скрипта.

            М.б. кто нибудь подскажет ссылочку на статью, для наводки? или пример.
              Сергей Шлоков
              02 февраля 2014, 22:24
              +1
              Для начала почитать можно тут
              Можно тут pdoTools
                Василий Наумкин
                02 февраля 2014, 22:28
                +1
                Ты когда вызываешь toArray(), xPDO довыбирает все невыбранные поля.

                Просто получай нужное через get() или используй PDO — там всё прозрачнее.
                  Павел Левин
                  03 февраля 2014, 18:20
                  0
                  Делаю так
                  $pubTime = strtotime(date("Y-m-d G:i:s"))-(24*60*60);
                  $pubTimeEnd = $pubTime+((24*60*60)*2);
                  $q = $modx->newQuery('modResource', array(
                           'context_key' => 'web'
                          ,'isfolder' => 0
                          ,'deleted' => 0
                          ,'publishedon:>' => $pubTime
                          ,'publishedon:<=' => $pubTimeEnd
                  	));
                  $q->select($modx->getSelectColumns('modResource','modResource','',array('id','parent')));
                  $q->prepare();
                  $q->stmt->execute();
                  $result = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
                  return $modx->toJSON($result);
                  Получаю массив, но не знаю… верно ли я это сделал, не особо понял зачем $modx->getSelectColumns и для чего указывать 'modResource','modResource', всё это меня смущает и возможно оно работает криво??
                    Павел Левин
                    04 февраля 2014, 14:28
                    1
                    0
                    сделал так, работает:
                    $q->select('id,parent');
                Павел Левин
                06 февраля 2014, 15:55
                0
                Не могу найти пример использования OR для запроса в БД, для
                $q->where()
                  Andrey Grachov
                  06 февраля 2014, 16:29
                  1
                  0
                  Этот код:
                  $q = $modx->newQuery('modResource');
                  $q->select(array(
                      'modResource.*',
                  ));
                  $q->where(array(
                      'template' => 1,
                      'OR:pagetitle:LIKE' => '%home%',
                  ));
                  даст такой запрос:
                  SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( `modResource`.`template` = 1 OR `modResource`.`pagetitle` LIKE '%home%' )
                  А этот:
                  $q = $modx->newQuery('modResource');
                  $q->select(array(
                      'modResource.*',
                  ));
                  $q->where(array(
                      array(
                          'template' => 1,
                          'pagetitle:LIKE' => '%home%',
                      ),
                      array(
                          'OR:id:=' => 1,
                          'published' => true,
                      ),
                  ));
                  даст две группы, объединенные оператором OR:
                  SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( `modResource`.`template` = 1 AND `modResource`.`pagetitle` LIKE '%home%' ) OR ( `modResource`.`id` = 1 AND `modResource`.`published` = 1 ) )
                  Дополнительную информацию можно посмотреть тут: rtfm.modx.com/xpdo/2.x/class-reference/xpdoquery/xpdoquery.where
                    Павел Левин
                    07 февраля 2014, 12:10
                    0
                    Спасибо. Странно, что мне он выводит пустой массив, хотя если делать 2 разных запроса, то массивы будут не пусты.

                    Пишу так:
                    array(
                             'context_key'      => $context
                            ,'isfolder'         => 0
                            ,'deleted'          => 0
                            ,'editedon:>'       => $pubTime
                            ,'editedon:<='      => $pubTimeEnd
                            ),
                            array(
                             'OR:context_key'      => $context
                            ,'isfolder'         => 0
                            ,'deleted'          => 0
                            ,'publishedon:>'    => $pubTime
                            ,'publishedon:<='   => $pubTimeEnd
                            )
                    Получаю пустоту [].
                      Andrey Grachov
                      07 февраля 2014, 12:42
                      0
                      Если используете OR:, то надо обязательно добавлять оператор сравнения, в вашем случае
                      'OR:context_key:=' => $context
                      вместо
                      'OR:context_key' => $context
                        Павел Левин
                        07 февраля 2014, 13:39
                        0
                        OR идёт для второго массива или только для значения $context ????

                        Помогло, что-то вывел), буду дальше пытать.
                        спасибо.
                  Артур
                  14 февраля 2014, 08:05
                  0
                  В конце статьи приведен «Ну и самый легкий вариант», очень показался подходящим, сделал на подобии запрос, мне нужно было вывести некоторые поля всех ресурсов + два тв параметра, с одним нет проблем, но два не выходит, цепляется только последний.
                  $sql = "
                  SELECT res.pagetitle, res.introtext, res.content, img.value, video.value
                  FROM {$modx->getTableName('modResource')} res
                  LEFT JOIN {$modx->getTableName('modTemplateVarResource')} img
                  ON res.id=img.contentid AND img.tmplvarid=2
                  LEFT JOIN {$modx->getTableName('modTemplateVarResource')} video
                  ON res.id=video.contentid AND video.tmplvarid=6
                  LIMIT 7000";
                  $q = $modx->prepare($sql);
                  $q->execute();
                  $arr = $q->fetchAll(PDO::FETCH_ASSOC);
                  print_r($arr[0]);
                  и выдается следующая структура массива:
                  Array ( [pagetitle] =>… [introtext] =>… [content] =>… [value] =>… ) т.е. почему-то на псевдонимы не заменяются названия таблиц, и из-за одинаковых полей работает только последний джоин. Как можно этого избежать?
                    Василий Наумкин
                    14 февраля 2014, 08:27
                    0
                    Нужно указать псевдонимы:
                    SELECT res.pagetitle, res.introtext, res.content, img.value as img, video.value as video
                      Артур
                      15 февраля 2014, 06:59
                      0
                      Спасибо! Если можно, спрошу заодно, насколько неразумно в проекте с большим количеством данных, и, например, в ответе запросов Ajax, такими методами выводить информацию?
                        Василий Наумкин
                        15 февраля 2014, 07:02
                        0
                        Запрос лучше строить всё-таки через xPDO, а выводить через PDO.

                        Это можно писать вручную, а можно использовать сниппеты pdoTools и методы самой библиотеки.
                        Алексей
                        28 марта 2014, 18:26
                        0
                        искал на эту полезную информацию на bezumkin.ru минут 30 -)) почему же поиск не выдает этот комментарий по запросу «select as»?
                          Василий Наумкин
                          28 марта 2014, 19:06
                          0
                          Я те больше скажу, эта информация даже по Ctrl+F находится только в твоем комментарии.

                          Офигенно релевантный запрос. Зато обрати внимание, что с твоим комментарием эта тема сразу оказалась на третьем месте и здесь и в яндексе.

                          P.S. На сайте есть еще bezumkin.ru/index для таких интересных статей. В верхнем меню — первая кнопка.
                            Антон Соловьёв
                            28 марта 2014, 22:03
                            0
                            Индекс это хорошо, но было бы удобнее, если была была возможность добавить тему «в избранное» как на modx.im. Чтоб щелкнул на звездочку в теме — и она в профиле в разделе «Избранное».
                              Василий Наумкин
                              28 марта 2014, 22:20
                              +1
                              modx.im — это движок LiveStreet.

                              Мой сайт построен целиком на MODX и компоненте Tickets. Здесь пока нет избранного, но когда-нибудь, возможно, и появится.

                              Мне вообще кажется неправильным строить сайты для модыксеров на базе неMODX решений.
                                Wassi Wassinen
                                29 марта 2014, 00:24
                                0
                                Да, Василий, добавь избранное. Иной раз читаешь, понимаешь — пользительно! А потом силишься вспомнить, йод пьешь,-втираешь, а не вспомнить, что за статья была.

                                И пока не забыл — отчего-то msOrder параметр tplSuccess забирает, только если прописать в дефолтных настройках. В вызове &tplSuccess=`bla-bla` не понимает.
                                  Василий Наумкин
                                  29 марта 2014, 06:07
                                  1
                                  0
                                  Ты готов заказать и оплатить этот новый функционал в Tickets?

                                  На всякий случай — в браузерах дааааавно есть такая функция.
                                    Алексей
                                    29 марта 2014, 07:53
                                    0
                                    Браузер может слететь, комп может полететь из окна (хотя я уже давно пользуюсь синхронизацией избранного с аккаунтом гугла для таких случаев).
                                    Но все же, как интересно было бы смотреть чужое избранное, к примеру, что Илья Уткин подчерпнул ценное на сайте, и что занес в избранное? Кстати, статистика статей добавленных в избранное покруче будет рейтинга — новый критерий для «веса» в поиске.

                                    PS: цена вопроса?
                                      Илья Уткин
                                      29 марта 2014, 18:25
                                      +1
                                      Решил на выходных помочь сообществу и Василию, написал систему избранного для Tickets. Если Василий примет pull reqгest, опишу подробнее уже в отдельной статье.
                                        Илья Уткин
                                        29 марта 2014, 18:41
                                        +1
                                        Как это выглядит, можно посмотреть в минидемонстрации. Сейчас там одно избранное на всех, а в реальности у каждого будет свое избранное.
                                        Василий Наумкин
                                        29 марта 2014, 19:45
                                        0
                                        Посмотрел по диагонали — для начала неплохо. Но нужны еще комментарии и вывод этого избранного самому юзеру, или другим.

                                        Постараюсь доработать на досуге и причесать всё, чтобы было в одном стиле с уже написанным.

                                        Спасибо!
                                        Илья Уткин
                                        29 марта 2014, 19:49
                                        +1
                                        Вывод пользователю:
                                        [[!pdoPage?
                                            &element=`getTickets`
                                            &sortby=`{"star_createdon":"DESC"}`
                                            &groupby=`star_id HAVING star_id IS NOT NULL AND star_user = [[!+modx.user.id]]`
                                        ]]
                                        Wassi Wassinen
                                        30 марта 2014, 00:02
                                        0
                                        Илья, чудесно реализовано! Спасибо!
                    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                    36