在 MySQL 中导入 50K+ 记录给出一般错误:1390 Prepared statement contains too many placeholders

Posted

技术标签:

【中文标题】在 MySQL 中导入 50K+ 记录给出一般错误:1390 Prepared statement contains too many placeholders【英文标题】:Import of 50K+ Records in MySQL Gives General error: 1390 Prepared statement contains too many placeholders 【发布时间】:2013-08-08 16:13:16 【问题描述】:

有没有人遇到过这个错误:一般错误:1390 Prepared statement contains too many placeholders

我刚刚通过 SequelPro 导入了超过 50,000 条记录,现在当我在我的视图中查看这些记录时 (Laravel 4) 我收到一般错误:1390 Prepared statement contains too many placeholders。

我的 AdminNotesController.php 文件中的以下 index() 方法是生成查询和呈现视图的内容。

public function index()

    $created_at_value = Input::get('created_at_value');
    $note_types_value = Input::get('note_types_value');
    $contact_names_value = Input::get('contact_names_value');
    $user_names_value = Input::get('user_names_value');
    $account_managers_value = Input::get('account_managers_value');

    if (is_null($created_at_value)) $created_at_value = DB::table('notes')->lists('created_at');
    if (is_null($note_types_value)) $note_types_value = DB::table('note_types')->lists('type');
    if (is_null($contact_names_value)) $contact_names_value = DB::table('contacts')->select(DB::raw('CONCAT(first_name," ",last_name) as cname'))->lists('cname');
    if (is_null($user_names_value)) $user_names_value = DB::table('users')->select(DB::raw('CONCAT(first_name," ",last_name) as uname'))->lists('uname');

    // In the view, there is a dropdown box, that allows the user to select the amount of records to show per page. Retrieve that value or set a default.
    $perPage = Input::get('perPage', 10);

    // This code retrieves the order from the session that has been selected by the user by clicking on a table column title. The value is placed in the session via the getOrder() method and is used later in the Eloquent query and joins.
    $order = Session::get('account.order', 'company_name.asc');
    $order = explode('.', $order);

    $notes_query = Note::leftJoin('note_types', 'note_types.id', '=', 'notes.note_type_id')
        ->leftJoin('users', 'users.id', '=', 'notes.user_id')
        ->leftJoin('contacts', 'contacts.id', '=', 'notes.contact_id')
        ->orderBy($order[0], $order[1])
        ->select(array('notes.*', DB::raw('notes.id as nid')));

    if (!empty($created_at_value)) $notes_query = $notes_query->whereIn('notes.created_at', $created_at_value);

    $notes = $notes_query->whereIn('note_types.type', $note_types_value)
        ->whereIn(DB::raw('CONCAT(contacts.first_name," ",contacts.last_name)'), $contact_names_value)
        ->whereIn(DB::raw('CONCAT(users.first_name," ",users.last_name)'), $user_names_value)
        ->paginate($perPage)->appends(array('created_at_value' => Input::get('created_at_value'), 'note_types_value' => Input::get('note_types_value'), 'contact_names_value' => Input::get('contact_names_value'), 'user_names_value' => Input::get('user_names_value')));

    $notes_trash = Note::onlyTrashed()
        ->leftJoin('note_types', 'note_types.id', '=', 'notes.note_type_id')
        ->leftJoin('users', 'users.id', '=', 'notes.user_id')
        ->leftJoin('contacts', 'contacts.id', '=', 'notes.contact_id')
        ->orderBy($order[0], $order[1])
        ->select(array('notes.*', DB::raw('notes.id as nid')))
        ->get();

    $this->layout->content = View::make('admin.notes.index', array(
        'notes'             => $notes,
        'created_at'        => DB::table('notes')->lists('created_at', 'created_at'),
        'note_types'        => DB::table('note_types')->lists('type', 'type'),
        'contacts'          => DB::table('contacts')->select(DB::raw('CONCAT(first_name," ",last_name) as cname'))->lists('cname', 'cname'),
        'accounts'          => Account::lists('company_name', 'company_name'),
        'users'             => DB::table('users')->select(DB::raw('CONCAT(first_name," ",last_name) as uname'))->lists('uname', 'uname'),
        'notes_trash'       => $notes_trash,
        'perPage'           => $perPage
    ));

任何建议将不胜感激。谢谢。

【问题讨论】:

如果您导入假设 100 条记录。可以吗? 基于错误(我什至不会尝试查看该 HUUUGE 代码段),您似乎正在准备一个带有 X 占位符和 Y 值的查询,其中 Y @TheDisintegrator 是的,它工作正常。 @CarlosCampderros 我不确定你的意思? 如果你明智地导入块,它会起作用。这是一个简单的例子 foreach (array_chunk($data,1000) as $t) DB::table('table-name')->insert($t); 【参考方案1】:

使用array_chunk 函数解决了这个问题。

解决方法如下:

foreach (array_chunk($data,1000) as $t)  

     DB::table('table_name')->insert($t); 


        

【讨论】:

这个答案很好 这 1000 个数字可以是动态的,因为占位符限制为“65536”。【参考方案2】:

你可以用 array_chunk 函数来做,像这样:

foreach(array_chunk($data, 1000) as $key => $smallerArray) 
        foreach ($smallerArray as $index => $value) 
                $temp[$index] = $value
        
        DB::table('table_name')->insert(temp);
    

【讨论】:

【参考方案3】:

我对上述问题的修复: 在我这边,当我遇到这个错误时,我通过将批量插入块大小从 1000 减少到 800 来修复它,它对我有用。 实际上,我的表格中有太多字段,其中大多数都包含大小的详细描述,就像完整的页面文本一样。当我去那里批量插入时,服务导致崩溃并通过上述错误。

