Best Practices khi dùng $wpdb trong WordPress

$wpdb là class toàn cục của WordPress cho phép bạn chạy truy vấn SQL trực tiếp với MySQL. Nó cực kỳ mạnh và linh hoạt, nhưng nếu dùng không đúng có thể gây ra lỗi bảo mật hoặc làm website chậm đi. Bài viết này sẽ chia sẻ các best practices khi sử dụng $wpdb để vừa tận dụng được hiệu năng, vừa đảm bảo an toàn.

Best Practices khi dùng $wpdb trong WordPress

Khi nào nên dùng $wpdb?

Hãy ưu tiên WP_Query hoặc API có sẵn (như get_posts(), get_users()) vì chúng được cache và hỗ trợ hook. Chỉ dùng $wpdb khi:

  • Bạn cần query phức tạp, có nhiều JOIN hoặc tính toán.
  • Bạn cần thống kê, aggregate (COUNT, SUM, AVG…).
  • API sẵn có của WordPress không đáp ứng được nhu cầu.

Sử dụng $wpdb->prepare() để chống SQL Injection

Tuyệt đối không được nối chuỗi trực tiếp từ input của user vào query. Luôn dùng $wpdb->prepare() để binding biến an toàn.

<?php
global $wpdb;

// Sai: dễ bị SQL injection
$sql = "SELECT * FROM {$wpdb->users} WHERE user_login = '" . $_GET['username'] . "'";
$wpdb->get_results($sql);

// Đúng: dùng prepare để escape dữ liệu
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->users} WHERE user_login = %s",
    $_GET['username']
);
$results = $wpdb->get_results($sql);

Các phương thức quan trọng trong $wpdb

  • get_results(): Trả về nhiều dòng (array of objects).
  • get_row(): Trả về một dòng duy nhất.
  • get_var(): Trả về một giá trị duy nhất (thường dùng cho COUNT hoặc SUM).
  • get_col(): Trả về một cột dữ liệu.
  • insert(), update(), delete(): Helper để thao tác CRUD.

Tối ưu hiệu năng với $wpdb

Khi dùng $wpdb, bạn chịu trách nhiệm tối ưu query. Một số tips:

  • SELECT cụ thể cột: Không dùng SELECT *, hãy chỉ lấy cột cần thiết.
  • LIMIT: Luôn giới hạn số dòng trả về, tránh query quá nhiều dữ liệu.
  • Sử dụng index: Kiểm tra xem cột bạn lọc/ORDER BY có index chưa.
  • Cache kết quả: Dùng wp_cache_set() hoặc Transients API để lưu kết quả query tốn kém.
  • Tránh query trong vòng lặp: Gộp dữ liệu lại và query một lần.

Ví dụ: Lấy top 10 user có nhiều bài viết nhất

<?php
global $wpdb;

$sql = "
    SELECT post_author, COUNT(ID) as total_posts
    FROM {$wpdb->posts}
    WHERE post_type = %s AND post_status = %s
    GROUP BY post_author
    ORDER BY total_posts DESC
    LIMIT 10
";

$results = $wpdb->get_results(
    $wpdb->prepare($sql, 'post', 'publish')
);

foreach ( $results as $row ) {
    $user = get_userdata($row->post_author);
    echo '<p>' . esc_html($user->display_name) . ': ' . intval($row->total_posts) . ' bài viết</p>';
}

Ví dụ: Insert dữ liệu an toàn

<?php
global $wpdb;
$wpdb->insert(
    $wpdb->prefix . 'custom_table',
    [
        'user_id'    => get_current_user_id(),
        'created_at' => current_time('mysql'),
        'score'      => 95
    ],
    [ '%d', '%s', '%d' ] // định nghĩa kiểu dữ liệu
);

Bonus: 10 mẫu truy vấn $wpdb “thực chiến”

Các ví dụ dưới đây đều dùng $wpdb->prepare() để an toàn, tối ưu theo từng tình huống phổ biến.

1. Đếm tổng bài viết theo trạng thái nhanh gọn

<?php
global $wpdb;
$total_published = (int) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(1) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
        'post', 'publish'
    )
);

2. Lấy danh sách ID bài có meta cụ thể (dùng IN và prepare động)

<?php
global $wpdb;
$ids = [12, 34, 56];
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$sql = $wpdb->prepare(
    "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN ($placeholders)",
    array_merge(['chap_unlock_vcoin'], $ids)
);
$post_ids = $wpdb->get_col($sql);

3. Join usermeta: tìm user bật thông báo

<?php
global $wpdb;
$sql = $wpdb->prepare(
    "SELECT u.ID, u.user_email
     FROM {$wpdb->users} u
     INNER JOIN {$wpdb->usermeta} um ON um.user_id = u.ID
     WHERE um.meta_key = %s AND um.meta_value IN ('1','yes','enabled')
     LIMIT %d",
    'init_manga_notifications_enabled', 500
);
$users = $wpdb->get_results($sql);

4. Lấy top category theo lượt xem (taxonomy join)

