Действительно ли EntityFieldQuery неэффективен?

11

Я новичок в Entity API, но пытаюсь это исправить. Я работаю над сайтом, который использует несколько типов контента с различными полями, прикрепленными к ним; ничего фантастического. Поэтому, когда я хочу получить набор записей, я по своему невежеству обращаюсь непосредственно к базе данных и делаю что-то вроде этого:

$query = db_select('node', 'n')->extend('PagerDefault');
$query->fields('n', array('nid'));
$query->condition('n.type', 'my_content_type');

$query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id');
$query->condition('role.field_user_role_value', $some_value);

$query->leftJoin('field_data_field_withdrawn_time', 'wt', 'n.nid = wt.entity_id');
$query->condition('wt.field_withdrawn_time_value', 0);

$query->orderBy('n.created', 'desc');

$query->limit(10);

$result = $the_questions->execute()->fetchCol();

(да, я мог бы, вероятно, свести кучу этих строк в одно $the_questions->утверждение; пока проигнорируйте это.)

Пытаясь переписать это с EntityFieldQuery, я придумываю:

$query = new EntityFieldQuery();
$query
  ->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'my_content_type')
  ->fieldCondition('field_user_role', 'value', $some_value)
  ->fieldCondition('field_withdrawn_time', 'value', 0)
  ->propertyOrderBy('created', 'desc')
  ->pager(10);

$result = $query->execute();

if (isset($result['node'])) {
    $result_nids = array_keys($result['node']);
}
else {
    $result_nids = array();
}

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

Итак, теперь я задаюсь вопросом о производительности. Для начала я добавляю каждый из этих битов кода в тупой for()цикл, записывая time()до и после выполнения. Я запускаю каждую версию 100 раз по не очень большой базе данных и получаю что-то вроде этого:

  • Прямая версия: 110 мс
  • Версия EFQ: 4943 мсек

Очевидно, что при повторном запуске теста я получаю разные результаты, но результаты постоянно находятся на одном уровне.

Хлоп. Я что-то здесь не так делаю, или это просто стоимость использования EFQ? Я не делал никакой специальной настройки базы данных в отношении типов контента; это именно то, что приходит от определения типов контента обычным, основанным на форме способом. есть идеи? Код EFQ определенно чище, но я действительно не думаю, что могу позволить себе 40-кратное снижение производительности.

Джим миллер
источник
3
Вы можете сбросить оба сгенерированных SQL-запросов?
Андре Баумайер
1
См. Этот, если вы не уверены, как получить SQL из EFQ
Клайв
2
Хорошо, есть прогресс: здесь происходит то, что на моем сайте есть куча правил доступа к узлам, которые немного увеличивают размер запроса. Те были автоматически применены к запросу EFQ (хотя нет ->addTag('node_access')в запросе ??). Я перезапускаю «прямой» запрос с тэгом node_access, и время выполнения намного ближе: время EFQ теперь только примерно в 2 раза больше, чем прямой подход, что выглядит разумно, учитывая относительный SQL, который выкачивают оба (что Я могу опубликовать, если люди все еще заботятся). (продолжение следующего комментария ....)
Джим Миллер
Итак, теперь вопрос, я думаю, почему я автоматически получаю материал node_access в версии EFQ? Я думал, что вы должны были явно попросить об этом через предложение addTag () ??
Джим Миллер

Ответы:

10

EntityFieldQueryКласс столь же эффективным , как его требования позволяют ему быть. Он должен быть совместим с любыми классами хранения полей, даже с теми, которые используют движок NoSQL для хранения полевых данных, такими как тот, который использует MongoDB . По этой причине EntityFieldQueryнельзя напрямую запрашивать базу данных, поскольку текущий сервер хранения полей может вообще не использовать базу данных SQL.

Даже в том случае, если хранилище полей использует механизм SQL для хранения своих данных, эквивалент $query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id'); $query->condition('role.field_user_role_value', $some_value);для EntityFieldQueryкласса требует:

  • Код для построения имени таблицы базы данных из имени поля
  • Код для построения условия, используемого для объединения таблицы, содержащей данные поля, с таблицей, содержащей данные объекта
  • Код для построения имени строки базы данных, содержащей данные поля

Разница сразу видна: в одном случае вы используете три литеральные строки, а в другом - код, который (в простейшем случае) объединяет строки.

Согласно вашему комментарию о коде, который проверяет, есть ли у пользователя разрешение на доступ к полям, вы можете обойти это, используя следующую строку, к коду, используя EntityFieldQueryкласс.

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');

Это работает, если вы используете Drupal 7.15 или выше; для более ранних версий вы должны использовать следующий код.

$account = user_load(1);
$query->addMetaData('account', $account);

Как обычно, вы не должны обходить разрешение на доступ, если код может показывать пользователю информацию, к которой у пользователя не должно быть доступа. Это похоже на то, что делается в Drupal, когда неопубликованный узел показывается только пользователям, которые имеют разрешение на просмотр неопубликованных узлов. Если целью кода является, например, выбор некоторых сущностей, которые удаляются последовательно (например, во время задач cron), то обход контроля доступа не причиняет никакого вреда, и это единственный способ продолжить.

киамлалуно
источник
я должен признать, что я, вероятно, не прав, так как в первом запросе тоже используется пейджер ( ->extend('PagerDefault');сначала я не заметил )
mojzis
Упс, вы правы.
kiamlaluno
это действительно заинтересовало меня, поэтому я пробую что-то в соответствии с приведенным выше экспериментом и не могу подтвердить огромную разницу в числах ... может кто-нибудь тоже попробовать это, пожалуйста?
Мойзис
Итак, просто для подтверждения: вызовы EFQ ВСЕГДА вызывают правила доступа к узлу сайта, если вы не сделаете что-то, чтобы этого не произошло (как описано выше). Правильно?
Джим Миллер
@JimMiller Это верно, и именно поэтому тег DANGEROUS_ACCESS_CHECK_OPT_OUT был добавлен в Drupal 7.15.
kiamlaluno