最常见原因是命名占位符被单引号包裹,导致pdo无法识别为占位符;其他原因包括混用两种占位符、重名绑定、模拟预处理开启、like中错误嵌入通配符及非法用于表名列名等结构。

为什么 PDO::prepare() 报 “Invalid parameter number”?
最常见原因是命名占位符被单引号包裹,导致 PDO 完全识别不到它。比如写成:WHERE name = ':name' —— 这里的 :name 在引号里,就是普通字符串,不是占位符;PDO 解析后发现 SQL 里根本没占位符,但你却调了 bindValue(':name', $val),自然报错。
其他诱因包括:
- SQL 中混用命名占位符(
:id)和问号占位符(?),PDO 不允许两者共存 - 同一语句中重复使用相同命名占位符,且
PDO::ATTR_EMULATE_PREPARES => true(默认可能开启),模拟模式下不支持重名 - SQL 函数里的格式字符串(如
DATE_FORMAT(sched_time, '%H:%i'))被误以为要转义冒号——其实不用,只要它在单引号内,PDO 就不会当占位符处理
PDO::ATTR_EMULATE_PREPARES => false 必须关吗?
必须关。不关就等于没防注入。
开启模拟预处理时,PDO 会在 PHP 层把占位符替换成带引号的值再发给 MySQL,相当于手动拼接 + addslashes(),遇到宽字节、多字节编码边界或特殊字符仍可能被绕过。而设为 false 后,MySQL 原生解析预处理语句,参数与 SQL 结构严格分离,这才是真正的隔离。
检查方式:var_dump($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES)); 返回 bool(false) 才算生效。
注意:某些旧版 MySQL(如 5.0 以下)或 MariaDB 特定版本不完全支持原生预处理,但 PHP 8.3 + MySQL 5.7+ 场景下,false 是底线要求。
LIKE 查询怎么安全加通配符?
不能把 % 写进 SQL 字符串里,比如 WHERE name LIKE ?% 或 "%{$_GET['q']}%" —— 前者语法非法,后者直接拼接,彻底失效。
正确三步走:
- 先清理用户输入中的 SQL 通配符:
$q = str_replace(['%', '_'], ['\%', '\_'], $_GET['q'] ?? ''); - 再在 PHP 层拼前后缀:
$search = '%' . $q . '%'; - 最后绑定,并在 SQL 中声明
ESCAPE:WHERE name LIKE ? ESCAPE '\',然后$stmt->execute([$search]);
漏掉 ESCAPE '\',用户传入 admin\_test 就可能匹配到 admin_test(下划线被当通配符),白清了也白清。
表名、字段名、ORDER BY 能用占位符吗?
不能。占位符只接受数据值,不接受 SQL 结构。
比如这些全是错的:
"SELECT * FROM :table""ORDER BY :sort_field""UNION SELECT * FROM :other_table"
这类结构必须走白名单校验。例如排序字段只允许 ['id', 'name', 'created_at'],用户传 sort=name 才放行;传 sort=id; DROP TABLE users 直接拒掉。别试图用 addslashes() 或正则“过滤”,结构控制必须是明确的枚举或严格规则。
最容易被忽略的是动态列名场景,比如导出功能要按用户选的字段查,这时哪怕只拼一个字段名,也得从预设列表里取,而不是直接 $_GET['fields'] 过来就用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











