見積り行数はどこから来るのか(rows=850 を最後まで分解する)
統計情報がないテーブルの見積り行数は、ページ数の下限(10 ページ)と、型ごとの既定の幅から計算した 1 ページあたりの行数の積で決まる。PostgreSQL 本体の estimate_rel_size() がその計算をしている。
3 行しかないテーブルで、850 行と見積もられる
テーブルを作って 3 行だけ入れ、EXPLAIN を打ちます。
CREATE TABLE estimate_demo (
sensor_id TEXT, room_name TEXT, floor INT
) WITH (autovacuum_enabled = false);
INSERT INTO estimate_demo VALUES
('s1','room-a',1), ('s2','room-b',2), ('s3','room-c',3);
EXPLAIN SELECT * FROM estimate_demo; Seq Scan on estimate_demo (cost=0.00..18.50 rows=850 width=68)3 行しか入っていないのに rows=850 と出ている。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
rows=850。3 行しか入っていないのにです。 そして cost=18.50。
この 850 も 18.50 も、当てずっぽうではありません。最後まで計算で再現できます。
手順 1 — 統計が無いことを確認する
SELECT relpages, reltuples FROM pg_class WHERE relname = 'estimate_demo'; -- relpages | reltuples -- ----------+----------- -- 0 | -1
relpages = 0、reltuples = -1。-1 は「0 行」ではなく「まだ調べていない」の印です。ANALYZE を一度も打っていないので、統計がありません。
手順 2 — ページ数は 10 とみなす
実ファイルは 1 ページしかありませんが、10 ページ未満かつ未調査なら 10 ページとして扱うという下限があります。
作ったばかりのテーブルを 0 行と見積もると、 そのテーブルを使う計画が極端に安く見えてしまうためです。
手順 3 — 1 行の幅を型から決める
統計が無いので、実際のデータは見ません。型ごとの既定値を使います。
text= 32 バイト(可変長なので決め打ち)int= 4 バイト
text が 2 つと int が 1 つなので、32 + 32 + 4 = 68。出力の width=68 と一致します。
手順 4 — 1 ページに何行入るか
(8192 - 24) ÷ (68 + 24 + 4) = 8168 ÷ 96 = 85.08... → 85 行
- 8192 — 1 ページの大きさ(8KB)
- 24 — ページの先頭にある管理領域
- 68 — さっき出した 1 行の幅
- 24 — 行ごとの管理領域
- 4 — ページ内で行の位置を指すポインタ
小数は切り捨てて 85 行。
手順 5 — 掛ける
85 行/ページ × 10 ページ = 850 行
rows=850 が出ました。
手順 6 — コストも同じ前提から出る
10 ページ × 1.0(ページを順に読む) + 850 行 × 0.01(1 行を処理する) = 10 + 8.5 = 18.50
cost=0.00..18.50 も一致しました。コストの単位の話はcost と rows の意味にあります。
この計算は PostgreSQL 本体の estimate_rel_size()(src/backend/optimizer/util/plancat.c)がやっていることを、そのままなぞったものです。 ソースを読める人は、そこを見ると同じ式が書いてあります。
列を変えると数字が動く
手順 3〜5 は列の型だけで決まります。実際に測った値を並べます。
| 列 | width | 1 ページの行数 | rows | cost |
|---|---|---|---|---|
| a BOOLEAN | 1 | 281 | 2810 | 38.10 |
| a INT | 4 | 255 | 2550 | 35.50 |
| a BIGINT | 8 | 226 | 2260 | 32.60 |
| a TEXT | 32 | 136 | 1360 | 23.60 |
| a TEXT, b INT | 36 | 127 | 1270 | 22.70 |
| a TEXT, b TEXT, c INT | 68 | 85 | 850 | 18.50 |
| a TEXT, b TEXT, c TEXT | 96 | 65 | 650 | 16.50 |
幅が広いほど 1 ページに入る行が減り、見積り行数も減ります。どの行も (8192 - 24) ÷ (width + 28) × 10 で計算できます。
この数字は信用できない
10 ページは下限であって決め打ちではない
2 万行入れて ANALYZE を打たずに EXPLAIN すると、 実ファイルの 345 ページがちゃんと使われます。
Seq Scan on big_noanalyze (cost=0.00..783.15 rows=43815 width=36)実ページ数 345 は使われている。外れているのは width。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
ページ数は正しいのに、行数は 43,815(実際は 20,000)。 原因は width=36 です。memo 列に 100 文字入れているのに、 統計が無いので text = 32 バイトと踏んでいます。
幅の外れが、そのまま行数の外れに連鎖します。(8192-24) ÷ (36+28) = 127 行/ページ × 345 ページ = 43,815。 計算は合っていて、入力が間違っているだけです。
ANALYZE すると推測が実測になる
ANALYZE big_noanalyze;
Seq Scan on big_noanalyze (cost=0.00..545.00 rows=20000 width=105)行数も幅も実測に置き換わった。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
rows=43815 width=36 が rows=20000 width=105 になりました。 3 行のテーブルでも同じです。
ANALYZE estimate_demo; EXPLAIN SELECT * FROM estimate_demo;
Seq Scan on estimate_demo (cost=0.00..1.03 rows=3 width=14)ANALYZE 後。850 が実測の 3 になった。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF
rows=850 width=68 が rows=3 width=14 になりました。幅も推測をやめて実データの平均長を使います。text は 32 バイト決め打ちではなく、実際に入っている's1'(2 文字 + 長さ 1 バイト = 3)と'room-a'(6 文字 + 1 = 7)で測られ、3 + 7 + 4 = 14 になります。
推測に使っていた下限も型ごとの既定幅も、もう使われません。統計が実測を持っているからです。
実験するときの注意
「統計が無い状態」は放っておくと勝手に消えます。自動で統計を取る仕組みが動くためです。ただしどのテーブルでも消えるわけではありません。
- 3 行のテーブル — 消えません。自動で統計を取る条件は「変更された行数が 50 + 全体の 10% を超えたら」なので、 3 行では届きません
- 2 万行のテーブル — 消えます。実測では
INSERTの 18 秒後に統計が取られていました
だから 2 万行の実験をするときは、テーブルを作るときにWITH (autovacuum_enabled = false) を付けます。付けないと、観察する前に統計が付いて再現しません。
統計があっても外れることはある
ここまでは「統計が無い」場合でした。統計があっても見積りは外れます。
遅いノードの見つけ方の題材では、 統計がある状態で 475 倍外れています。 条件が 4 つあり、それぞれの選択率を掛け算しているためです (実際には 4 つが連動しているので、掛けると小さくなりすぎる)。
同じ計画の内側にも、もう 1 つ外れているノードがあります。rows=23 と見積もって実測は 2 行。 こちらは1 つの注文に何行の明細があるかの見積りが外れているためです。 1200 万行をサンプリングして異なり数を数えているので、異なり数を少なく見積もると、1 件あたりの行数が多く見えます。
よくある疑問
もっと学びたい方へ(おすすめ書籍)
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 コース受講)