【讨论】:

【参考方案4】:

使用 Laravel 模型,在几秒钟内将所有 11000 条记录从 sqlite 数据库复制到 mysql 数据库。将数据数组分块为 500 条记录:

public function handle(): void

    $smodel = new Src_model();
    $smodel->setTable($this->argument('fromtable'));
    $smodel->setConnection('default'); // sqlite database
    $src = $smodel::all()->toArray();

    $dmodel = new Dst_model();
    $dmodel->setTable($this->argument('totable'));
    $dmodel->timestamps = false;
    $stack = $dmodel->getFields();
    $fields = array_shift($stack);

    $condb = DB::connection('mysql');
    $condb->beginTransaction();

    $dmodel::query()->truncate();
    $dmodel->fillable($stack);
    $srcarr=array_chunk($src,500);
    $isOK=true;
    foreach($srcarr as $item) 
        if (!$dmodel->query()->insert($item)) $isOK=false;
    
    if ($isOK) 
        $this->notify("Przenieśliśmy tabelę z tabeli : $this->argument('fromtable') do tabeli: $this->argument('totable')", 'Będzie świeża jak nigdy!');
        $condb->commit();
    
    else $condb->rollBack();


【讨论】:

【参考方案5】:

我认为每个查询的占位符数量限制为 65536 个(至少在旧 mysql 版本中)。

我真的无法辨别这段代码生成了什么。但如果它是一个巨大的查询,那就是你的问题。

您应该为每条记录生成一个查询以导入并将其放入事务中。

【讨论】:

对否决票的解释会很好。我很确定问题在于 OP 达到了占位符限制 我认为这就是你被否决的原因:“你应该为每条记录生成一个查询” “你应该为每条记录生成一个查询”在我的情况下很慢。每条记录一个查询比用完所有 65536 个占位符要慢。在您的情况下,Sql 解析和事务将花费您的 CPU。而且网络 rrt 也会花费很多时间。【参考方案6】:

MariaDB 5.5 中有限制 65,535 (2^16-1) 个占位符,应该与 具有相同的行为>MySQL 5.5

不确定是否相关,我使用 MySQLi / MySQLND 在 PHP 5.5.12 上对其进行了测试。

【讨论】:

这就是我要找的答案!【参考方案7】:

此错误在满足两个以下条件时发生:

    您使用的是MySQL Native Driver (mysqlnd),而不是 MySQL 客户端库 (libmysqlclient) 您没有模仿准备。

如果您更改其中任何一个因素,则不会发生此错误。但是请记住,出于性能或安全问题,建议同时执行这两项操作,因此除了您遇到的一次性或临时问题之外,我不会推荐此解决方案。为防止发生此错误,修复方法很简单:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

【讨论】:

谢谢@mike,这就是我一直在寻找的答案 @Sadee 数据库配置选项的存储位置和方式以及打开连接的方式对于每个应用程序都不同。 @Mike 是的,你说得对。并通过这种方式(在 config/database.php 文件中设置选项)在我的应用程序中生成其他问题(例如:在查询中设置参数)。所以,我在这个大查询之前删除了它并设置了这个属性。对此有何建议?我删除了我之前的评论。 @Sadee 你可能会更好地开始一个新的问题,因为它有点离题了。包括您的代码和您收到的任何错误。 @Mike:将属性更改为“true”后,error: 1390 Prepared statement contains too many placeholders 消失了。但我观察到 INSERT 的速度会慢得多。插入包含 7 个列的 25.000 行需要 106 秒。将行数减少到 9200 行后,插入只需要大约 10 行。 15 秒。因此,我会说应该注意占位符的数量。【参考方案8】:

虽然我认为@The Disintegrator 关于占位符受到限制是正确的。我不会为每条记录运行 1 个查询。

我有一个运行良好的查询,直到我再添加一列,现在我有 72k 占位符,我收到了这个错误。但是,这 72k 由 9000 行和 8 列组成。一次运行此查询 1 条记录需要几天时间。 (我正在尝试将 AdWords 数据导入数据库,如果我一次导入 1 条记录,那么导入一天的数据实际上需要超过 24 小时。我先尝试过。)

我会推荐一些 hack。首先要么动态确定您想要允许的最大占位符数量 - 即 60k 是安全的。使用此数字根据列数确定一次可以导入/返回的完整记录数。为您的查询创建完整的数据数组。使用 array_chunk 和 foreach 循环以最少的查询次数获取您想要的所有内容。像这样:

$maxRecords = 1000;
$sql = 'SELECT * FROM ...';
$qMarks = array_fill(0, $maxInsert, '(?, ...)');
$tmp = $sql . $implode(', ', $qMarks);
foreach (array_chunk($data, $maxRecords) AS $junk=>$dataArray) 
  if (count($dataArray) < $maxRecords))  break; 

  // Do your PDO stuff here using $tmp as you SQL statement with all those placeholders - the ?s


// Now insert all the leftovers with basically the same code as above except accounting for
// the fact that you have fewer than $maxRecords now.

【讨论】:

以上是关于在 MySQL 中导入 50K+ 记录给出一般错误:1390 Prepared statement contains too many placeholders的主要内容,如果未能解决你的问题,请参考以下文章

如何在 phpmyadmin MySql 中导入记事本 .txt 文件

Navicat向mysql数据库中导入sql文件常见报错问题总结及解决办法

PhpOffice\PhpSpreadsheet 从 excel 错误字符集中导入 mysql

在 webpack 中导入 html 文件时找不到模块错误

在 Maya 中导入模型时出现错误

如何使用 php 在 mysql 数据库中导入 .sql 文件