LIMITで件数制限
1000 行受け取って、使うのは 10 行
一覧画面を作るとき、テーブルの行を全部取り出してから、画面に出す分だけアプリ側で切り出す書き方をしてしまうことがあります。行が 100 件のうちは動きます。10 万件になった日に、通信量もメモリも 1000 倍になって止まります。
必要な件数だけをデータベースに返してもらう句が LIMIT です。
SQL クエリ
SELECT id, name
FROM shops
LIMIT 10;shops に何万行入っていても、返ってくるのは 10 行です。行数の分からないテーブルを触るときは、まず LIMIT 10 を付けて中身を覗く。これは事故を防ぐ意味でも役に立つ癖です。
件数を絞るのは、画面のためだけではありません。ランキングの上位だけが欲しい、新着のいくつかだけを出したい、という要件は、どれも「並べたうえで先頭から何件か」という同じ形に落ちます。
「先頭 10 件」は毎回同じ 10 件とは限らない
ただし上のクエリには問題があります。並び順を指定していないので、どの 10 行が返るかはデータベース任せです。同じクエリを 2 回実行して違う行が返ることも、テスト環境と本番で挙動が変わることもあります。
LIMIT は「並んだ結果の先頭から何件か」を切り取る句です。先に並びを決めておかないと、切り取る場所そのものが決まりません。
SQL クエリ
SELECT id, name, opened_at
FROM shops
ORDER BY opened_at
LIMIT 10;開店日の古い順に並べてから、先頭 10 件を取っています。LIMIT を書いたら ORDER BY も書く、とセットで覚えてしまうのが安全です。この種のバグは「たまに順番が違う」という形でしか出ないので、報告を受けても再現できず、原因にたどり着くまでに何日もかかります。
2 ページ目から先へ進む
1 ページ 20 件の一覧で 3 ページ目を出したいときは、先頭 40 件を読み飛ばしてから 20 件取ります。読み飛ばす件数を指定するのが OFFSET です。
SQL クエリ
SELECT id, name
FROM shops
ORDER BY opened_at
LIMIT 20 OFFSET 40;飛ばす件数は (ページ番号 - 1) × 1 ページの件数で決まります。3 ページ目なら 2 × 20 で 40 です。アプリから (page - 1) * pageSize を渡せば、そのまま動きます。1 ページ目は OFFSET 0 になるので、OFFSET を省いた形と同じです。
現場の話
OFFSETは、飛ばす行を読まずに済ませているわけではありません。OFFSET 100000は先頭 10 万行を読んでから捨てて、その次の行を返します。後ろのページほど遅くなるので、件数の多い一覧では「直前のページの最後の id より小さい行を 20 件」のように、位置を鍵で指定する方式に切り替えます。
テーブル構造
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
views INT NOT NULL,
published_at DATE NOT NULL
);
INSERT INTO articles (id, title, views, published_at) VALUES
(1, 'SQL 入門', 1500, '2026-01-10'),
(2, 'JOIN の使い方', 3200, '2026-02-05'),
(3, 'インデックス設計', 2800, '2026-02-12'),
(4, 'トランザクション基礎', 1800, '2026-03-01'),
(5, '正規化と非正規化', 4500, '2026-03-15'),
(6, 'ウィンドウ関数入門', 5200, '2026-04-02'),
(7, 'クエリチューニング', 6100, '2026-04-20'),
(8, 'NoSQL との比較', 2200, '2026-05-01'),
(9, 'CTE と再帰クエリ', 3800, '2026-05-10'),
(10, 'パーティショニング', 1900, '2026-05-15');期待される出力
| id | title | views |
|---|---|---|
| 7 | クエリチューニング | 6100 |
| 6 | ウィンドウ関数入門 | 5200 |
| 5 | 正規化と非正規化 | 4500 |