PHP
laravel

【Laravel】重複クエリを排除する簡単な方法

Laravelデバッガーで重複クエリを確認

LaravelデバッガーのQueriesのタブを見ると、今のページの通信で何クエリ実行され、そのうち何クエリーに重複があるかがわかる。
キャッシュ変数などを使うことで減らすことができるのだが、重複クエリを一網打尽にする方法がある。
方法としては発行したSELECT文のSQLをキャッシュさせ、重複するSQLであればキャッシュから内容を引っ張ってくるというシンプルなもの。
Laravel5.4で確認しています。

重複するクエリを排除する方法

~/app/Support/Database/Builder.php
<?php

namespace App\Support\Database;

use Cache;
use Illuminate\Database\Query\Builder as QueryBuilder;

class Builder extends QueryBuilder
{
    /**
     * Run the query as a "select" statement against the connection.
     *
     * @return array
     */
    protected function runSelect()
    {
        return Cache::store('request')->remember($this->getCacheKey(), 1, function() {
            return parent::runSelect();
        });
    }

    /**
     * Returns a Unique String that can identify this Query.
     *
     * @return string
     */
    protected function getCacheKey()
    {
        return json_encode([
            $this->toSql() => $this->getBindings()
        ]);
    }
}

クエリをキャッシュするメインの処理。Cache::store('request')は下記のconfigで設定する必要がある。

~/config/cache.php
'stores' => [
    'request' => [
        'driver' => 'array'
    ]
]

あえてクエリをarrayキャッシュさせることでリクエスト処理後にキャッシュは削除され、ページ間でのキャッシュは引き継がせないようにされている。これにより1リクエストごとにメモリは解放され、重複クエリの排除のために余計なことを考えなくてすむ。
ただ、ページ間にわたって重複するクエリが多いようであれば、redisやmemcachedなど別のドライバーを使ってもいいかもしれない。

~/public/index.php
try
{
    // Flush the Request Cache
    $app->make('cache')->store('request')->flush();
}
catch(ReflectionException $ex)
{
    // Do nothing
}

$kernel->terminate($request, $response)の後に記述する。

~/app/Support/Database/CacheQueryBuilder.php
<?php

namespace App\Support\Database;

trait CacheQueryBuilder
{
    /**
     * Get a new query builder instance for the connection.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    protected function newBaseQueryBuilder()
    {
        $conn = $this->getConnection();

        $grammar = $conn->getQueryGrammar();

        return new Builder($conn, $grammar, $conn->getPostProcessor());
    }
}

各モデルで読込み使用するトレイトを作成する。

~/.../SomeModel.php
<?php

namespace App;

use App\Support\Database\CacheQueryBuilder;
use Illuminate\Database\Eloquent\Model;

class SomeModel extends BaseModel
{
    /**
     * For Caching all Queries.
     */
    use CacheQueryBuilder;
}

モデルで上記トレイトを使用することでクエリビルダ作成時にクエリがキャッシュされ、重複するクエリは排除されるようになる。

参考サイト

Never Execute a Duplicate Query Again