Czy EntityFieldQuery naprawdę jest tak nieefektywne?

11

Jestem wpuszczonym nowicjuszem w interfejsie API Entity, ale próbuję to wyleczyć. Pracuję nad witryną, która wykorzystuje wiele typów treści z dołączonymi do nich różnymi polami; nic fajnego. Tak więc, gdy chcę pobrać zestaw wpisów, w swojej ignorancji dzwoniłem bezpośrednio do bazy danych i robiłem coś takiego:

$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();

(tak, prawdopodobnie mógłbym zwinąć kilka z tych wierszy w jedno $the_questions->zdanie; pls zignoruj ​​to na razie.)

Próbując przepisać to za pomocą EntityFieldQuery, wymyślam:

$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();
}

co daje mi pożądane wyniki i jest z pewnością ładniejsze.

Więc teraz zastanawiam się nad wydajnością. Na początek wrzucam każdy z tych fragmentów kodu do głupiej for()pętli, przechwytując time()przed i po wykonaniu. Każdą wersję uruchamiam 100 razy na niezbyt dużej bazie danych i otrzymuję coś takiego:

  • Wersja bezpośrednia: 110 ms
  • Wersja EFQ: 4943 ms

Oczywiście po ponownym uruchomieniu testu otrzymuję różne wyniki, ale wyniki są konsekwentnie w tym samym parku.

Yikes. Czy robię tu coś złego, czy to tylko koszt korzystania z EFQ? Nie przeprowadziłem żadnego specjalnego strojenia bazy danych w odniesieniu do typów treści; wynikają one ze zdefiniowania typów treści w zwykły sposób oparty na formularzach. jakieś pomysły? Kod EFQ jest zdecydowanie czystszy, ale naprawdę nie sądzę, że mogę sobie pozwolić na 40-krotny wzrost wydajności.

Jim Miller
źródło
3
czy możesz zrzucić oba wygenerowane zapytania SQL?
Andre Baumeier,
1
Zobacz ten, jeśli nie masz pewności, jak pobrać SQL z EFQ
Clive
2
OK, postęp jest następujący: w mojej witrynie jest kilka reguł dostępu do węzłów, które znacznie zwiększają rozmiar zapytania. Zostały one automatycznie zastosowane do zapytania EFQ (nawet jeśli nie ma go ->addTag('node_access')w zapytaniu ??). Zmieniłem zapytanie „bezpośrednie” za pomocą znacznika node_access, a czasy wykonania są znacznie bliższe: czas EFQ jest teraz tylko o współczynnik 2 większy niż bezpośrednie podejście, co wygląda rozsądnie, biorąc pod uwagę względne SQL, które wypompowują oba ( Mogę pisać, jeśli ludzie nadal się przejmują). (ciąg dalszy od następnego komentarza ....)
Jim Miller,
Więc teraz pytanie, jak sądzę, dlaczego automatycznie otrzymuję informacje o węźle node_access w wersji EFQ? Myślałem, że musisz wyraźnie o to poprosić za pomocą klauzuli addTag ()?
Jim Miller,

Odpowiedzi:

10

EntityFieldQueryKlasa jest tak skuteczny jak jego wymagania pozwalają mu być. Musi być kompatybilny z dowolnymi klasami pamięci masowej, nawet z tymi, które używają silnika NoSQL do przechowywania danych pól, takich jak ta, która używa MongoDB . Z tego powodu EntityFieldQuerynie można bezpośrednio wysłać zapytania do bazy danych, ponieważ bieżący backend pamięci pola może w ogóle nie używać bazy danych SQL.

Nawet w przypadku, gdy pamięć masowa używa silnika SQL do przechowywania danych, odpowiednik $query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id'); $query->condition('role.field_user_role_value', $some_value);dla EntityFieldQueryklasy wymaga:

  • Kod do zbudowania nazwy tabeli bazy danych na podstawie nazwy pola
  • Kod, aby zbudować warunek używany do połączenia tabeli zawierającej dane pola z tabelą zawierającą dane encji
  • Kod do zbudowania nazwy wiersza bazy danych zawierającego dane pola

Różnica jest natychmiast widoczna: w jednym przypadku używasz trzech łańcuchów literalnych, podczas gdy w drugim przypadku jest kod, który (w najprostszym przypadku) łączy łańcuchy.

Zgodnie z komentarzem na temat kodu, który sprawdza, czy użytkownik ma uprawnienia dostępu do pól, można go obejść, używając następującego wiersza do kodu przy użyciu EntityFieldQueryklasy.

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');

Działa to, jeśli używasz Drupala 7.15 lub nowszego; we wcześniejszych wersjach powinieneś użyć następującego kodu.

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

Jak zwykle nie należy pomijać uprawnień dostępu, jeśli kod może pokazywać użytkownikowi informacje, do których użytkownik nie powinien mieć dostępu. Jest to podobne do tego, co robi Drupal, gdy niepublikowany węzeł jest pokazywany tylko użytkownikom, którzy mają uprawnienia do wyświetlania niepublikowanych węzłów. Jeśli celem kodu jest na przykład wybranie niektórych jednostek, które są sukcesywnie usuwane (np. Podczas zadań CRON), to ominięcie kontroli dostępu nie wyrządzi żadnej szkody i jest to jedyny sposób, aby kontynuować.

kiamlaluno
źródło
I należy przyznać, że jestem chyba nie jest w porządku, ponieważ pierwsze zapytanie używa pagera, zbyt (zrobił zauważam ->extend('PagerDefault');na początku)
Mojžiš
Ups, masz rację.
kiamlaluno
to mnie bardzo zainteresowało, więc próbuję czegoś podobnego do powyższego eksperymentu i nie mogę potwierdzić ogromnej różnicy liczb ... czy ktoś też mógłby spróbować?
mojzis
Tak więc, tylko w celu potwierdzenia: wywołania EFQ ZAWSZE wywołują reguły dostępu do węzła witryny, chyba że zrobisz coś, aby temu zapobiec (jak opisano powyżej). Dobrze?
Jim Miller,
@JimMiller To prawda i jest to powód, dla którego znacznik „DANGEROUS_ACCESS_CHECK_OPT_OUT” został dodany do Drupala 7.15.
kiamlaluno