【问题标题】:Laravel Bulk UPDATELaravel 批量更新
【发布时间】:2014-11-25 20:34:51
【问题描述】:

我正在尝试更新一个包含 slug 值的表,其中每个记录都有随机 slug。

$vouchers = Voucher->get(); // assume 10K for example

foreach ($vouchers as $voucher) {
    $q .= "UPDATE vouchers set slug = '" . Str::random(32) . "' WHERE id = " . $voucher->id . ";";
}

DB::statement($q);

大约有 200 万条记录,因此我需要批量执行此操作。将其作为单独的记录进行处理花费的时间太长了。我似乎找不到批量运行它们的方法,比如以 10K 为一组。

尝试了->update()DB::statement 的一系列变体,但似乎无法成功。

【问题讨论】:

标签: laravel laravel-4 eloquent laravel-5


【解决方案1】:

我制作了一个批量更新功能以在我的 Laravel 项目中使用。对于任何想在 Laravel 中使用批量更新查询的人来说,它可能很有用。它的第一个参数是表名字符串,第二个是要更新行或多行的键名字符串,大多数情况下它将是“id”,第三个参数是以下格式的数据数组:

array(
    array(
        'id' => 1,
        'col_1_name' => 'col_1_val',
        'col_2_name' => 'col_2_val',
        //...
    ),
    array(
        'id' => 2,
        'col_1_name' => 'col_1_val',
        'col_2_name' => 'col_2_val',
        //...
    ),
    //...
);

该函数将返回受影响的行数。函数定义:

private function custom_batch_update(string $table_name = '', string $key = '', Array $update_arr = array()) {

    if(!$table_name || !$key || !$update_arr){
        return false;
    }

    $update_keys = array_keys($update_arr[0]);
    $update_keys_count = count($update_keys);

    for ($i = 0; $i < $update_keys_count; $i++) {
        $key_name = $update_keys[$i];
        if($key === $key_name){
            continue;
        }
        $when_{$key_name} = $key_name . ' = CASE';
    }

    $length = count($update_arr);
    $index = 0;
    $query_str = 'UPDATE ' . $table_name . ' SET ';
    $when_str = '';
    $where_str = ' WHERE ' . $key . ' IN(';

    while ($index < $length) {
        $when_str = " WHEN $key = '{$update_arr[$index][$key]}' THEN";
        $where_str .= "'{$update_arr[$index][$key]}',";
        for ($i = 0; $i < $update_keys_count; $i++) {
            $key_name = $update_keys[$i];
            if($key === $key_name){
                continue;
            }
            $when_{$key_name} .= $when_str . " '{$update_arr[$index][$key_name]}'";
        }
        $index++;
    }

    for ($i = 0; $i < $update_keys_count; $i++) {
        $key_name = $update_keys[$i];
        if($key === $key_name){
            continue;
        }
        $when_{$key_name} .= ' ELSE ' . $key_name . ' END, ';
        $query_str .= $when_{$key_name};
    }
    $query_str = rtrim($query_str, ', ');
    $where_str = rtrim($where_str, ',') . ')';
    $query_str .= $where_str;
    $affected = DB::update($query_str);

    return $affected;
}

它将像这样生成并执行查询字符串:

UPDATE table_name SET col_1_name = CASE 
WHEN id = '1' THEN 'col_1_value' 
WHEN id = '2' THEN 'col_1_value' 
ELSE col_1_name END, 
col_2_name = CASE 
WHEN id = '1' THEN 'col_2_value' 
WHEN id = '2' THEN 'col_2_value' 
ELSE col_2_name END 
WHERE id IN('1','2')

【讨论】:

  • 这很容易受到 MySQL 注入的攻击!
【解决方案2】:

如果有人像我一样登陆此页面,laravel 允许批量更新为:

$affectedRows = Voucher::where('id', '=', $voucher-&gt;id)-&gt;update(array('slug' =&gt; Str::random(32)));

