PHP PDO用bindParam仍报SQLSTATE[HY093]参数数量错误的解决咨询
你遇到的这个问题其实挺常见的——虽然你用了bindParam而不是直接给execute传数组,但很多PDO驱动(比如MySQL的默认驱动)并不支持在预处理语句中重复使用同名参数标记,不管你用哪种绑定方式,都会触发Invalid parameter number错误。
问题根源
PDO的原生预处理(也就是关闭EMULATE_PREPARES时)会把预处理语句发送给数据库服务器解析,这时数据库会把每个:user和:offset都当成独立的占位符。比如你的第一条SQL里:user出现了两次,:offset一次,总共需要3个参数,但你只绑定了2个(user和offset各一次),自然就参数数量不匹配了。
之前你用同一个query函数没报错,大概率是当时的SQL没有重复参数,或者你的PDO连接默认开启了模拟预处理。
解决方案
下面给你几个实用的解决办法,按推荐程度排序:
1. 开启PDO的模拟预处理(最简便)
当开启PDO::ATTR_EMULATE_PREPARES时,PDO会在客户端完成参数替换,而不是把预处理语句发给数据库。这时重复的参数标记会被正确替换多次,不会有参数数量问题。
你只需要在初始化PDO连接的时候添加这个属性:
// 假设你是这样初始化PDO的,修改成下面的样子 $this->_pdo = new PDO( 'mysql:host=your_host;dbname=your_db;charset=utf8mb4', 'username', 'password', [ PDO::ATTR_EMULATE_PREPARES => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 推荐开启错误模式,方便调试 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ // 可选,和你的fetchAll保持一致 ] );
这个方法不需要修改你的SQL和query函数,直接解决问题。
2. 修改SQL避免重复参数
如果你不想开启模拟预处理,可以调整SQL语句,把重复的参数合并成一次引用。比如第一条SQL可以改成:
SELECT upload_id FROM uploads WHERE upload_artist IN ( SELECT following FROM followers WHERE follower = :user UNION ALL SELECT :user -- 把OR的条件合并到IN里 ) AND upload_private = 0 AND upload_published = 1 ORDER BY upload_date desc LIMIT :offset, 7
这样SQL里:user只出现一次,:offset一次,参数数量就匹配了。第二条SQL同理,把OR repost_user = :user合并到IN子查询里。
3. 为每个重复参数单独绑定(繁琐但原生)
如果上面两种方法都不想用,你可以在$params里为每个重复的参数添加条目,比如第一条SQL里:user出现两次,就把$params改成:
$params = [ "user" => $user_id, "user2" => $user_id, "offset" => $offset ];
同时修改SQL里的第二个:user为:user2:
SELECT upload_id FROM uploads WHERE upload_artist IN (SELECT following FROM followers WHERE follower = :user) OR upload_artist = :user2 AND upload_private = 0 AND upload_published = 1 ORDER BY upload_date desc LIMIT :offset, 7
但这种方法需要修改SQL和参数数组,比较繁琐,不推荐。
验证修改
修改完PDO属性或者SQL后,再运行你的代码,应该就不会再出现HY093错误了。如果还是有问题,可以检查一下PDO连接的属性是否正确设置,或者SQL里的参数标记是否和绑定的参数名完全匹配。
内容的提问来源于stack exchange,提问作者Grover




