読者です 読者をやめる 読者になる 読者になる

アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

ランダムで取得&速度検証

`table` テーブルよりランダムで1件取得したいという場合、次のように書きます。

SELECT   *
FROM     `table`
ORDER BY RAND()
LIMIT    1

速度検証

ランダムは遅いと言われますし、
585万件入ったテーブルを使って色々試してみました。

-- ①絞り込みを行わず1件取得(23.5秒)
SELECT * FROM `test` 
ORDER BY RAND() LIMIT 1

-- ②絞り込みを行わず5件取得(20.3秒)
SELECT * FROM `test` 
ORDER BY RAND() LIMIT 5

-- ③インデックスの貼られていないカラムで絞り込み1件取得(6.7秒 / 対象35万件)
SELECT * FROM `test` WHERE `hoge` = 1
ORDER BY RAND() LIMIT 1

-- ④インデックスの貼られているカラムで絞り込み1件取得(2.3秒 / 対象44万件)
SELECT * FROM `test` WHERE `fuga` = 1
ORDER BY RAND() LIMIT 1

-- ⑤少数に絞り込み1件取得(0.0004秒 / 対象10件)
SELECT * FROM `test` WHERE `fuga` = 2
ORDER BY RAND() LIMIT 1

①と②は…何故か②の方が速かったですが、誤差?
どちらにしろ、取得件数に応じて時間が長くなるわけではなさそうです。

⑤のようにかなり絞り込んでからランダムであれば問題はなさそうですが、
母数が大きい場合はとても実用には向きません。

WHERE句で絞り込み後の件数を調節しながらざっくり試したところ、

  • 4万件で0.1秒
  • 7万件で0.2秒
  • 13万件で0.5秒
  • 20万件で0.7秒
  • 28万件で1.0秒

という感じでした。
(※絞り込みにはプライマリキーを使用)

単純に「ランダムは遅いからダメ!」と切り捨ててしまうのではなく、
対象のレコード数・クエリ実行頻度・実行時間と相談しながら使用可否を決めるのが良いと思います。

速度改善妥協案

まあ「10万件の応募が見込まれる抽選で5名ランダムで選ぶ」とかなら
少々速度が遅かろうが、実行頻度が高くないと予想されるので、まあ、問題ないでしょう。

しかし「500万人のユーザがいるSNSのトップページに、友達候補として全ユーザからランダムで10名選んで掲載」なんてことになると大変です。
速度は求められるし実行頻度もかなり高いはず。バカ正直に毎回SELECTをかけるわけにはいきません。
例えば以下のような妥協案でなんとかする必要があります。

  • とにかく対象を絞り込む
      • 1日以内にログインしたユーザのみを対象にする
      • そのページを見ているユーザに近い属性のユーザに絞り込む
    • 問題点
      • 全ユーザから選ぶという仕様を変える必要がある
      • 絞り込み方法によっては、速度に問題ない数まで絞り込めるとは限らない
  • 数分に1回程度の頻度で取得してサーバや一時テーブルに保存しておき、それを表示するようにする
    • 問題点
      • 一定時間は表示内容が変わらないが、それでも良いかどうか
      • 100件取得しておいてアプリ側でさらに10件ランダムで取る方法もあるが、どちらにしろ完全ランダムではない
  • ある程度アプリ側でなんとかする
      • アプリ側で、会員IDの最小値~最大値の範囲でランダム値を出して、それを利用する(この例の場合は退会している可能性等があるので、それをそのまま使用するのは難しい)