必须用 $wpdb->prepare() 处理所有含用户输入的 sql,否则易遭注入;占位符仅限 %d、%f、%s,like 用 %%,in 列表需动态生成占位符,查询方法依返回结构选择,且须全局声明 $wpdb 并正确处理前缀与自定义表。

必须用 $wpdb->prepare() 包裹所有含用户输入或动态变量的 SQL,否则大概率被注入——这不是建议,是 WordPress 官方强制要求的安全底线。
为什么不能直接拼接 SQL 字符串
直接用 "SELECT * FROM $wpdb->posts WHERE post_status = '$status'" 这类写法,一旦 $status 来自 $_GET 或表单提交,攻击者就能传入 ' OR 1=1 -- 让整张表被拖走。WordPress 不会帮你过滤,它只信任你主动做安全处理的结果。
-
esc_sql()已被官方标记为“不推荐”,仅用于极少数无法用prepare()的边缘场景(比如动态表名) -
addslashes()或mysql_real_escape_string()在现代 PHP 环境下完全失效,且与$wpdb的连接方式不兼容 - 硬编码值(如
"WHERE post_type = 'post'")看似安全,但只要混入一个变量,整条语句就失守
正确使用 $wpdb->prepare() 的三个硬规则
$wpdb->prepare() 不是可选项,是唯一受支持的参数化查询方式。它按顺序绑定变量,不依赖引号、不拼接字符串。
- 占位符只能是
%d(整数)、%f(浮点)、%s(字符串),不可加引号、括号或空格,例如"%s"错,%s对 - LIKE 查询中百分号要写成
%%,比如"WHERE post_title LIKE %s"+"%{$keyword}%"→ 应写成"WHERE post_title LIKE %s"+"%{$keyword}%%" - IN 列表不能用单个
%s代入数组,必须展开为多个%d或%s占位符,再用array_merge()拼接参数
示例:
$ids = [1, 5, 12];
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$query = $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID IN ($placeholders)", $ids);
get_var() / get_row() / get_results() 选哪个?看返回结构
选错方法会导致前端输出乱码、NULL、对象转字符串失败,甚至暴露调试信息。
- 只取一个数字或字符串(如
COUNT(*)、SUM(price))→ 用$wpdb->get_var(),它只返回第一行第一列 - 只取一行数据(如查某用户邮箱)→ 用
$wpdb->get_row(),默认返回对象,加ARRAY_A可得关联数组 - 要遍历多行(如列表页查 10 篇文章)→ 用
$wpdb->get_results(),别用get_var()然后 foreach,那会报错 - 所有输出到 HTML 的结果,必须过
esc_html()或esc_attr(),get_var()返回的 NULL 不会自动转成空字符串
容易被忽略的四个执行细节
这些点不写进文档,但线上出问题八成栽在这儿。
- 每次调用前必须声明
global $wpdb;,漏写会导致Fatal error: Call to a member function prepare() on null - 自定义表未注册到
$wpdb对象时(比如表名不是$wpdb->prefix . 'my_table'),需手动挂载:$wpdb->my_table = $wpdb->prefix . 'my_table'; -
$wpdb->query()执行 INSERT/UPDATE/DELETE 后,返回的是影响行数(int),不是布尔值;失败时返回false,要用is_int()判断而非=== true - 多站点环境下,
$wpdb->prefix是动态的,硬写wp_前缀会导致子站查询失败,必须用{$wpdb->prefix}my_table











