简体   繁体   English

Laravel参数绑定导致偶尔的MySQL一般错误

[英]Laravel parameter binding causing occasional MySQL general error

I have an array of integers that need to be inserted as a batch of rows. 我有一个整数数组,需要作为一批行插入。 Each row needs some other data. 每行还需要其他一些数据。

$ids = [1,2]
$thing = 1
$now = Carbon::now(); // This is just a timestamp.
$binding_values = trim(str_repeat("({$thing}, ?, '{$now}'),", count($ids)), ',');

The string $binding_values looks like this: 字符串$binding_values如下所示:

"(1, ?, '2019-01-01 00:00:00'), (1, ?, '2019-01-01 00:00:00')" “(1,?,'2019-01-01 00:00:00'),(1,?,'2019-01-01 00:00:00')“

Then I prepare my query string and bind the parameters to it. 然后,我准备查询字符串并将参数绑定到该字符串。 The IGNORE is used because I have a composite unique index on the table. 使用IGNORE是因为我在表上有一个复合唯一索引。 It doesn't seem relevant to the problem though so I've left the details out. 尽管它似乎与问题无关,所以我省略了细节。

DB::insert("
    INSERT IGNORE INTO table (thing, id, created_at)
    VALUES {$binding_values}
", $ids);

This works almost all the time but every now and then I get an error SQLSTATE[HY000]: General error: 2031 . 几乎一直都有效,但是时不时地出现错误SQLSTATE[HY000]: General error: 2031

Is the way I'm doing this parameter binding some kind of anti-pattern with Laravel? 我执行此参数的方式是否与Laravel绑定某种反模式? What might the source of this error be? 该错误的根源可能是什么?

EDIT: Because there is no risk of injection in this method and there is no chance that this method would be extended to a use case with a risk of injection, I've modified it to bake in all the parameters and skip parameter binding. 编辑:因为此方法没有注入风险,并且没有机会将此方法扩展到有注入风险的用例,所以我对其进行了修改,使其可以烘焙所有参数并跳过参数绑定。 I haven't seen any errors so far. 到目前为止,我还没有看到任何错误。

I would still like to know what might cause this behaviour so I can manage it better in the future. 我仍然想知道是什么原因导致这种行为,以便将来更好地进行管理。 I'd be grateful for any insight. 我将不胜感激。

I don't see a big issue with your query other than baking parameters into the query, which is vulnerable to SQL injection. 除了将参数烘焙到查询中之外,我没有看到您的查询有什么大问题,因为该参数容易受到SQL注入的攻击。

From what I can see, your problem is that you need INSERT ÌGNORE INTO which is not supported out of the box by Laravel. 据我INSERT ÌGNORE INTO ,您的问题是您需要INSERT ÌGNORE INTO ,而INSERT ÌGNORE INTO不支持该功能。 Have you thought about using a third-party package like staudenmeir/laravel-upsert ? 您是否考虑过使用staudenmeir / laravel-upsert这样的第三方软件包?

An alternative could be to wrap your query in a transaction and select existing entries first, giving you the chance to not insert them a second time: 另一种选择是将查询包装在事务中,然后首先选择现有条目,这使您有机会第二次不插入它们:

$ids   = [1, 2];
$thing = 1;
$time  = now();

DB::transaction(function () use ($ids, $thing, $time) {
    $existing = DB::table('some_table')->whereIn('id', $ids)->pluck('id')->toArray();
    $toInsert = array_diff($ids, $existing);

    $dataToInsert = array_map(function ($id) use ($thing, $time) {
        return [
            'id' => $id,
            'thing' => $thing,
            'created_at' => $time
        ];
    }, $toInsert);

    DB::table('some_table')->insert($dataToInsert);
});

This way you will only insert what is not present yet, but you will also stay within the framework capabilities of the query builder. 这样,您将只插入尚不存在的内容,但也将保留在查询生成器的框架功能之内。 Only downside is that it will be slightly slower due to a second roundtrip to the database. 唯一的缺点是,由于第二次往返数据库,因此它将稍微慢一些。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM