必须用 $wpdb->prepare() 处理所有含用户输入的 sql,因直接拼接易遭注入;占位符仅限 %d/%f/%s,like 用 %%,in 需动态生成占位符;动态表名列名须用 $wpdb->prefix 拼接;查询前需 global $wpdb;输出须 esc_html()。

必须用 $wpdb->prepare() 包裹所有含用户输入或动态变量的 SQL,否则大概率被注入——这不是建议,是 WordPress 官方强制要求的安全底线。
为什么直接拼接字符串会出事
像 "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'")看似安全,但只要混入一个变量,整条语句就失守 - 即使只查一条记录、只改一个字段,只要 SQL 里有变量,就必须过
prepare()
$wpdb->prepare() 的占位符怎么写才对
占位符只能是 %d(整数)、%f(浮点)、%s(字符串),不能加引号、括号或空格:"%s" 错,%s 对。
LIKE 查询中的百分号要写成 %%:想查包含关键词的标题,不能写 "WHERE post_title LIKE '%{$keyword}%'",而要写 "WHERE post_title LIKE %s" + "%{$keyword}%%"(注意结尾是两个 %)。
IN 列表不能用单个 %s 代入数组,必须展开占位符:
安全的随机密码生成器。支持自定义长度、字符类型(大写/小写字母、数字、特殊符号),排除相似字符,批量生成。纯 Python 标准库,无需 API 密钥。
$ids = [1, 5, 12];
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$query = $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID IN ($placeholders)", $ids);
- 动态表名、列名不能用占位符,需用
$wpdb->prefix或$wpdb->users等预定义属性拼接 - 不要在占位符前后手动加引号,
$wpdb->prepare()会自动处理
选 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() —— 即使是 get_var() 返回的字符串也不能直接 echo。
global $wpdb; 忘了这句就全白干
在函数、钩子回调、短代码里用 $wpdb,必须先写 global $wpdb;,否则会报 Call to a member function query() on null。
插件主文件或独立脚本中若未加载 WordPress 环境(比如直接访问 .php 文件),$wpdb 也为空,此时需引入 wp-load.php 或改用 AJAX/REST API 方式调用。
前缀问题常被忽略:不要硬写 wp_users,要用 $wpdb->users 或 $wpdb->prefix . 'users',否则换环境就查不到数据。










