ホームページ >バックエンド開発 >PHPチュートリアル >MySQL のクエリに関する PHP 同時実行の例 (図)
この記事では主にMySQLのPHP同時クエリのサンプルコードを紹介していますが、編集者が非常に良いと思ったので、参考として共有させていただきます。エディターをフォローして見てみましょう
私は最近 PHP を勉強していて、とても気に入っています。PHP での MySQL の同時クエリの問題に遭遇しました。ちなみに、勉強してメモを残しました。 query
これは最も一般的なものです。呼び出しモードでは、クライアントは Query[関数] を呼び出し、クエリ コマンドを開始し、結果が返されるのを待って、結果を読み取り、次に 2 番目のクエリ コマンドを送信し、返される結果を取得し、その結果を読み取ります。所要時間の合計は、2 つのクエリの時間の合計になります。たとえば、以下に示すようにプロセスを簡略化します。
サンプル画像、1.1 から 1.3 は Query [関数] の呼び出しです。2 つのクエリは 1.1、1.2、1.3、2.1、2.2、2.3 を通過します。特に 1.2 と 2.2 では、ブロックされて待機し、プロセスは他のことを行うことができません。
同期呼び出しの利点は、直感的な考え方に準拠しており、呼び出しと処理が簡単であることです。欠点は、結果が返されるのを待ってプロセスがブロックされ、実行時間が余分にかかることです。
複数のクエリリクエストがある場合、またはプロセスが他の処理を行っている場合、待ち時間を合理的に利用してプロセスの処理能力を向上させることは可能ですか?
分割
次に、Query [関数] を分割します。クライアントは 1.1 の直後に戻り、1.2 をスキップして 1.3 にデータが到着した後にデータを読み取ります。このようにして、プロセスは元の 1.2 の段階から解放され、別の SQL クエリ [2.1] を開始するなど、より多くのことを実行できるようになります。並行クエリのプロトタイプを見たことがありますか?
同時クエリ
同期クエリと比較して、同時クエリでは、前のクエリリクエストが開始された直後に次のクエリリクエストを開始できます。以下に示すように、プロセスを簡略化します。
例の図では、1.1.1 でリクエストが正常に送信された後、すぐに [1.1.2] が返され、最終的なクエリ結果がリモート 1.2 で返されます。ただし、1.1.1 と 1.2 の間では、この期間中に 2 つのクエリ リクエストが同時に開始され、2.2 は 1.2 よりも前に到着したため、2 つのクエリの合計時間は の時間と同じでした。最初のクエリ。
同時クエリの利点は、プロセスの使用率を向上させ、サーバーによるクエリの処理のブロックと待機を回避し、複数のクエリの時間を短縮できることです。ただし、欠点も明らかです。N 個の同時クエリを開始するには、データベース接続プールを備えたアプリケーションの場合、この状況を回避できます。
縮退
理想的には、N 個のクエリを同時に実行し、合計の消費時間はクエリ時間が最も長いクエリと同じになるようにする必要があります。ただし、同時クエリが [同期クエリ] に [縮退] する可能性もあります。何?例の図では、2.1.1 より前に 1.2 が返された場合、同時クエリは [同期クエリ] に [縮退] しますが、コストは同期クエリよりも高くなります。
多重化
クエリ1を開始
クエリ 1 の結果を読む
クエリ IO ごとに read を呼び出しますか?ブロッキング IO が発生した場合、1 つの IO でブロックされ、他の IO では結果が返され、処理できなくなります。したがって、ノンブロッキング IO の場合、いずれかの IO がブロックされることを心配する必要はありません。ただし、ポーリングと判定が継続的に発生し、CPU リソースが浪費されます。
この状況では、多重化を使用して複数の IO をポーリングできます。
PHP は MySQL を実装します
PHP の mysqli (mysqlnd ドライバー) は、多重ポーリング IO (mysqli_poll) と非同期クエリ (MYSQLI_ASYNC、mysqli_reap_async_query) を提供します。サンプル コード:
<?php $sqls = array( 'SELECT * FROM `mz_table_1` LIMIT 1000,10', 'SELECT * FROM `mz_table_1` LIMIT 1010,10', 'SELECT * FROM `mz_table_1` LIMIT 1020,10', 'SELECT * FROM `mz_table_1` LIMIT 10000,10', 'SELECT * FROM `mz_table_2` LIMIT 1', 'SELECT * FROM `mz_table_2` LIMIT 5,1' ); $links = []; $tvs = microtime(); $tv = explode(' ', $tvs); $start = $tv[1] * 1000 + (int)($tv[0] * 1000); // 链接数据库,并发起异步查询 foreach ($sqls as $sql) { $link = mysqli_connect('127.0.0.1', 'root', 'root', 'dbname', '3306'); $link->query($sql, MYSQLI_ASYNC); // 发起异步查询,立即返回 $links[$link->thread_id] = $link; } $llen = count($links); $process = 0; do { $r_array = $e_array = $reject = $links; // 多路复用轮询IO if(!($ret = mysqli_poll($r_array, $e_array, $reject, 2))) { continue; } // 读取有结果返回的查询,处理结果 foreach ($r_array as $link) { if ($result = $link->reap_async_query()) { print_r($result->fetch_row()); if (is_object($result)) mysqli_free_result($result); } else { } // 操作完后,把当前数据链接从待轮询集合中删除 unset($links[$link->thread_id]); $link->close(); $process++; } foreach ($e_array as $link) { die; } foreach ($reject as $link) { die; } }while($process < $llen); $tvs = microtime(); $tv = explode(' ', $tvs); $end = $tv[1] * 1000 + (int)($tv[0] * 1000); echo $end - $start,PHP_EOL;。 mysqli_poll ソースコード:
#ifndef PHP_WIN32 #define php_select(m, r, w, e, t) select(m, r, w, e, t) #else #include "win32/select.h" #endif /* {{{ mysqlnd_poll */ PHPAPI enum_func_status mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, int * desc_num) { struct timeval tv; struct timeval *tv_p = NULL; fd_set rfds, wfds, efds; php_socket_t max_fd = 0; int retval, sets = 0; int set_count, max_set_count = 0; DBG_ENTER("_mysqlnd_poll"); if (sec < 0 || usec < 0) { php_error_docref(NULL, E_WARNING, "Negative values passed for sec and/or usec"); DBG_RETURN(FAIL); } FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); // 从所有mysqli链接中获取socket链接描述符 if (r_array != NULL) { *dont_poll = mysqlnd_stream_array_check_for_readiness(r_array); set_count = mysqlnd_stream_array_to_fd_set(r_array, &rfds, &max_fd); if (set_count > max_set_count) { max_set_count = set_count; } sets += set_count; } // 从所有mysqli链接中获取socket链接描述符 if (e_array != NULL) { set_count = mysqlnd_stream_array_to_fd_set(e_array, &efds, &max_fd); if (set_count > max_set_count) { max_set_count = set_count; } sets += set_count; } if (!sets) { php_error_docref(NULL, E_WARNING, *dont_poll ? "All arrays passed are clear":"No stream arrays were passed"); DBG_ERR_FMT(*dont_poll ? "All arrays passed are clear":"No stream arrays were passed"); DBG_RETURN(FAIL); } PHP_SAFE_MAX_FD(max_fd, max_set_count); // select轮询阻塞时间 if (usec > 999999) { tv.tv_sec = sec + (usec / 1000000); tv.tv_usec = usec % 1000000; } else { tv.tv_sec = sec; tv.tv_usec = usec; } tv_p = &tv; // 轮询,等待多个IO可读,php_select是select的宏定义 retval = php_select(max_fd + 1, &rfds, &wfds, &efds, tv_p); if (retval == -1) { php_error_docref(NULL, E_WARNING, "unable to select [%d]: %s (max_fd=%d)", errno, strerror(errno), max_fd); DBG_RETURN(FAIL); } if (r_array != NULL) { mysqlnd_stream_array_from_fd_set(r_array, &rfds); } if (e_array != NULL) { mysqlnd_stream_array_from_fd_set(e_array, &efds); } // 返回可操作的IO数量 *desc_num = retval; DBG_RETURN(PASS); }
同時クエリ操作の結果
効果をより直感的に確認するために、操作用に最適化されていない 1 億 3,000 万のデータ量を持つテーブルを見つけました。
同時クエリの結果:
同期クエリの結果:
この結果から、同期クエリの合計消費時間は、すべてのクエリの時間の合計です。同時クエリの消費量は実際に最も長いクエリです (同期クエリの 4 番目のクエリには数秒かかりますが、これは同時クエリの合計時間と一致します)。また、同時クエリのクエリ順序は次のクエリとは異なります。結果が到着する順序。
クエリ時間が短い複数のクエリの比較
クエリ時間が短い複数の SQL クエリを使用して比較します
同時クエリのテスト 1 結果 (データベースリンク時間もカウントされます):
同期の結果クエリ (データベース リンク時間もカウントされます):
同時クエリのテスト 2 の結果 (データベース リンク時間はカウントされません):
結果から判断すると、同時クエリ テスト1は恩恵を受けませんでした。同期クエリの観点から見ると、各クエリには約 3 ~ 4 ミリ秒かかります。ただし、データベース接続時間が統計に含まれていない場合 (同期クエリにはデータベース接続が 1 つしかありません)、同時クエリの利点が再び反映される可能性があります。
結論
ここでは、PHP での同時クエリ MySQL の実装について説明し、実験結果から同時クエリの長所と短所を直観的に理解しました。データベース接続を確立する時間が、最適化された SQL クエリの大部分を占めます。 #接続プールがないのですが、何に使いますか
以上がMySQL のクエリに関する PHP 同時実行の例 (図)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。