之前在做tp框架批量插入测试的时候有出现过一个报错,1390 Prepared statement contains too many placeholders
字面意思就是预处理语句包含了太多占位符
我们来看一下框架源码
/**
* 执行语句
* @access public
* @param string $sql sql指令
* @param array $bind 参数绑定
* @param Query $query 查询对象
* @return int
* @throws BindParamException
* @throws \PDOException
* @throws \Exception
* @throws \Throwable
*/
public function execute($sql, $bind = [], Query $query = null)
{
$this->initConnect(true);
if (!$this->linkID) {
return false;
}
// 记录SQL语句
$this->queryStr = $sql;
$this->bind = $bind;
Db::$executeTimes++;
try {
// 调试开始
$this->debug(true);
// 预处理
$this->PDOStatement = $this->linkID->prepare($sql);
// 是否为存储过程调用
$procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
// 参数绑定
if ($procedure) {
$this->bindParam($bind);
} else {
$this->bindValue($bind);
}
// 执行语句
$this->PDOStatement->execute();
// 调试结束
$this->debug(false, '', true);
if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) {
$query->readMaster();
}
$this->numRows = $this->PDOStatement->rowCount();
return $this->numRows;
} catch (\PDOException $e) {
if ($this->isBreak($e)) {
return $this->close()->execute($sql, $bind, $query);
}
throw new PDOException($e, $this->config, $this->getLastsql());
} catch (\Throwable $e) {
if ($this->isBreak($e)) {
return $this->close()->execute($sql, $bind, $query);
}
throw $e;
} catch (\Exception $e) {
if ($this->isBreak($e)) {
return $this->close()->execute($sql, $bind, $query);
}
throw $e;
}
}
可以看到框架使用了PDO的预处理方法来执行MySQL的预处理,那问题就出在这里了
MySQL在进行预处理的时候需要先执行预处理语句,此时如果是带参数的查询就需要进行参数绑定,也就是需要占位符来对实际数据进行占位,后面执行参数绑定来执行实际的插入
示例:
SQL语句:
prepare sql_2 from "select * from pdo where id > ? && age > ?";
设置两个变量
set @id=2;
set @age=30;
执行
execute sql_2 using @id,@age;
删除预处理
drop prepare sql_2;
以上大概就是MySQL预处理的一个过程,
PHP中同样可以使用PDO来执行预处理
示例:
<?php
/**
* 利用PDO对象实现预处理操作
*/
echo "<meta charset=utf-8>";
//PDO类的实例化
//设置数据源相关参数
$dbms = 'mysql';
$host = 'localhost';
$port = '3306';
$dbname = 'test';
$charset = 'utf8';
$dsn = "$dbms:host=$host;port=$port;dbname=$dbname;charset=$charset";
//设置用户名密码
$user = 'root';
$pwd = '123456';
//实例化PDO类
$pdo = new PDO($dsn,$user,$pwd);
//预处理
//发送预处理
$sql = "insert into pdo values(null, ?, ?)";
$stmt = $pdo->prepare($sql);
//绑定参数
// 先把每个参数保存到一个变量中
$name = 'qqqq';
$age = '45';
//再使用bindParam方法绑定参数变量
$stmt->bindParam(1,$name); //1代表第一个数据占位符
$stmt->bindParam(2,$age);
//执行预处理语句
$res = $stmt->execute();
if ($res) {
echo "预处理语句执行成功!";
}else{
echo "预处理语句执行失败!";
}
以上就是关于MySQL预处理的内容
那回到我们最初的问题,为什么会产生 Prepared statement contains too many placeholders 这个错误呢?
我们来看一下之前使用insertAll()方法是实际执行的SQL语句,为缩短执行时间我们调整一下插入的数据量,减为20条,并且打印一下执行的SQL语句
代码:
<?php
namespace app\index\controller;
use think\Db;
class Index
{
public function test4()
{
//防止超300s 500错误
set_time_limit(0);
ini_set('memory_limit','1024M');
$t1 = microtime(true);
//插入20条数据
for ($i=1; $i<=20; $i++){
$data[$i] = ['value' => uniqid().$i];
}
//程序运行时间
$t2 = microtime(true);
echo '循环耗时:'.round($t2-$t1,3).'秒<br>';
Db::name('myisam')->insertAll($data);
//打印一下最后执行的SQL语句
echo Db::name('myisam')->getLastSql().'<br>';
$t3 = microtime(true);
echo '插入耗时:'.round($t3-$t2,3).'秒<br>';
echo '内存消耗:'.round(memory_get_usage()/1048576,2)." M<br/>";
}
}
结果:
我们看到是一条拼接后的SQL语句
实际在thinkPHP框架中,也是按照拼接后的SQL语句去执行预处理的,我们来看一下框架里的预处理语句
可以看到,框架使用的是 ThinkBind 这样一个变量名作为占位符,并且按照实际的语句进行占位,这样就导致预处理语句中会存在200000或100000个占位符
而MySQL对于预处理语句中的占位符数量是有数量限制的,据说是65535,也就是插入的 ,列数乘条数 总和不能超过65535,因为我没有在MySQL的配置里面找到这一项,不过我自己用插入65535条和65536条分别试了一下,65535应该是没错的
这样,Prepared statement contains too many placeholders 的问题就解决了,当然,在实际中,使用这么长的插入语句也不是好的行为,这里只是对于问题本身进行讨论
本文为原创文章,转载无需和本人联系,但请注明来自 飞行猿博客 https://www.hzfblog.com
最新评论