ハッシュ関数でキーからバケットを直接計算し、平均 O(1) で検索・挿入・削除を実現する静的ハッシュインデックス
ハッシュインデックスは、ハッシュ関数を使ってキーからデータの格納位置を直接計算するインデックス構造です。B-Treeがルートから枝を辿って目的のデータにたどり着く「木構造型」の索引であるのに対し、ハッシュインデックスは計算一発で格納場所を特定する「直接アドレッシング型」の索引です。内部的にはバケット(固定容量のスロット配列)を持ち、ハッシュ関数の出力値をバケット番号としてデータを振り分けます。
日常的な例で考えてみましょう。500ページの本から特定の単語を探すとき、B-Treeは「索引のページを開いて、該当する範囲を絞り込んでいく」方法です。一方ハッシュインデックスは、「単語を計算式に入れると即座にページ番号が出てくる」方法です。途中の比較や絞り込みが一切不要で、一発で目的地にたどり着けます。
技術的に見ると、ハッシュ関数は任意の入力を固定範囲の整数値に変換する決定的関数です。「決定的」とは、同じ入力に対して必ず同じ出力を返すこと。最も基本的なハッシュ関数は h(key) = key mod N(Nはバケット数)で、割り算の余りをそのままバケット番号として使います。
この仕組みにより、ハッシュインデックスは平均O(1)(定数時間)で等価検索を実現します。B-Treeの検索がO(log n)であるのに対し、ハッシュインデックスはデータ量に依存しません。1万件でも1億件でも、ハッシュ値を1回計算してバケットにアクセスするだけです。これは配列の添字アクセス array[i] がO(1)であるのと同じ原理で、ハッシュ値がそのまま配列のインデックスになります。
ただし、重要なトレードオフがあります。ハッシュ関数は入力値の大小関係(順序)を保存しません。h(100)=0, h(101)=1, h(102)=2 のように連続する保証はなく、h(104)=0 のように飛びます。このため範囲検索には使えません。
また、複数のキーが同じバケットに割り当てられる「衝突(collision)」が避けられず、衝突が増えると検索性能がO(1)からO(n)に劣化します。そのためバケット数やバケット容量の設計が性能を左右します。
実務では、等価検索が支配的なワークロードで広く採用されています。
ハッシュインデックスは、ハッシュ関数とバケット配列の2つで構成されます。キーをハッシュ関数に通してバケット番号を得て、そのバケットにデータを格納・検索します。下の図で全体の流れを見てみましょう。
上のツールでは簡略化のために整数のキーを直接 mod N していますが、実際のデータベースでハッシュインデックスの対象になるのはテーブルのカラム値です。ユーザーIDのような整数だけでなく、メールアドレスのような文字列、UUIDなどあらゆるデータ型がハッシュの入力になり得ます。
では文字列のような非数値データはどうやってバケット番号に変換するのでしょうか? 答えは2段階のハッシュです。
バケット数4、容量3の設定で、キー 5, 12, 42, 37 を順に挿入する例で手順を見ていきましょう。

ディレクトリを倍増させて動的にバケットを拡張するハッシュ方式

小テーブルからハッシュテーブルを構築し大テーブルでプローブする等価結合アルゴリズム

メモリに収まらないテーブルをパーティション分割してディスク上でHash Joinする手法

Grace Hash Joinの改良版。1パーティションをメモリに保持してI/Oを30-50%削減

カラム値ごとにビット配列を持ちAND/OR/NOTをビット演算で高速処理するインデックス

テーブル統計情報を元に複数の実行プランのコストを比較し最小コストのプランを選択する最適化手法