発展数字の出どころ

見積り行数はどこから来るのか(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=8503 行しか入っていないのにです。 そして cost=18.50

この 850 も 18.50 も、当てずっぽうではありません。最後まで計算で再現できます。

手順 1 — 統計が無いことを確認する

SELECT relpages, reltuples FROM pg_class WHERE relname = 'estimate_demo';
--  relpages | reltuples
-- ----------+-----------
--         0 |        -1

relpages = 0reltuples = -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 は列の型だけで決まります。実際に測った値を並べます。

width1 ページの行数rowscost
a BOOLEAN1281281038.10
a INT4255255035.50
a BIGINT8226226032.60
a TEXT32136136023.60
a TEXT, b INT36127127022.70
a TEXT, b TEXT, c INT688585018.50
a TEXT, b TEXT, c TEXT966565016.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=36rows=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=68rows=3 width=14 になりました。幅も推測をやめて実データの平均長を使います。text は 32 バイト決め打ちではなく、実際に入っている's1'(2 文字 + 長さ 1 バイト = 3)と'room-a'(6 文字 + 1 = 7)で測られ、3 + 7 + 4 = 14 になります。

推測に使っていた下限も型ごとの既定幅も、もう使われません。統計が実測を持っているからです。

実験するときの注意

「統計が無い状態」は放っておくと勝手に消えます。自動で統計を取る仕組みが動くためです。ただしどのテーブルでも消えるわけではありません。

  • 3 行のテーブル — 消えません。自動で統計を取る条件は「変更された行数が 50 + 全体の 10% を超えたら」なので、 3 行では届きません
  • 2 万行のテーブル — 消えます。実測では INSERT18 秒後に統計が取られていました

だから 2 万行の実験をするときは、テーブルを作るときにWITH (autovacuum_enabled = false) を付けます。付けないと、観察する前に統計が付いて再現しません。

統計があっても外れることはある

ここまでは「統計が無い」場合でした。統計があっても見積りは外れます。

遅いノードの見つけ方の題材では、 統計がある状態で 475 倍外れています。 条件が 4 つあり、それぞれの選択率を掛け算しているためです (実際には 4 つが連動しているので、掛けると小さくなりすぎる)。

同じ計画の内側にも、もう 1 つ外れているノードがあります。rows=23 と見積もって実測は 2 行。 こちらは1 つの注文に何行の明細があるかの見積りが外れているためです。 1200 万行をサンプリングして異なり数を数えているので、異なり数を少なく見積もると、1 件あたりの行数が多く見えます。

よくある疑問

Q.reltuples が -1 なのは 0 行という意味ですか?
A.違います。-1 は「まだ調べていない」という印です。0 行と区別するためにこの値が使われています。ANALYZE を打つと実測値に置き換わります。
Q.統計が無いテーブルでも 10 ページと仮定されるのはなぜですか?
A.作ったばかりのテーブルに対して 0 行と見積もると、そのテーブルを使う計画が極端に安く見えてしまうためです。下限を置くことで、極端な計画が選ばれるのを防いでいます。
Q.読者の手元で数字が一致しません
A.ANALYZE はテーブル全体ではなくサンプリングで統計を取るので、実行するたびに数 % ずれます。大事なのは特定の数字ではなく、掛け算の手順の方です。

もっと学びたい方へ(おすすめ書籍)

[改訂3版]内部構造から学ぶPostgreSQL
勝俣智成 ほか

PostgreSQLの内部構造・ストレージ・インデックス機構を丁寧に解説。設計と運用計画の鉄則が学べる。

Amazon で見る →
おすすめ
達人に学ぶDB設計徹底指南書 第2版
ミック

テーブル設計と正規化、パフォーマンス考慮のインデックス設計まで実務レベルで学べる定番書。第2版ではクラウド対応も強化。

Amazon で見る →
おすすめ
楽々ERDレッスン (CodeZine BOOKS)
羽生章洋

ER 図をどう「使える設計」に落とすか、実務の判断まで踏み込んだ入門書。エンティティの切り出しから多対多の扱いまで具体例が豊富。

Amazon で見る →
おすすめ
スッキリわかるSQL入門 第4版 ドリル256問付き!
中山清喬/飯田理恵子

ドリル 256 問を実際に打ちながら進める SQL の入門書。付属のブラウザ環境で演習できるので、SELECT から結合・集約までを環境構築で止まらずに通せる。

Amazon で見る →
達人に学ぶSQL徹底指南書 第2版
ミック

SQLの本質的な使い方と、インデックスが効くクエリの書き方を学べる。ウィンドウ関数など現代SQLも網羅。

Amazon で見る →
SQLアンチパターン 第2版
Bill Karwin

実務でやりがちなSQL・DB設計のアンチパターンとその回避策を体系的に学べる。

Amazon で見る →
情報処理教科書 データベーススペシャリスト 2025年版
三好康之

IPAデータベーススペシャリスト試験の総合対策書。インデックス関連は本サイトと合わせて学ぶと理解が深まる。

Amazon で見る →
理論から学ぶデータベース実践入門
奥野幹也

リレーショナルモデルの理論から、インデックス設計を含む実務で使えるSQLまで解説。

Amazon で見る →
SQL実践入門 ── 高速でわかりやすいクエリの書き方
ミック

「なぜこの書き方が速いのか」を実行計画から説明する一冊。条件分岐・集約・結合・更新のそれぞれで、良い書き方と悪い書き方を対比しながら読める。

Amazon で見る →

本セクションはAmazonアソシエイトのリンクを含みます。

オンライン個別指導

もっと深くDBを学びたい方へ。

たいてっくが、SQL・データベース設計・パフォーマンスチューニング・IPAデータベーススペシャリスト対策まで、1対1で学習をサポートします。まずは無料相談から。

無料相談を予約する →
受講者の声 (DB・SQL コース)
menta レビュー原文を見る →
  • 教え方も上手で、お人柄も良いメンターです。DB周りの知識はもちろん、何より、しっかり教えてあげようという姿勢がとてもありがたかったです。データベース、SQLの学習を考えている方にはおススメです。
    H 様DB・SQL コース受講
  • 体系的に知識を教えてくださり、実際の業務でも大変役立っております。特に短い時間で効率よく知識の習得や、練習をできているのは期待以上でした。
    M 様DB・SQL コース受講
  • 大変充実したコンテンツでわかりやすいご説明をありがとうございました。基本的な質問にも丁寧にご説明いただき、また業務のご相談にも乗って頂き大変有意義な時間でした。
    K 様DB・SQL コース受講