スキャンの種類(Seq / Index / Index Only / Bitmap)
Seq Scan はテーブルを先頭から読む方法、Index Scan はインデックスを辿って 1 行ずつ取りに行く方法、Index Only Scan はインデックスだけで完結してテーブルを読まない方法、Bitmap Heap Scan は該当する行の位置をいったんビットマップに集めてからページ順にまとめて読む方法である。
4 つの表記
テーブルからデータを取り出すノードには 4 つの顔があります。どれが出ているかで、何をしているかが分かります。
先に用語を 1 つ。この先に出てくる Heap(Bitmap Heap Scan /Heap Blocks / Heap Fetches)はテーブル本体のことです。インデックスと区別するための呼び名で、「Heap を読んだ」=「テーブル本体を読んだ」と読み替えて構いません。
Seq Scan — 先頭から最後まで読む
が条件に合う行。読んだページ: 8 / 8(該当行が 2 ページ分でも、全部読む)
Seq Scan on members (cost=0.00..10417.00 rows=500000 width=34) (actual time=0.006..22.839 rows=500000.00 loops=1)
Filter: (age >= 20)
Rows Removed by Filter: 0
Planning Time: 0.145 ms
Execution Time: 33.422 ms50 万行を全部読む。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
テーブルを順番に全部読みます。インデックスが使えないとき、あるいは使わない方が速いと判断されたときに選ばれます。
Index Scan — インデックスを辿って 1 行ずつ取りに行く
が条件に合う行。読んだページ: 3 回(3 ページ目を 2 回読んでいる)
Index Scan using members_pkey on members (cost=0.42..8.44 rows=1 width=34) (actual time=0.015..0.016 rows=1.00 loops=1)
Index Cond: (id = 42)
Index Searches: 1
Planning Time: 0.253 ms
Execution Time: 0.046 ms主キーで 1 行だけ引く。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
Index Cond: に条件が乗っているのが目印です。 インデックスで場所を特定してから、その行があるページを読みに行きます。
該当行が多いと不利になります。行ごとにページをバラバラに読むことになるためです。
Index Only Scan — テーブルを読まない
が条件に合う行。読んだページ: 0(必要な値がインデックスに入っている)
SELECT city FROM members WHERE city = 'city-7';
取り出すのが city だけで、 その city にインデックスが張ってあります。欲しい値がインデックスの中に全部あるので、テーブル本体に用がありません。
Index Only Scan using members_city_idx on members (cost=0.42..213.17 rows=10100 width=7) (actual time=0.032..0.435 rows=10000.00 loops=1)
Index Cond: (city = 'city-7'::text)
Heap Fetches: 0
Index Searches: 1
Planning Time: 0.278 ms
Execution Time: 0.689 msHeap Fetches: 0。テーブル本体を 1 回も触っていない。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
必要な列が全部インデックスに入っているときだけ選ばれます。 テーブル本体を読まないので、いちばん速い形です。Heap Fetches: 0 が「1 回も触っていない」印です。
これを狙って作るのがカバリングインデックスです。遅いノードの見つけ方では、 実際にこれで内側を 5 分の 1 にしています。
Bitmap Heap Scan — 位置を集めてから、まとめて読む
が条件に合う行。読んだページ: 2(重複が消え、左から右へ 1 回ずつ)
Bitmap Heap Scan on members (cost=114.70..4407.95 rows=10100 width=34) (actual time=0.815..5.297 rows=10000.00 loops=1)
Recheck Cond: (city = 'city-7'::text)
Heap Blocks: exact=4167
-> Bitmap Index Scan on members_city_idx (cost=0.00..112.17 rows=10100 width=0) (actual time=0.507..0.507 rows=10000.00 loops=1)
Index Cond: (city = 'city-7'::text)
Index Searches: 1
Planning Time: 0.209 ms
Execution Time: 5.571 msBitmap Index Scan で位置を集め、Bitmap Heap Scan でページ順に読む。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
2 段構えになっているのが特徴です。
Bitmap Index Scanがインデックスを辿って、該当行の位置だけを集めるBitmap Heap Scanが、集めた位置をページ順に並べ替えて読む
Index Scan がインデックスを引くたびにテーブルへ飛ぶのに対して、先に位置を全部集めてから、ページ順にまとめて読むのが違いです。 該当行が「そこそこ多い」ときに選ばれます。
ここで Heap Blocks: exact=4167 の意味を確かめてください。これは実際に読んだページ数です。この例が返しているのは 50 万行のうち 1 万行(2%)だけなのに、読んだページ数は 4,167 ページ —— 上の Seq Scan がテーブル全体を読んだのと同じ数です。
Bitmap は「読むページを減らす」方式ではありません。city の値がテーブル全体に散らばっているので、結局どのページにも 1 行以上入っているからです。 Bitmap が減らしているのはページを行き来する回数で、読む順番を整える方式だと考えるのが正確です。
ページ数まで減るのは、該当行が固まって置かれているときです。 行の物理的な並びがインデックス順にどれだけ近いかで効果が変わります(クラスタ化インデックス)。
Recheck Cond: が出るのは、 ビットマップが粗くなったときにページ単位で持つことがあり、 そのとき行を読んでから条件をもう一度確かめるためです。 この行の読み方はIndex Cond と Filter の違いに。
何 % を超えると全表スキャンになるか
実際に測りました。50 万行のテーブルで、条件に合う行の割合を少しずつ上げていきます。
| 条件 | 見積り行数 | 全体比 | 選ばれたスキャン |
|---|---|---|---|
| age < 21 | 8,683 | 2% | Bitmap Heap Scan |
| age < 25 | 41,100 | 8% | Bitmap Heap Scan |
| age < 30 | 83,633 | 17% | Bitmap Heap Scan |
| age < 40 | 165,400 | 33% | Bitmap Heap Scan |
| age < 45 | 207,800 | 42% | Bitmap Heap Scan |
| age < 50 | 250,550 | 50% | Bitmap Heap Scan |
| age < 52 | 267,083 | 53% | Seq Scan |
| age < 55 | 292,933 | 59% | Seq Scan |
| age < 60 | 334,517 | 67% | Seq Scan |
| age < 80 | 500,000 | 100% | Seq Scan |
50% 前後で切り替わりました。250,550 行(50.1%)までは Bitmap Heap Scan、267,083 行(53.4%)から Seq Scan です。
つまり「インデックスがあるのに Seq Scan になっている」のは、 たいてい正しい判断です。半分読むなら順に読んだ方が速いからです。
この境目は環境で動きます。バラバラに読む手間を順に読む手間の何倍と仮定するか、という設定値が効くためです。 既定では 4 倍と仮定していますが、SSD ではもっと近く、 キャッシュがよく効いていればさらに近くなります。「何 % で切り替わるか」を暗記するのではなく、 自分の環境で測るのが正しい向き合い方です。
インデックスがあるのに使われないとき
ここまでは「対象行が多いから使わない」という正しいケースでした。 それ以外の理由で使われないことも多くあります。 列に関数をかけた、型が合っていない、複合インデックスの左端を使っていない、など。
そちらはインデックスが使われないときに何を見るかに まとまっています。
よくある疑問
関連トピック
もっと学びたい方へ(おすすめ書籍)
PostgreSQLの内部構造・ストレージ・インデックス機構を丁寧に解説。設計と運用計画の鉄則が学べる。
テーブル設計と正規化、パフォーマンス考慮のインデックス設計まで実務レベルで学べる定番書。第2版ではクラウド対応も強化。
ER 図をどう「使える設計」に落とすか、実務の判断まで踏み込んだ入門書。エンティティの切り出しから多対多の扱いまで具体例が豊富。
ドリル 256 問を実際に打ちながら進める SQL の入門書。付属のブラウザ環境で演習できるので、SELECT から結合・集約までを環境構築で止まらずに通せる。
SQLの本質的な使い方と、インデックスが効くクエリの書き方を学べる。ウィンドウ関数など現代SQLも網羅。
実務でやりがちなSQL・DB設計のアンチパターンとその回避策を体系的に学べる。
IPAデータベーススペシャリスト試験の総合対策書。インデックス関連は本サイトと合わせて学ぶと理解が深まる。
リレーショナルモデルの理論から、インデックス設計を含む実務で使えるSQLまで解説。
「なぜこの書き方が速いのか」を実行計画から説明する一冊。条件分岐・集約・結合・更新のそれぞれで、良い書き方と悪い書き方を対比しながら読める。
本セクションはAmazonアソシエイトのリンクを含みます。
もっと深くDBを学びたい方へ。
たいてっくが、SQL・データベース設計・パフォーマンスチューニング・IPAデータベーススペシャリスト対策まで、1対1で学習をサポートします。まずは無料相談から。
「教え方も上手で、お人柄も良いメンターです。DB周りの知識はもちろん、何より、しっかり教えてあげようという姿勢がとてもありがたかったです。データベース、SQLの学習を考えている方にはおススメです。」
— H 様(DB・SQL コース受講)「体系的に知識を教えてくださり、実際の業務でも大変役立っております。特に短い時間で効率よく知識の習得や、練習をできているのは期待以上でした。」
— M 様(DB・SQL コース受講)「大変充実したコンテンツでわかりやすいご説明をありがとうございました。基本的な質問にも丁寧にご説明いただき、また業務のご相談にも乗って頂き大変有意義な時間でした。」
— K 様(DB・SQL コース受講)