개별 데이터로 여러 레코드 대량 업데이트 — Laravel

3985 단어 mysqllaravel
일반적으로 for 루프 내에서 데이터베이스 쿼리를 실행해서는 안 됩니다!

"데이터베이스 트랜잭션"은 비용이 많이 드는 작업입니다.

예를 들어 인벤토리 소프트웨어를 설계했고 1년 동안 생산에 사용했으며 1,000,000건의 거래에 도달했다고 가정해 보겠습니다.

갑자기 우리는 거래에 VAT를 추가하지 않았다는 것을 알게 되었습니다. 미래의 거래에 대해서는 다루기가 매우 쉽습니다. 아마도 돌연변이원일 수도 있습니다.

class Transaction extends Model { 
    public $vat = 0.20;

    public function setPriceAttribute($value) {
        $this->attributes['price'] += $value * $this->vat;
    }
}


미래의 기록은 다루기가 매우 쉽습니다. 그러나 과거의 100만 레코드를 어떻게 편집할 것입니까?

과거의 데이터를 편집하려면 Seeder를 만드는 것을 선호합니다.
php artisan make:seeder AddVatToTransactions

어떻게하지?





class AddVatToTransactions extends Seeder {

  public function run() 
  {
    $vat = 0.20;
    $transactions = Transaction::get();

    foreach ($transactions as $transaction) {
       $transaction->price += $transaction->price * $vat
       $transaction->save();
    }
  }
}


그러나 100만 개의 루프에서 실행하고 루프의 각 반복에서 "데이터베이스 트랜잭션"을 만드는 것은 좋은 생각이 아닙니다! (스포일러 경고: 시스템이 정지됩니다 😀)


그렇다면 어떻게 해야 할까요?



다시, 우리의 AddVatToTransactions 시더에서:

mysql 쿼리의 아이디어는 "CASE 문"입니다.

UPDATE db.transactions
SET PRICE = CASE  
              WHEN id = 3 THEN 500
              WHEN id = 4 THEN 300
           END 
WHERE ID IN (3, 4)


이제 Laravel에서 해봅시다.




$vat = 0.20;
$transactions = Transaction::get();

$cases = [];
$ids = [];
$params = [];

foreach ($transactions as $transaction) {
    $cases[] = "WHEN {$transaction->id} then ?";
    $params[] = $transaction->profit * $vat;
    $ids[] = $transaction->id;
}

$ids = implode(',', $ids);
$cases = implode(' ', $cases);

if (!empty($ids)) {
    \DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
}


이것은 하나의 데이터베이스 트랜잭션을 만들어 모든 업데이트를 작성합니다.⚡️




🗣 "아직도 얼고 있어"라는 말을 들을 수 있습니다.

그래서.. 더 최적화:



#1: RAM을 덜 사용하기 위해 데이터베이스에서 필요한 데이터만 "선택"하십시오.

이 예에서는 "id"및 "price"열만 사용합니다. 그래서 그것들 만 선택합시다.

$transactions = Transaction::select('id', 'price')->get();


#2: 컬렉션을 "청크"하여 트랜잭션을 여러 데이터베이스 트랜잭션으로 분리합니다.

Laravel에서는 다음과 같은 청크 컬렉션을 만들 수 있습니다.

Transaction::get()->chunk(5000);



예제에 모두 적용해 보겠습니다.



여기에서 먼저 $transactions 컬렉션을 5000개의 청크로 나누고 한 번에 5k 레코드당 "데이터베이스 트랜잭션"을 수행합니다.

$vat = 0.20;
$transactions = Transaction::get();

foreach ($transactions->chunk(5000) as $chunk) {
   $cases = [];
   $ids = [];
   $params = [];

   foreach ($chunk as $transaction) {
       $cases[] = "WHEN {$transaction->id} then ?";
       $params[] = $transaction->profit * $vat;
       $ids[] = $transaction->id;
   }

   $ids = implode(',', $ids);
   $cases = implode(' ', $cases);

   if (!empty($ids)) {
       \DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
   }
}


이 트릭이 마음에 드셨으면 좋겠습니다!

댓글로 여러분의 생각을 알려주세요 💬

즐거운 코딩하세요! 😊

좋은 웹페이지 즐겨찾기