web-dev-qa-db-ja.com

laravel cursorとlaravel=チャンクメソッドの違いは何ですか?

laravelチャンクとlaravel=カーソルメソッド。どのメソッドが使用に適していますか?使用例は何ですか?カーソルを使用してメモリを節約する必要があることを知っていますが、バックエンドで実際にどのように機能するのですか?

私はstackoverflowや他のサイトで検索しましたが、多くの情報が見つかりませんでしたので、例で詳細な説明が役立つでしょう。

laravelドキュメントからのコードスニペットです。

チャンキング結果

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //
    }
});

カーソルを使用

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}
12
Suraj

確かに、この質問はいくつかの意見のある答えを引き付けるかもしれませんが、簡単な答えはここにあります Laravel Docs

参考のために:

これはチャンクです:

enter image description here

これはカーソルです:

enter image description here

チャンクはデータベースからレコードを取得し、メモリにロードしますが、最後に取得したレコードにカーソルを設定して、クラッシュが発生しないようにします。

したがって、ここでの利点は、送信する前にlargeレコードを再フォーマットする場合、またはn番目のレコードで操作を実行する場合です。時間ごとに、これは便利です。たとえば、ビューアウト/ Excelシートを作成している場合、すべてのレコードが一度にメモリにロードされずにメモリ制限に達することがないように、完了するまでレコードをカウントできます。

カーソルはPHP Generatorsを使用します。 php generators ページを確認できますが、興味深いキャプションがあります。

enter image description here

Cursorの概念を完全に理解していることを保証することはできませんが、Chunkの場合、chunkはレコードサイズごとにクエリを実行し、それを取得して、レコードをさらに処理するためにクロージャに渡します。

これが役立つことを願っています。

比較があります:chunk()vs cursor()

  • cursor():高速
  • chunk():一定のメモリ使用量

10,000レコード

+-------------+-----------+------------+
|             | Time(sec) | Memory(MB) |
+-------------+-----------+------------+
| get()       |      0.17 |         22 |
| chunk(100)  |      0.38 |         10 |
| chunk(1000) |      0.17 |         12 |
| cursor()    |      0.16 |         14 |
+-------------+-----------+------------+

100,000レコード

+--------------+------------+------------+
|              | Time(sec)  | Memory(MB) |
+--------------+------------+------------+
| get()        |        0.8 |     132    |
| chunk(100)   |       19.9 |      10    |
| chunk(1000)  |        2.3 |      12    |
| chunk(10000) |        1.1 |      34    |
| cursor()     |        0.5 |      45    |
+--------------+------------+------------+
  • TestData:Laravelデフォルト移行のユーザーテーブル
  • ホームステッド0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22
11

chunkはページネーションに基づいており、ページ番号を維持し、ループを実行します。

たとえば、DB::table('users')->select('*')->chunk(100, function($e) {})は、結果セットがチャンクサイズ(100)より小さくなるまで複数のクエリを実行します。

select * from `users` limit 100 offset 0;
select * from `users` limit 100 offset 100;
select * from `users` limit 100 offset 200;
select * from `users` limit 100 offset 300;
select * from `users` limit 100 offset 400;
...

cursorは、PDOStatement::fetchおよびGeneratorに基づいています。

$cursor = DB::table('users')->select('*')->cursor()
foreach ($cursor as $e) { }

単一のクエリを発行します:

select * from `users`

ただし、ドライバーは結果セットを一度に取得しません。

9
oraoto

カーソルを使用してベンチマークを作成しました

foreach (\App\Models\Category::where('type','child')->get() as $res){

}

foreach (\App\Models\Category::where('type', 'child')->cursor() as $flight) {
    //
}

return view('welcome');

結果は次のとおりです。 chunk is faster thank using where

0
hendra1

Cursor()

  • 単一のクエリのみ
  • PDOStatement::fetch()を呼び出して結果を取得します
  • デフォルトでは、バッファされたクエリが使用され、すべての結果が一度にフェッチされます。
  • 現在の行のみを雄弁なモデルに変えました

長所

  • 雄弁なモデルメモリオーバーヘッドを最小限に抑える
  • 操作が簡単

短所

  • 巨大な結果は、メモリ不足につながります
  • バッファ付きまたはバッファなしはトレードオフです

Chunk()

  • 制限とオフセットを持つクエリへのクエリのチャンク
  • _PDOStatement::fetchAll_を呼び出して結果を取得します
  • 結果を雄弁なモデルにバッチ処理した

長所

  • 制御可能な使用メモリサイズ

短所

  • 結果を雄弁なモデルにバッチ処理すると、メモリのオーバーヘッドが発生する可能性があります
  • クエリとメモリ使用量は裏切りです

TL; DR

cursor()は毎回クエリを実行し、1行の結果のみをメモリに保持すると考えていました。 @ mohammad-asghariの比較表を見たとき、本当に混乱しました。背後にあるbufferでなければなりません。

Laravel以下のコードで追跡

_/**
 * Run a select statement against the database and returns a generator.
 *
 * @param  string  $query
 * @param  array  $bindings
 * @param  bool  $useReadPdo
 * @return \Generator
 */
public function cursor($query, $bindings = [], $useReadPdo = true)
{
    $statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
        if ($this->pretending()) {
            return [];
        }

        // First we will create a statement for the query. Then, we will set the fetch
        // mode and prepare the bindings for the query. Once that's done we will be
        // ready to execute the query against the database and return the cursor.
        $statement = $this->prepared($this->getPdoForSelect($useReadPdo)
                          ->prepare($query));

        $this->bindValues(
            $statement, $this->prepareBindings($bindings)
        );

        // Next, we'll execute the query against the database and return the statement
        // so we can return the cursor. The cursor will use a PHP generator to give
        // back one row at a time without using a bunch of memory to render them.
        $statement->execute();

        return $statement;
    });

    while ($record = $statement->fetch()) {
        yield $record;
    }
}
_

Laravel wrapPDOStatement :: fetch()でこの機能を構築します。そして、検索buffer PDO fetchおよびMySQL、このドキュメントを見つけました。

https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php

クエリはデフォルトでバッファモードを使用しています。これは、クエリ結果がMySQLサーバーからPHPに直ちに転送され、その後PHPプロセスのメモリに保持されることを意味します。

pDOStatement :: execute()を実行することで、実際に結果行全体を1で取得し、メモリに格納します、1行だけではありません。したがって、結果が非​​常に大きい場合、これはメモリ不足例外につながります。

示されているドキュメントでは、$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);を使用してバッファリングされたクエリを削除できます。ただし、欠点は注意が必要です。

バッファリングされていないMySQLクエリはクエリを実行し、データがMySQLサーバーでフェッチされるのを待機している間にリソースを返します。これにより、PHP側のメモリ使用量は少なくなりますが、サーバーの負荷が増加する可能性があります。サーバーから完全な結果セットがフェッチされない限り、同じ接続を介してさらにクエリを送信することはできません。バッファリングされていないクエリは、「結果を使用する」とも呼ばれます。

0
劉恒溫