<?php
global $wpdb;
$sql = "
SELECT t.term_id, t.name, SUM(COALESCE(pm.meta_value,0)) AS views
FROM {$wpdb->terms} t
JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id AND tt.taxonomy = 'category'
JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
JOIN {$wpdb->posts} p ON p.ID = tr.object_id AND p.post_status = 'publish'
LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = 'chapter_view'
GROUP BY t.term_id
ORDER BY views DESC
LIMIT 10";
$rows = $wpdb->get_results($sql);

5. Thống kê điểm review trung bình theo category

<?php
global $wpdb;
$sql = $wpdb->prepare(
    "SELECT AVG(CAST(pm.meta_value AS DECIMAL(10,2))) AS avg_score
     FROM {$wpdb->posts} p
     JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s
     WHERE p.post_type = %s AND p.post_status = %s",
    'review_score', 'review', 'publish'
);
$avg_score = (float) $wpdb->get_var($sql);

6. Phân trang với OFFSET/LIMIT

<?php
global $wpdb;
$page = max(1, (int) ($_GET['page'] ?? 1));
$per_page = 50;
$offset = ($page - 1) * $per_page;

$sql = $wpdb->prepare(
    "SELECT ID, post_title
     FROM {$wpdb->posts}
     WHERE post_type = %s AND post_status = %s
     ORDER BY post_date DESC
     LIMIT %d OFFSET %d",
    'post', 'publish', $per_page, $offset
);
$items = $wpdb->get_results($sql);

7. Tìm kiếm có điều kiện với LIKE an toàn

<?php
global $wpdb;
$keyword = sanitize_text_field($_GET['s'] ?? '');
$like = '%' . $wpdb->esc_like($keyword) . '%';

$sql = $wpdb->prepare(
    "SELECT ID, post_title
     FROM {$wpdb->posts}
     WHERE post_type = %s AND post_status = %s
       AND (post_title LIKE %s OR post_content LIKE %s)
     LIMIT 20",
    'post', 'publish', $like, $like
);
$results = $wpdb->get_results($sql);

8. Cập nhật hàng loạt bằng CASE WHEN

<?php
global $wpdb;
$updates = [
    101 => 5,
    202 => 9,
    303 => 0,
];
$ids = array_keys($updates);
$placeholders = implode(',', array_fill(0, count($ids), '%d'));

$case = '';
$params = [];
foreach ($updates as $pid => $score) {
    $case   .= ' WHEN %d THEN %d';
    $params[] = $pid;
    $params[] = $score;
}

$sql = "
UPDATE {$wpdb->postmeta}
SET meta_value = CASE post_id $case ELSE meta_value END
WHERE meta_key = %s AND post_id IN ($placeholders)
";
$params[] = 'review_score';
$params   = array_merge($params, $ids);

$wpdb->query( $wpdb->prepare($sql, $params) );

9. Xóa transients đã hết hạn (dọn rác)

<?php
global $wpdb;
// Chú ý: wp_options có thể là InnoDB/auto-clean ở WP mới, nhưng thủ thuật này vẫn hữu ích trên site cũ
$now = time();
$wpdb->query(
    $wpdb->prepare(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE %s
           AND option_name NOT LIKE %s
           AND option_value < %d",
        $wpdb->esc_like('_transient_timeout_') . '%',
        $wpdb->esc_like('_site_transient_timeout_') . '%',
        $now
    )
);

10. “Upsert” nhẹ với ON DUPLICATE KEY (bảng custom)

<?php
global $wpdb;
// Bảng custom có unique key (user_id, post_id)
$table = $wpdb->prefix . 'manga_stats';
$sql = "
INSERT INTO $table (user_id, post_id, view_count, updated_at)
VALUES (%d, %d, %d, %s)
ON DUPLICATE KEY UPDATE
  view_count = view_count + VALUES(view_count),
  updated_at = VALUES(updated_at)
";
$wpdb->query( $wpdb->prepare($sql, get_current_user_id(), $post_id, 1, current_time('mysql')) );

Mẹo nhanh khi viết query

  • Dùng %d, %s, %f đúng kiểu; không ép kiểu linh tinh trong SQL.
  • Tạo placeholder động cho mệnh đề IN bằng implode(',', array_fill(...)) rồi prepare với mảng tham số.
  • Ưu tiên COUNT(1) và chọn cột cụ thể thay vì SELECT *.
  • Nhớ LIMIT/OFFSET, và thêm index phù hợp cho cột lọc/ORDER BY.
  • Nếu query nặng và lặp lại, cache kết quả bằng Transients hoặc object cache.

Kết luận

$wpdb là công cụ mạnh mẽ nhưng đi kèm trách nhiệm: bạn phải tự tối ưu và tự bảo mật. Luôn dùng $wpdb->prepare(), tránh query trong vòng lặp, và cache kết quả nếu query nặng. Khi dùng đúng, $wpdb có thể giúp bạn xây dựng những tính năng mạnh mẽ và hiệu năng cao trong WordPress.

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...