MySQLで50個のIDからレコードを取得したいときに、プレースホルダで50回叩くのとINで一回で取るの、どっちが速いか。
今日はふぁぼったーのフロントエンドのDB処理まわりをリファクタリングしました。この辺りも、もう1年半も開発・拡張を続けている部分なのでかなり汚くなっており、今後のためにできるだけソースを短くし、保守性の高いコードに書き直すなどの作業です。
そのなかで、以前から気になっていた点があったので、せっかくなので検証してみました。
前提
ふぁぼったーでは、発言をデータベースから取得する際、最初に発言とそのユーザーをSTATUSテーブルとUSERテーブルからSELECTして、そこで得た発言のIDを用いて、右下に出る各発言のふぁぼりユーザーをFAVORITEテーブルとUSERテーブルから別途SELECTしています。二つに分けたのはその方が速かったからですが、今回検証したのは、後半のふぁぼりユーザー取得部分の書き方です。
FAVORITEテーブルから複数の既知のステータスIDのレコードをSELECTしてきたい訳ですが、大体以下の二つの取得方法が考えられます。
比較
人間が書きやすいのは多分Aなのですが、パフォーマンスの面ではどの程度差が出るのか気になったので、ベンチマークを取ってみました。
条件
- 2008/11/22の人気ふぁぼられページでテスト
- SQLキャッシュは無効
- プレースホルダを利用する関係上、SQL単体での比較は難しい。よって、各方式でふぁぼりユーザーを取得する関数を作成し、関数全体の実行時間を比較
A方式.
SELECT SQL_NO_CACHE f.id as status_id,screen_name,profile_image_url from FAVORITE f,USER u where f.id=? and f.user_id=u.id; /*以下のプレースホルダで逐次実行。 1424797395, 1429571494, 1425533647, 1424770030, 1424798850, 1429457857, 1427719403, 1424996482, 1427697348, 1425186009, 1425883861, 1425883445, 1424981533, 1424930159, 1430825300, 1431058634, 1425179905, 1424813902, 1430527920, 1425768797, 1424796665, 1430748456, 1424828035, 1430514385, 1430474389, 1427714952, 1424987539, 1424839614, 1431577764, 1427783898, 1425414359, 1427950164, 1425191835, 1424916362, 1430541056, 1429964773, 1428692993, 1427342901, 1425160690, 1425081143, 1430872949, 1425871198, 1424959392, 1430738299, 1430707134, 1430345316, 1430150316, 1427859488, 1427784934, 1427701659, 1424995649 */
B方式.
以下のSQL文で実行
SELECT SQL_NO_CACHE f.id as status_id,screen_name,profile_image_url from FAVORITE f,USER u where f.id IN ( 1424797395, 1429571494, 1425533647, 1424770030, 1424798850, 1429457857, 1427719403, 1424996482, 1427697348, 1425186009, 1425883861, 1425883445, 1424981533, 1424930159, 1430825300, 1431058634, 1425179905, 1424813902, 1430527920, 1425768797, 1424796665, 1430748456, 1424828035, 1430514385, 1430474389, 1427714952, 1424987539, 1424839614, 1431577764, 1427783898, 1425414359, 1427950164, 1425191835, 1424916362, 1430541056, 1429964773, 1428692993, 1427342901, 1425160690, 1425081143, 1430872949, 1425871198, 1424959392, 1430738299, 1430707134, 1430345316, 1430150316, 1427859488, 1427784934, 1427701659, 1424995649 ) and f.user_id=u.id;
結果
- A方式(プレースホルダ)
- 0.0757秒
- B方式(IN句)
- 0.0390秒
参考:ページあたりのfav数の少ない「新着fav」ページでテストした結果
- A方式(プレースホルダ)
- 0.0279秒
- B方式(IN句)
- 0.0053秒
結論
2倍から6倍ほどIN句の方が高速でした。なんとなく予想はしていましたが、思った以上に一回のクエリで処理できるというアドバンテージは大きいみたいですね。
出来るだけ高速にしたい局面では、複数の既知のIDからレコードを取得する際にはIN句を使った方がよさそうです。
参考
実際に使った関数を置いておきます。
350 private function getStatusesFavotters(){
351 $favSQL="SELECT SQL_NO_CACHE f.id as status_id,screen_name,profile_image_url from FAVORITE f,USER u where f.id=? and f.user_id=u.id;";
352 preprint($favSQL);
353 $favStmt=$this->pdo->prepare($favSQL);
354 foreach ($this->statuses as $key => $rev) {
355 $favotters = array();
356 $favStmt->execute( array( $rev['status_id'] ) );
357
358 while($favotter=$favStmt->fetch(PDO::FETCH_ASSOC)){
359 $url=$favotter['profile_image_url'];
360 $favotter['profile_image_url_mini']=preg_replace("/(bigger|normal).(jpg|jpeg|gif|png)/i","mini.$2",$url,1);
361 array_push($favotters,$favotter);
362 }
363 $this->statuses[$key]['favotters'] = $favotters;
364
365 }
366 }
367 private function getStatusesFavotters2(){
368 $sids = array();
369 $favotters = array();
370 foreach($this->statuses as $st){
371 array_push($sids,$st['status_id']);
372 $favotters[$st['status_id']] = array();
373 }
374
375 $favSQL="SELECT SQL_NO_CACHE f.id as status_id,screen_name,profile_image_url from FAVORITE f,USER u where f.id IN (".implode(',',$sids).") and f.user_id=u.id;";
376 preprint($favSQL);
377 $favStmt = $this->pdo->query($favSQL);
378 while($favotter = $favStmt->fetch(PDO::FETCH_ASSOC)){
379 $url=$favotter['profile_image_url'];
380 $favotter['profile_image_url_mini']=preg_replace("/(bigger|normal).(jpg|jpeg|gif|png)/i","mini.$2",$url,1);
381 array_push($favotters[ $favotter['status_id'] ],$favotter);
382 }
383 foreach( $this->statuses as $key => $rev ){
384 $this->statuses[$key]['favotters'] = $favotters[ $rev['status_id'] ];
385 }
386 }