N+1 Query trong WordPress: Cách nhận biết và xử lý gọn nhẹ

N+1 là một vấn đề hiệu năng kinh điển khi bạn lặp qua danh sách đối tượng (post, user, term…) rồi lại truy vấn thêm dữ liệu liên quan cho từng phần tử trong vòng lặp. Kết quả là 1 truy vấn ban đầu cộng thêm N truy vấn phát sinh. Bài này giải thích N+1 theo ngữ cảnh WordPress, đưa ví dụ cực dễ hiểu và cách sửa nhanh bằng kỹ thuật “eager loading” và priming cache.

N+1 Query trong WordPress: Cách nhận biết và xử lý gọn nhẹ

N+1 là gì trong WordPress?

Giả sử bạn cần hiển thị 20 bài viết kèm theo một custom field và danh sách category. Nếu trong vòng lặp bạn gọi get_post_meta()get_the_terms() cho từng bài, WordPress có thể phát sinh thêm hàng chục truy vấn meta/term. Đó chính là N+1: 1 truy vấn để lấy danh sách bài + N truy vấn cho meta/term.

Ví dụ “xấu”: Gọi meta/term trong vòng lặp

<?php
$query = new WP_Query([
    'post_type'      => 'post',
    'posts_per_page' => 20,
]);

if ( $query->have_posts() ) :
    while ( $query->have_posts() ) : $query->the_post();
        // Mỗi lần lặp có thể tạo thêm 1 truy vấn meta
        $rating = get_post_meta( get_the_ID(), 'rating', true );

        // Và thêm 1 truy vấn cho terms
        $cats = get_the_terms( get_the_ID(), 'category' );

        echo '<h3>' . esc_html( get_the_title() ) . '</h3>';
        echo '<p>Rating: ' . esc_html( $rating ) . '</p>';
        if ( ! is_wp_error( $cats ) && ! empty( $cats ) ) {
            echo '<p>Categories: ' . esc_html( implode( ', ', wp_list_pluck( $cats, 'name' ) ) ) . '</p>';
        }
    endwhile;
    wp_reset_postdata();
endif;

Sửa nhanh với “eager loading” bằng cache priming

Ý tưởng: lấy sẵn tất cả meta và term cho toàn bộ danh sách post trước khi render. WordPress đã có sẵn hàm nội bộ để prime cache.

<?php
$query = new WP_Query([
    'post_type'      => 'post',
    'posts_per_page' => 20,
    'fields'         => 'ids', // Lấy ID cho nhanh, ít dữ liệu
]);

$post_ids = $query->posts;

// Prime cache: meta + post object
if ( function_exists( '_prime_post_caches' ) ) {
    _prime_post_caches( $post_ids, true, true );
}

// Prime cache: terms (category, tag...)
if ( function_exists( 'update_object_term_cache' ) ) {
    update_object_term_cache( $post_ids, 'post' );
}

// Bây giờ gọi get_post() / get_post_meta() / get_the_terms() gần như không phát sinh thêm query
foreach ( $post_ids as $pid ) {
    $post   = get_post( $pid );
    $rating = get_post_meta( $pid, 'rating', true );
    $cats   = get_the_terms( $pid, 'category' );

    echo '<h3>' . esc_html( get_the_title( $post ) ) . '</h3>';
    echo '<p>Rating: ' . esc_html( $rating ) . '</p>';
    if ( ! is_wp_error( $cats ) && ! empty( $cats ) ) {
        echo '<p>Categories: ' . esc_html( implode( ', ', wp_list_pluck( $cats, 'name' ) ) ) . '</p>';
    }
}

Biến thể: Không dùng hàm nội bộ? Dùng API public

Nếu bạn muốn hạn chế dùng hàm nội bộ, có thể dùng các hàm public để đạt hiệu ứng tương tự.

<?php
$query = new WP_Query([
    'post_type'      => 'post',
    'posts_per_page' => 20,
    'fields'         => 'ids',
]);

$post_ids = $query->posts;

// Eager load post meta
update_meta_cache( 'post', $post_ids );

// Eager load terms
update_object_term_cache( $post_ids, 'post' );

foreach ( $post_ids as $pid ) {
    $rating = get_post_meta( $pid, 'rating', true );
    $cats   = get_the_terms( $pid, 'category' );
    echo '<p>#' . intval( $pid ) . ' - Rating: ' . esc_html( $rating ) . '</p>';
}

N+1 với User Meta: Ví dụ đơn giản

Khi bạn lặp qua danh sách user và gọi get_user_meta() cho từng user, cũng có nguy cơ N+1. Hãy prime cache trước.

<?php
$user_query = new WP_User_Query([
    'number' => 50,
    'fields' => ['ID', 'display_name'],
]);

$users = $user_query->get_results();
$user_ids = wp_list_pluck( $users, 'ID' );

// Eager load user meta
update_meta_cache( 'user', $user_ids );

foreach ( $users as $u ) {
    $score = get_user_meta( $u->ID, 'score', true ); // Lúc này lấy từ cache
    echo '<p>' . esc_html( $u->display_name ) . ': ' . esc_html( $score ) . '</p>';
}

Checklist chống N+1 trong WordPress

  • Duyệt nhiều post? Hãy lấy ID trước bằng fields="ids" để nhẹ payload.
  • Prime cache trước khi render: _prime_post_caches(), update_meta_cache(), update_object_term_cache().
  • Với user: dùng update_meta_cache( 'user', $user_ids ) trước khi gọi get_user_meta().
  • Giảm truy vấn lặp lại bằng cách gom ID rồi xử lý hàng loạt thay vì gọi từng phần tử trong vòng lặp.
  • Đo đạc bằng Query Monitor hoặc bật define('SAVEQUERIES', true) khi cần debug cục bộ.

Khi nào nên tối ưu?

Nếu bạn thấy số lượng query tăng tuyến tính theo số item trong vòng lặp, đó là dấu hiệu N+1. Với các trang list lớn (archive, trang admin tùy biến, cron render), việc prime cache trước khi render có thể giảm query từ hàng trăm xuống còn vài chục hoặc ít hơn.

Kết luận

N+1 trong WordPress thường xuất hiện khi gọi meta/term/user meta bên trong vòng lặp. Giải pháp đơn giản và hiệu quả là “eager loading” bằng cách prime cache cho toàn bộ danh sách trước khi hiển thị. Cách này dễ áp dụng, an toàn và mang lại cải thiện tức thì cho tốc độ tải trang.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...