Query Go
ランダムサンプリング — ORDER BY RANDOM() の罠と代替
ガイド

ランダムサンプリング — ORDER BY RANDOM() の罠と代替

ランダムに N 件取りたいとき。ORDER BY RANDOM() より速い方法。

ランダムサンプリング — ORDER BY RANDOM() の罠と代替 diagram

問題 — ORDER BY RANDOM() は大テーブルで崩壊する

「テーブルからランダムに10件」と言われて多くの人が書くのは ORDER BY RANDOM() LIMIT 10 (MySQL は RAND())。数千行なら平気ですが、数千万行になると致命的に遅い

理由: ORDER BY RANDOM()全行にランダム値を振って全体ソートする必要があり、テーブル全件スキャン+巨大ソートが発生する。インデックスが一切使われないので逃げ場がない。

解法1: TABLESAMPLE (PostgreSQL / SQL Server)

PostgreSQL と SQL Server には SQL 標準の TABLESAMPLE 句があります。物理ブロック単位で抽出するので超高速。

欠点: 厳密な均等サンプリングではない。ブロック単位のためブロック密度の偏りが結果に影響する(BERNOULLI は行単位ランダムで理論的にはフェアだが、実装により挙動差)。統計用途より「とりあえず眺めたい」用途に向きます。

sql
-- PostgreSQL: 約1%をサンプリング
SELECT * FROM orders TABLESAMPLE SYSTEM (1);

-- より均等だが遅い: BERNOULLI
SELECT * FROM orders TABLESAMPLE BERNOULLI (1);

-- 件数指定したいなら LIMIT
SELECT * FROM orders TABLESAMPLE SYSTEM (5) LIMIT 10;

解法2: ID レンジからランダム選択

id が連番に近いなら、アプリ側で乱数を作って IN で拾うのが最速。インデックス1回ルックアップで済みます。

ID に大きな欠番がある場合は、足りなかった分を再抽選 or 多めに引いて LIMIT で切る、等の工夫が必要。

sql
-- アプリで random IDs を作って投げる
SELECT * FROM users
WHERE id IN (:id1, :id2, :id3, :id4, :id5);

-- SQL 内で完結させる: max id から範囲乱数 (PostgreSQL)
SELECT * FROM users
WHERE id >= (
  SELECT FLOOR(RANDOM() * (SELECT MAX(id) FROM users))
)
ORDER BY id
LIMIT 10;

解法3: RANDOM() を WHERE に (フィルタ型)

WHERE RANDOM() < 0.01 で各行を1%の確率で採用する方式。ソートが不要なので全行走査でも ORDER BY RANDOM() より速い。

ただし件数が厳密に決まらない(確率1%なら期待値だけ、±揺れる)。少し多めに採って LIMIT で切る運用が実用的。

sql
SELECT * FROM events
WHERE RANDOM() < 0.001  -- 約0.1%
LIMIT 100;

注意点 — 乱数関数の RDBMS 差

関数名が RDBMS で違います:

  • PostgreSQL / SQLite: RANDOM()
  • MySQL / SQL Server / Oracle: RAND() (SQL Server は NEWID() でソートするのが伝統手法)

また RAND()MySQL では各行ごとに再評価されない場合がある(同じ値が返る)バグ的挙動が過去にあり、RAND(RAND()) と重ねる等の workaround が書かれた古いコードがある。現行は基本的に大丈夫ですが、古い DB では挙動確認を。

統計的に厳密な均等サンプリングが必要な用途(A/B テスト配分、母集団推定等)では、TABLESAMPLE のブロックサンプリングは使わず、行単位ランダム(BERNOULLI や WHERE RANDOM())を選ぶこと。

関連トピック