请参阅http://laravel.com/docs/4.2/eloquent#insert-update-delete 下的“更新检索到的模型”

【讨论】:

  • 这仍然需要用户进行 200 万次数据库调用,而不是批量更新。
  • 我想你真正要找的是Voucher::whereIn('id', [...])-&gt;update([...])
  • 这被称为“批量更新”,因为它不会将模型加载到内存中,例如执行模型事件。
  • 不幸的是 whereIn()->update() 只允许你对多个模型执行一次更新,而不是并行的多个更新。
【解决方案3】:

我在CodeIgniter 中创建了用于多次更新的我的自定义函数,例如update_batch

只需将此函数放置在您的任何模型中,或者您可以创建辅助类并将此函数放置在该类中:

//test data
/*
$multipleData = array(
   array(
      'title' => 'My title' ,
      'name' => 'My Name 2' ,
      'date' => 'My date 2'
   ),
   array(
      'title' => 'Another title' ,
      'name' => 'Another Name 2' ,
      'date' => 'Another date 2'
   )
)
*/

/*
 * ----------------------------------
 * update batch 
 * ----------------------------------
 * 
 * multiple update in one query
 *
 * tablename( required | string )
 * multipleData ( required | array of array )
 */
static function updateBatch($tableName = "", $multipleData = array()){

    if( $tableName && !empty($multipleData) ) {

        // column or fields to update
        $updateColumn = array_keys($multipleData[0]);
        $referenceColumn = $updateColumn[0]; //e.g id
        unset($updateColumn[0]);
        $whereIn = "";

        $q = "UPDATE ".$tableName." SET "; 
        foreach ( $updateColumn as $uColumn ) {
            $q .=  $uColumn." = CASE ";

            foreach( $multipleData as $data ) {
                $q .= "WHEN ".$referenceColumn." = ".$data[$referenceColumn]." THEN '".$data[$uColumn]."' ";
            }
            $q .= "ELSE ".$uColumn." END, ";
        }
        foreach( $multipleData as $data ) {
            $whereIn .= "'".$data[$referenceColumn]."', ";
        }
        $q = rtrim($q, ", ")." WHERE ".$referenceColumn." IN (".  rtrim($whereIn, ', ').")";

        // Update  
        return DB::update(DB::raw($q));

    } else {
        return false;
    }
}

它将产生:

UPDATE `mytable` SET `name` = CASE
WHEN `title` = 'My title' THEN 'My Name 2'
WHEN `title` = 'Another title' THEN 'Another Name 2'
ELSE `name` END,
`date` = CASE 
WHEN `title` = 'My title' THEN 'My date 2'
WHEN `title` = 'Another title' THEN 'Another date 2'
ELSE `date` END
WHERE `title` IN ('My title','Another title')

【讨论】:

  • 我必须更改此行以使其正常工作,在 $data[$referenceColumn] $q .= "WHEN ".$referenceColumn." = '".$data[ $referenceColumn]."' THEN '".$data[$uColumn]."' ";之后,它按描述工作:-)
  • 注意:我还在最后一个 $q 分配之前添加了以下行,以从 wereIn 列表中删除最后一个额外的逗号。 $whereIn = rtrim($whereIn, ', ');
  • 我已经修剪了 ',' 。请正确检查。看起来你做错了什么
  • 我认为没有任何值被转义并且查询容易受到 SQL 注入的攻击
【解决方案4】:

分块结果是在不占用所有 RAM 的情况下执行此类操作的最佳方法,并且 Laravel 支持开箱即用的分块结果。

例如:

凭证::chunk(2000, function($vouchers) { foreach ($vouchers 作为 $voucher) { // } });

【讨论】:

  • 您只提供了通过读取数据库进行分块的解决方案。批量更新在哪里?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-15
  • 2019-01-02
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
  • 1970-01-01
  • 2013-06-09
相关资源
最近更新 更多