Meta-Abfrage schrecklich langsam

Ich habe eine benutzerdefinierte Meta-Abfrage, die schrecklich langsam ist oder nicht einmal bis zum Ende lädt. Mit bis zu drei arrays in 'meta_query' Abfrage 'meta_query' die Abfrage gut, mit vier und 'meta_query' funktioniert es nicht mehr.

Bei der Suche nach einem Grund habe ich diesen Beitrag gefunden, aber ich bin absolut nicht vertraut mit benutzerdefinierten DB-Abfragen.

Jede Hilfe wird sehr geschätzt! Vielen Dank!

  $post_type, 'posts_per_page' => -1, 'meta_query' => array( 'relation' => 'OR', array( 'key'=>'_author', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_publisher', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_1', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_2', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_3', 'value'=> $author_single["fullname"], 'compare' => '=' ) ) ); $posts = new WP_Query($args); if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?> 
  • <a href="https://wordpress.stackexchange.com/questions/158898/meta-query-terribly-slow/">
  • – – – – –

    Aktualisierter Code mit den Zusätzen boger gemacht:

    Seite.php

      $post_type, 'posts_per_page' => -1, 'meta_query' => array( 'relation' => 'OR', array( 'key'=>'_author', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_publisher', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_1', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_2', 'value'=> $author_single["fullname"], 'compare' => '=' ), array( 'key'=>'_contributor_3', 'value'=> $author_single["fullname"], 'compare' => '=' ) ) ); add_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10, 2 ); $posts = new WP_Query($args); if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?> 
  • <a href="https://wordpress.stackexchange.com/questions/158898/meta-query-terribly-slow/">
  • functionen.php

     function wpse158898_posts_clauses( $pieces, $query ) { global $wpdb; $relation = isset( $query->meta_query->relation ) ? $query->meta_query->relation : 'AND'; if ( $relation != 'OR' ) return $pieces; // Only makes sense if OR. $prepare_args = array(); $key_value_compares = array(); foreach ( $query->meta_query->queries as $meta_query ) { // Doesn't work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS. if ( ! isset( $meta_query['value'] ) || is_array( $meta_query['value'] ) ) return $pieces; // Bail if no value or is array. $key_value_compares[] = '(pm.meta_key = %s AND pm.meta_value ' . $meta_query['compare'] . ' %s)'; $prepare_args[] = $meta_query['key']; $prepare_args[] = $meta_query['value']; } $sql = ' JOIN ' . $wpdb->postmeta . ' pm on pm.post_id = ' . $wpdb->posts . '.ID' . ' AND (' . implode( ' ' . $relation . ' ', $key_value_compares ) . ')'; array_unshift( $prepare_args, $sql ); $pieces['join'] = call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args ); $pieces['where'] = preg_replace( '/ AND[^w]+wp_postmeta.*$/s', '', $pieces['where'] ); // Zap postmeta clauses. return $pieces; } 

    – – –

    $posts->request Ausgänge

     $args = array( 'post_type' => $post_type, 'posts_per_page' => -1, 'meta_query' => array( 'relation' => 'OR', array( 'key'=>'_author', 'value'=> "Hanna Meier", 'compare' => '=' ), array( 'key'=>'_publisher', 'value'=> "Friedhelm Peters", 'compare' => '=' ) ) ); 

    ohne die benutzerdefinierte Abfrage

     SELECT wp_vacat_posts.* FROM wp_vacat_posts INNER JOIN wp_vacat_postmeta ON (wp_vacat_posts.ID = wp_vacat_postmeta.post_id) INNER JOIN wp_vacat_postmeta AS mt1 ON (wp_vacat_posts.ID = mt1.post_id) WHERE 1=1 AND wp_vacat_posts.post_type = 'product' AND (wp_vacat_posts.post_status = 'publish' OR wp_vacat_posts.post_status = 'private') AND ( (wp_vacat_postmeta.meta_key = '_author' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = 'Hanna Meier') OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Friedhelm Peters') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC 

    mit der benutzerdefinierten Abfrage

     SELECT wp_vacat_posts.* FROM wp_vacat_posts JOIN wp_vacat_postmeta pm on pm.post_id = wp_vacat_posts.ID AND ((pm.meta_key = '_author' AND pm.meta_value = 'Hanna Meier') OR (pm.meta_key = '_publisher' AND pm.meta_value = 'Friedhelm Peters')) WHERE 1=1 AND wp_vacat_posts.post_type = 'product' AND (wp_vacat_posts.post_status = 'publish' OR wp_vacat_posts.post_status = 'private') AND ( (wp_vacat_postmeta.meta_key = '_author' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = 'Hanna Meier') OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Friedhelm Peters') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC 

    Solutions Collecting From Web of "Meta-Abfrage schrecklich langsam"

    Ich bin auf dieses Problem gestoßen und es sieht so aus, als würde MySQL nicht gut mit den multiplen Joins zu derselben Tabelle (wp_postmeta) und OR-ed WHERE umgehen, die WP hier generiert. Ich habe damit umgegangen, indem ich den Join neu geschrieben habe und wie in dem Beitrag erwähnt, auf den Sie verlinken – hier ist eine Version, die in Ihrem Fall funktionieren sollte ( aktualisiert für WP 4.1.1 ) ( aktualisiert für WP 4.2.4 ):

     function wpse158898_posts_clauses( $pieces, $query ) { global $wpdb; $relation = isset( $query->meta_query->relation ) ? $query->meta_query->relation : 'AND'; if ( $relation != 'OR' ) return $pieces; // Only makes sense if OR. $prepare_args = array(); $key_value_compares = array(); foreach ( $query->meta_query->queries as $key => $meta_query ) { if ( ! is_array( $meta_query ) ) continue; // Doesn't work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS. if ( $meta_query['compare'] === 'EXISTS' ) { $key_value_compares[] = '(pm.meta_key = %s)'; $prepare_args[] = $meta_query['key']; } else { if ( ! isset( $meta_query['value'] ) || is_array( $meta_query['value'] ) ) return $pieces; // Bail if no value or is array. $key_value_compares[] = '(pm.meta_key = %s AND pm.meta_value ' . $meta_query['compare'] . ' %s)'; $prepare_args[] = $meta_query['key']; $prepare_args[] = $meta_query['value']; } } $sql = ' JOIN ' . $wpdb->postmeta . ' pm on pm.post_id = ' . $wpdb->posts . '.ID' . ' AND (' . implode( ' ' . $relation . ' ', $key_value_compares ) . ')'; array_unshift( $prepare_args, $sql ); $pieces['join'] = call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args ); // Zap postmeta clauses. $wheres = explode( "\n", $pieces[ 'where' ] ); foreach ( $wheres as &$where ) { $where = preg_replace( array( '/ +\( +' . $wpdb->postmeta . '\.meta_key .+\) *$/', '/ +\( +mt[0-9]+\.meta_key .+\) *$/', '/ +mt[0-9]+.meta_key = \'[^\']*\'/', ), '(1=1)', $where ); } $pieces[ 'where' ] = implode( '', $wheres ); $pieces['orderby'] = str_replace( $wpdb->postmeta, 'pm', $pieces['orderby'] ); // Sorting won't really work but at least make it not crap out. return $pieces; } 

    und dann um deine Frage:

      add_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10, 2 ); $posts = new WP_Query($args); remove_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10 ); 

    Nachtrag:

    Der Fix dafür, ticket 24093 , schaffte es nicht in 4.0 ( und es hat das Problem sowieso nicht behoben ), also habe ich ursprünglich eine verallgemeinerte Version des oben genannten versucht, aber es ist zu flockig, wirklich eine solche Lösung zu versuchen, also ich habe es entfernt …

    Die kurze Antwort ist, dass Metadaten in WordPress nicht für relationale Daten verwendet werden sollen. Es ist nicht die Idee hinter Metadaten, Posts unter verschiedenen Bedingungen zu ihren Metadaten zu holen. Daher sind die Abfragen, Tabellenstrukturen und Indizes dafür nicht optimiert.

    Die längere Antwort:

    Was Ihre Meta-Abfrage ergibt, ist ungefähr so:

     SELECT wp_4_posts.* FROM wp_4_posts INNER JOIN wp_4_postmeta ON (wp_4_posts.ID = wp_4_postmeta.post_id) INNER JOIN wp_4_postmeta AS mt1 ON (wp_4_posts.ID = mt1.post_id) INNER JOIN wp_4_postmeta AS mt2 ON (wp_4_posts.ID = mt2.post_id) INNER JOIN wp_4_postmeta AS mt3 ON (wp_4_posts.ID = mt3.post_id) INNER JOIN wp_4_postmeta AS mt4 ON (wp_4_posts.ID = mt4.post_id) WHERE 1=1 AND wp_4_posts.post_type = 'post' AND (wp_4_posts.post_status = 'publish' OR wp_4_posts.post_status = 'private') AND ( (wp_4_postmeta.meta_key = '_author' AND CAST(wp_4_postmeta.meta_value AS CHAR) = 'Test') OR (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Test') OR (mt2.meta_key = '_contributor_1' AND CAST(mt2.meta_value AS CHAR) = 'Test') OR (mt3.meta_key = '_contributor_2' AND CAST(mt3.meta_value AS CHAR) = 'Test') OR (mt4.meta_key = '_contributor_3' AND CAST(mt4.meta_value AS CHAR) = 'Test') ) GROUP BY wp_4_posts.ID ORDER BY wp_4_posts.post_date DESC 

    Sehen wir uns einmal an, wie MySQL diese Abfrage behandelt ( EXPLAIN ):

      id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE wp_4_posts range PRIMARY,type_status_date type_status_date 124 NULL 5 Using where; Using temporary; Using filesort 1 SIMPLE wp_4_postmeta ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1 1 SIMPLE mt1 ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1 1 SIMPLE mt2 ref post_id,meta_key post_id 8 wordpress.mt1.post_id 1 Using where 1 SIMPLE mt3 ref post_id,meta_key post_id 8 wordpress.wp_4_posts.ID 1 1 SIMPLE mt4 ref post_id,meta_key post_id 8 wordpress.wp_4_postmeta.post_id 1 Using where 

    Nun, was Sie sehen können, macht MySQL eine Auswahl auf wp_posts und verbindet 5 Mal die Tabelle wp_postmeta . Der Typ ref besagt, dass MySQL alle Zeilen in dieser Tabelle untersuchen muss, wobei der Index (post_id, meta_key) einen nicht indizierten Spaltenwert mit Ihrer where Klausel vergleicht, und dass für jede Kombination von Zeilen aus der vorherigen Tabelle . Das MySQL-Handbuch sagt : »Wenn der verwendete Schlüssel nur wenigen Zeilen entspricht, ist das ein guter Join-Typ.« Und das ist das erste Problem: Auf einem durchschnittlichen WordPress-System kann die Anzahl der Post-Metas pro Post leicht wachsen bis zu 30-40 Datensätze oder mehr. Der andere mögliche Schlüssel meta_key wächst mit Ihrer meta_key . Wenn Sie also 100 Posts haben und jeder ein _publisher Meta hat, gibt es _publisher 100 Zeilen mit diesem Wert als meta_key in wp_postmeta .

    Um all diese möglichen Ergebnisse zu behandeln, erstellt mysql eine temporäre Tabelle ( using temporary ). Wenn diese Tabelle zu groß wird, speichert der Server sie normalerweise auf dem Datenträger statt des Speichers. Ein weiterer möglicher Engpass.

    Mögliche Lösungen

    Wie in den vorhandenen Antworten beschrieben, könnten Sie versuchen, die Abfrage selbst zu optimieren. Das mag gut für Ihre Bedenken funktionieren, kann aber zu Problemen führen, wenn die Post- / Postmetatabellen wachsen.

    Wenn Sie jedoch die WordPress-Abfrage-API verwenden möchten, sollten Sie erwägen, Taxonomien zu verwenden, um Daten zu speichern, nach denen Sie suchen möchten.

    ORs sind sehr teuer.

    Sie haben zu viele Schlüssel, aber nehmen wir an, Sie können das jetzt nicht ändern. Die andere Sache, die Sie tun können, ohne zu viel zu programmieren, ist die Anzahl der Posts zu ändern, die Sie bekommen, ändern Sie 'posts_per_page' auf 10 oder eine größere Zahl, und sehen Sie, wie stark sich die performance ändert.

    Das könnte etwas zu spät sein, aber ich stieß auf das gleiche Problem. Beim Erstellen eines Plugins für die Suche nach Eigenschaften würde meine erweiterte Suchoption bis zu 20 verschiedene Meta-Einträge für jeden Beitrag abfragen, um diejenigen zu finden, die den Suchkriterien entsprechen.

    Meine Lösung bestand darin, die database direkt mit $wpdb global $wpdb . Ich habe jeden Meta-Eintrag einzeln abgefragt und die post_ids der Beiträge gespeichert, die zu jedem Kriterium passen. Ich habe dann einen Schnittpunkt in jedem der übereinstimmenden Sätze gemacht, um die post_ids , die alle Kriterien erfüllten.

    Mein Fall war etwas einfach, weil ich keine OR Elemente hatte, die ich erklären musste, aber sie konnten ziemlich leicht eingeschlossen werden. Abhängig davon, wie komplex Ihre Anfrage ist, ist dies eine funktionierende und schnelle Lösung. Obwohl, ich gebe zu, es ist eine schlechte Option im Vergleich zu einer echten relationalen Abfrage.

    Der folgende Code wurde stark von dem, was ich verwendet habe, vereinfacht, aber Sie können die Idee daraus ableiten.

     class property_search{ public function get_results($args){ $potential_ids=[]; foreach($args as $key=>$value){ $potential_ids[$key]=$this->get_ids_by_query(" SELECT post_id FROM wp_postmeta WHERE meta_key = '".$key."' AND CAST(meta_value AS UNSIGNED) > '".$value."' ");//a new operator would need to be created to handle each type of data and comparison. } $ids=[]; foreach($potential_ids as $key=>$temp_ids){ if(count($ids)==0){ $ids=$temp_ids; }else{ $ids=array_intersect($ids,$temp_ids); } } $paged = (get_query_var('paged')) ? get_query_var('paged') : 1; $args = array( 'posts_per_page'=> 50, 'post_type'=>'property', 'post_status'=>'publish', 'paged'=>$paged, 'post__in'=>$ids, ); $search = new WP_Query($args); return $search; } public function get_ids_by_query($query){ global $wpdb; $data=$wpdb->get_results($query,'ARRAY_A'); $results=[]; foreach($data as $entry){ $results[]=$entry['post_id']; } return $results; } } 

    wp_postmeta hat ineffiziente Indizes. Hier ist eine Diskussion über solche, plus empfohlene Heilmittel:

    http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmeta