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() và 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ọiget_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