コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB

統計情報とカーディナリティ

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

統計情報とカーディナリティ

このレッスンで分かること

  • 統計情報がプランナにとって何を意味するか
  • カーディナリティ・選択率・ヒストグラムの関係
  • 統計を最新化する運用テクニック

統計情報とカーディナリティ とは

プランナがコストを計算するために使う統計の仕組み。本レッスンでは、統計情報とカーディナリティ の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

カーディナリティと選択率

カーディナリティ (Cardinality) とは、あるカラムに含まれる 異なる値の数 です。

カラムカーディナリティ
user_id1,000,000全ユーザー数
email1,000,0001 ユーザー 1 メアド
age1000 〜 100 歳
gender2M / F
is_deleted2true / false

カーディナリティが高いほど、インデックスが効きます。gender = 'F' は半分の行を返す可能性があり、インデックスを使うより全表走査の方が速い、ということもあります。

選択率 (Selectivity) はカーディナリティの逆数で、「フィルタを通過する行の割合」を意味します。

  • email = ? の選択率は 1 / 1,000,000 ≒ 0
  • gender = ? の選択率は 1/2 = 0.5

選択率が低い(数字が小さい)ほど、インデックスが速くなります。

プランナが行数を見積もる仕組み

SELECT * FROM users WHERE age > 30 AND country = 'JP' を考えます。プランナは次の手順で行数を見積もります。

  1. テーブルの総行数 を統計から取得 → 1,000,000
  2. country='JP' の選択率 をヒストグラムから推定 → 0.3
  3. age > 30 の選択率 をヒストグラムから推定 → 0.5
  4. 独立と仮定して掛け算 → 1,000,000 × 0.3 × 0.5 = 150,000

これがプランナの推定行数です。実際の行数とこれが大きくズレていると、悪い計画が選ばれます。

diagram (will load when visible)

ヒストグラム

カラムの値分布を粗く記録したものが ヒストグラム です。例えば age カラムの分布を 10 階級に分けて、各階級の頻度を保持します。

プレーンテキスト

age 0-10 : 5000 行 age 10-20 : 30000 行 age 20-30 : 80000 行 age 30-40 : 50000 行 ...

WHERE age > 30 ならその右側を合計して推定値を出します。ヒストグラムがないとカラムの最小・最大から線形補間するため、偏りに弱くなります。

統計情報の中身

主要 DB が持つ統計情報の例です。

  • テーブル行数
  • カラムごとのカーディナリティ (distinct count)
  • カラムごとの最小・最大値
  • NULL 率
  • ヒストグラム (PostgreSQL は標準、MySQL も 8.0 でサポート)
  • 複数カラムの相関 (PostgreSQL の CREATE STATISTICS)

これらは pg_stats (PostgreSQL) や、ヒストグラムは INFORMATION_SCHEMA.COLUMN_STATISTICS・インデックスのカーディナリティは INFORMATION_SCHEMA.STATISTICS (いずれも MySQL) で確認できます。

統計が古くなると何が起きるか

統計は テーブル作成時、ANALYZE 実行時、自動メンテナンス時 に更新されます。データが大幅に変わった後で統計が古いと、悲惨な計画が選ばれます。

実例として「テーブル作成直後に大量挿入したが ANALYZE を忘れた」場合、プランナは「テーブルは空」と勘違いし、Nested Loop で巨大テーブルをスキャンします。クエリが数時間止まる事故もよく起きます。

大量データ挿入の 直後ANALYZE を実行する習慣をつけよう。

統計更新の運用

PostgreSQL

autovacuumVACUUM と一緒に ANALYZE も実行します。デフォルトはテーブルの 10% が変更されたら走ります。テーブルが極端に大きい / 小さい場合はテーブル単位で閾値を調整します。

SQL クエリ

ALTER TABLE big_table SET (autovacuum_analyze_scale_factor = 0.01); -- 1% で起動

MySQL InnoDB

innodb_stats_auto_recalc で自動再計算します。サンプリングが粗いことがあるので、重要テーブルは ANALYZE TABLE を明示的に実行することもあります。

SQL クエリ

ANALYZE TABLE users;

サンプリングの落とし穴

統計は 全行スキャン ではなく サンプリング で取られます。デフォルトでは少なくとも 1 万〜数十万行が対象で、極端な偏りはサンプルから漏れることがあります。

サンプル外れ値が多い場合は、PostgreSQL の default_statistics_target を上げます(デフォルト 100 → 1000)。これでヒストグラムのビン数が増え、精度が上がります。

複数カラムの相関問題

(country, city) を独立と仮定すると、country='JP' AND city='Tokyo' の推定が外れます。city='Tokyo' の行は ほぼ確実に country='JP' だからです。

PostgreSQL は CREATE STATISTICS で相関も記録できます。

SQL クエリ

CREATE STATISTICS stat_geo (dependencies) ON country, city FROM users; ANALYZE users;

これで「city が決まれば country もほぼ決まる」という関係を統計に含められます。

EXPLAIN で統計のズレを発見

EXPLAIN ANALYZErows=80 (actual rows=8000) のような乖離は統計エラーのサインです。

推定 / 実測解釈
推定 << 実測統計が古い、または相関を見落とし
推定 >> 実測データが減ったのに統計が古い
ほぼ一致統計は健全

統計の精度がクエリ性能を決める。「インデックスを貼ったのに遅い」の犯人は統計が古いことが多い。

やってみよう

  • PostgreSQL で SELECT * FROM pg_stats WHERE tablename = 'users' を実行し、自分のテーブルのヒストグラムを観察する
  • MySQL で ANALYZE TABLE 前後の EXPLAIN 出力を比較する
  • 1 万件挿入直後に ANALYZE を実行しないと推定がどう外れるか実験する

よくある質問

Q. このトピックは実務でどう役立ちますか?

A. DB のクエリ最適化、API 設計、データ構造の選択など、設計判断の根拠になります。表面的にライブラリを使うだけでなく「なぜそれが速いのか」を理解できると、性能問題を未然に防げます。コーディング面接でも頻出のテーマです。

Q. 計算量はどう求めれば良いですか?

A. ループのネストごとに掛け算する、再帰なら漸化式から解く、というのが基本です。Big-O 表記は定数倍と低次の項を無視するため、n の指数(n²、n log n など)に注目してください。最悪・平均・最良の 3 つを意識すると説得力が増します。

Q. 覚えるべき定番アルゴリズムは何ですか?

A. 二分探索、クイックソート/マージソート、BFS/DFS、ダイクストラ、DP の基本問題(フィボナッチ・ナップサック)が必修です。これらを「白紙から書ける」状態にすると、応用問題が一気に解けるようになります。

次のレッスン

次は レプリケーション で、1台のDBの変更を複数のコピーへ反映する仕組みと、同期方式の違い・レプリカラグが生む整合性問題を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 統計情報とカーディナリティ の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 統計情報とカーディナリティ とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン