Intereting Posts

Улучшение производительности WP_Query для нескольких таксономий

У меня есть сайт с несколькими пользовательскими таксономиями и обнаружил, что одна из самых медленных частей сайта пытается запросить OR с несколькими из них сразу. Я использую WP_Query следующим образом:

 array( 'tax_query' => array( 'relation' => 'OR', array('taxonomy' => 'tax1', 'field' => 'slug', 'terms' => 'term1'), array('taxonomy' => 'tax2', 'field' => 'slug', 'terms' => 'term2'), array('taxonomy' => 'tax3', 'field' => 'slug', 'terms' => 'term3'), array('taxonomy' => 'tax4', 'field' => 'slug', 'terms' => 'term4'), ) ) 

Генерируемый SQL занимает недопустимые 6 секунд для запуска:

 SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) INNER JOIN wp_term_relationships AS tt1 ON (wp_posts.ID = tt1.object_id) INNER JOIN wp_term_relationships AS tt2 ON (wp_posts.ID = tt2.object_id) INNER JOIN wp_term_relationships AS tt3 ON (wp_posts.ID = tt3.object_id) WHERE 1=1 AND wp_posts.ID NOT IN (70) AND (wp_term_relationships.term_taxonomy_id IN (23) OR tt1.term_taxonomy_id IN (5) OR tt2.term_taxonomy_id IN (11) OR tt3.term_taxonomy_id IN (10) ) AND (wp_posts.post_status = 'publish') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 500 

Но этот эквивалентный запрос занимает намного больше 0,29 секунды:

 SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND wp_posts.ID NOT IN (70) AND (wp_term_relationships.term_taxonomy_id IN (23, 5, 11, 10)) AND (wp_posts.post_status = 'publish') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 500 

Ясно, что множественные соединения делают его медленнее, чем нужно. SQL не волнует, что термины исходят из разных таксономий, но WP_Query делает, потому что их ищут пули. Есть ли способ убедить WP_Query генерировать что-то ближе ко второму?

(Обратите внимание, что выше было анонимным для защиты моего клиента)

Solutions Collecting From Web of "Улучшение производительности WP_Query для нескольких таксономий"

У меня есть решение, но оно очень уродливое. Мне бы хотелось услышать лучшее, но я не уверен, что это возможно.

WP_Query::get_posts() вызывает parse_tax_query() дважды: сначала рядом с началом, затем снова перед тем, как получить от него SQL. Нет единственного крючка, который позволяет мне перехватывать и корректировать значение $tax_query вовремя, чтобы настроить SQL, поэтому вместо этого я должен был сделать это в двух частях.

  • Действие на pre_get_posts , около начала get_posts() распознает таксономические запросы с 'relation' => 'OR' и упрощает их, чтобы они генерировали только одно соединение в wp_term_relationships . В то же время он сохраняет разрешенный список идентификаторов из всех таксономий внутри объекта «WP_Query» для использования позже.

  • Фильтр на posts_where_paged , намного позже в query_posts() , проверяет сохраненный список идентификаторов и заменяет условие на соединение.

Вот код:

 add_action('pre_get_posts', 'wp_query__pre'); function wp_query__pre ($wp_query) { if (!isset($wp_query->query['tax_query'])) return; if ($wp_query->query['tax_query']['relation'] != 'OR') return; $allterms = array(); foreach ($wp_query->tax_query->queries as $query) { $tax = $query['taxonomy']; $terms = $query['terms']; $wp_query->tax_query->_transform_terms($terms, $query['taxonomy'], $query['field'], 'term_taxonomy_id'); $allterms = array_merge($allterms, $terms); } $tax_query = array(array( 'taxonomy' => $tax, 'terms' => $terms, 'operator' => 'IN', 'include_children' => 0, 'field' => 'term_taxonomy_id', )); $wp_query->query['tax_query'] = $tax_query; $wp_query->query_vars['tax_query'] = $tax_query; $wp_query->tax_query = new WP_Tax_Query($tax_query); $wp_query->saved_tax_terms = $allterms; } add_filter('posts_where_paged', 'wp_query__where', 10, 2); function wp_query__where ($where, $wp_query) { if (!empty($wp_query->saved_tax_terms)) { $terms = implode(", ", $wp_query->dft_tax_terms); $where = preg_replace("!term_taxonomy_id IN \([^)]*\)!", "term_taxonomy_id IN ($terms)", $where); } return $where; } 

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

Потенциал этого подхода заключается в том, что он не требует, чтобы остальная часть вашего кода знала об этом. Просто запросите запрос о таксономии с помощью OR и он будет подхвачен и оптимизирован. Он не обеспечил столь же большого увеличения скорости, как я надеялся, но это было определенное улучшение.

Мне интересно, если команда WordPress не должна включать что-то подобное в основной код.


Обновление: как и подобает такой уродливой хаке, это нарушается в WordPress 3.2. Я ищу исправление.

Вы можете использовать WordPress более эффективный запрос, используя фильтры posts_join и posts_where, что-то вроде:

 add_filter( 'posts_join', 'tax_posts_join', 10, 2 ); add_filter( 'posts_where', 'tax_posts_where', 10, 2 ); add_filter( 'posts_request', 'tax_posts_request' ); function tax_posts_join( $sql, $wp_query ){ if( $tax_ids = $wp_query->get('term_taxonomy_ids_in') ) $sql .= " INNER JOIN wp_term_relationships ON ( wp_posts.ID = wp_term_relationships.object_id )"; return $sql; } function tax_posts_where( $sql, $wp_query ){ if( $tax_ids = $wp_query->get('term_taxonomy_ids_in') ){ $tax_ids = implode( ', ', $tax_ids ); $sql .= " AND ( wp_term_relationships.term_taxonomy_id IN (".$tax_ids.") ) "; } return $sql; } function tax_posts_request( $sql ){ //var_dump( $sql ); return $sql; } $args = array( 'term_taxonomy_ids_in' => array(23, 5, 11, 10) ); $tax_posts = new WP_Query( $args ); 

Это можно немного очистить, возможно, дезинфицировать идентификаторы таксономии, прежде чем вы posts_groupby sql, вам может понадобиться фильтр posts_groupby , но вы должны идти в правильном направлении. http://codex.wordpress.org/Custom_Queries