PHPは同期的に実行されます。これはMySQLなどのデータベースを操作する時も同じです。クエリを投げた後PHPはデータベースから結果が返って来るのを待っているわけです。非同期実行を使うことでこの待ち時間の無駄を省き高速化できます。この記事ではPHPの非同期方法の一つであるSwooleを使ってクエリを非同期で発行し、その結果を取得する方法を紹介します。
SwooleはPHPの拡張です。PHPのdebian系のDockerイメージならば次のように書くことでSwoole拡張を追加して有効化できます。
RUN apt-get update && apt-get install -y libbrotli-dev && \ pecl install swoole && \ docker-php-ext-enable swoole
実際のコード例が次です。
<?php require 'vendor/autoload.php'; // .envファイルの読み込み $env = \Dotenv\Dotenv::createImmutable([__DIR__])->safeLoad(); /** * クエリを実行 * * @param string $query * @return bool|array */ function executeQuery(string $query): bool|array { global $env; // 非同期実行のためには異なるPDOインスタンスでDBと接続する必要あり $pdo = new PDO( dsn: "mysql:host={$env['DB_HOST']};dbname={$env['DB_DATABASE']};charset=utf8", username: $env['DB_USERNAME'], password: $env['DB_PASSWORD'], options: [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $stmt = $pdo->query($query); return $stmt->fetchAll(); } /***************** * 直列実行 *****************/ $start = microtime(true); echo "直列実行\n"; // 3秒かかるクエリと1秒かかるクエリを直列で実行 $result = []; $result[0] = executeQuery('SELECT SLEEP(3)'); $result[1] = executeQuery('SELECT SLEEP(1)'); $end = microtime(true); echo "処理時間: " . ($end - $start) . "秒\n"; echo " 結果: " . json_encode($result) . "\n"; //処理時間: 4.0209400653839秒 // 結果: [[{"SLEEP(3)":0}],[{"SLEEP(1)":0}]] /*********** * 並列実行 ***********/ $start = microtime(true); echo "並列実行\n"; // 3秒かかるクエリと1秒かかるクエリを並列で実行 \Swoole\Runtime::enableCoroutine(); // 先に終わった方から書き込まれるので、あらかじめ書き込み先のインデックスを用意するか後でsortするかしないとキーの順番が不自然になる $result = [null, null]; // Swooleのコルーチンを使って並列実行 \Co\run(function () use (&$result) { $co1 = go(function () use (&$result) { // クエリを実行して、リファレンス渡しで結果を格納 $result[0] = executeQuery('SELECT SLEEP(3)'); }); $co2 = go(function () use (&$result) { $result[1] = executeQuery('SELECT SLEEP(1)'); }); // 並列実行の終了を待つ \co::join([$co1, $co2]); }); $end = microtime(true); echo "処理時間: " . ($end - $start) . "秒\n"; echo " 結果: " . json_encode($result) . "\n"; //処理時間: 3.0064129829407秒 // 結果: [[{"SLEEP(3)":0}],[{"SLEEP(1)":0}]]
結果の取得に3秒かかるクエリと1秒かかるクエリを実行する例です。普通のPHPの書き方の通りに順番に実行した場合は完了までに4秒かかりましたが、非同期実行した方は3秒で終わりました。このようにあるクエリの待ち時間中に他のことをできます。クエリが少なく、全て高速であれば非同期実行の有無による速度差はあまり出ませんが重いクエリを複数実行する処理がある場合などは特に効果がでます。
高速化を狙える手法ですが注意点もあります。これは同じPDOインスタンスを使いまわすと結局直列になる点、データベースへの瞬間的な負荷が直列実行より大きい点、トランザクション管理が複雑になるという点、素で書くよりプログラムが複雑になりやすい点です。またこの方法で高速化をするよりもクエリそのものが高速になる方が計算資源に優しいです。
この記事ではSwooleを使ったPHPの非同期データベースクエリ実行方法を紹介しました。積極的に使うようなものでもないですが、これを使うことで非同期化によりレスポンスの待ち時間を短縮することが可能です。