MySQL 1390 Prepared statement contains too many placeholders 问题分析

之前在做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 的问题就解决了,当然,在实际中,使用这么长的插入语句也不是好的行为,这里只是对于问题本身进行讨论

飞行猿博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论