InnoDBのNULL
2026/09/13

SQLのNULLは「0」でも「空文字」でもなく、「値が存在しない」という状態です。InnoDBはこの状態をデータ領域を使わずに表現します。

NULLビットマップ

COMPACT系の行フォーマット(COMPACT・DYNAMIC・COMPRESSED)では、Nullableなフィールドの分だけレコード内にNULLビットマップを持ちます。行フォーマットREDUNDANTではこのビットマップを持たず、各フィールドのオフセット情報にNULLかどうかを書き込みます。ビットマップのサイズは、Nullableなフィールド数を8で割って切り上げたバイト数です。0個なら0バイト、1〜8個なら1バイト、9〜16個なら2バイトと増えます。

各Nullableフィールドにはビットが1つ割り当てられ、フィールドの定義順に並びます。ビットが1ならそのフィールドはNULL、0なら非NULLです。NULLのフィールドには長さ情報もデータも格納されません。レコードを走査するときは、Nullableフィールドに来るたびにビットマップを見て、ビットが1ならそのフィールドを読み飛ばします。

NULLビットマップは、データ先頭であるORIGINから見てレコードヘッダー側にあります。ヘッダーは5バイトで、その直前にビットマップが置かれます。

(アドレスの小さい側)可変長フィールドの長さ情報NULLビットマップレコードヘッダー(5バイト)フィールド1のデータフィールド2のデータ(NULLなら省略)…(アドレスの大きい側)record startORIGIN

図では、上へ行くほどアドレスが小さく、下へ行くほど大きくなります。record startとORIGINは、どちらも領域の内部ではなく、隣接する領域の境界を指しています。record startはレコードの最もアドレスの小さい位置、つまり先頭バイトの手前の境界で、可変長フィールドがあればここから長さ情報が並び始めます。ORIGINは、メタデータ(可変長フィールドの長さ情報・NULLビットマップ・レコードヘッダー)とフィールドデータ本体とのあいだの境界で、フィールド1のデータはORIGINのすぐ下(アドレスの大きい側)から始まります。

ALTER TABLE ADD COLUMNとビットマップ

ALTER TABLE ... ADD COLUMNでは、ALGORITHM=INSTANTによってテーブル全体の再構築を避けられる場合があります。この方式をInstant ADDと呼びます。既存の行は物理的に書き換えず、新カラムの値はデフォルトとして参照され、新しく挿入する行だけが新カラムを物理レコードに含めます。一方、再構築が行われる場合は、全レコードを新しい構造で書き直すため、NULLビットマップも新スキーマに合わせて作り直されます。

メタデータの登録

Instant ADDされたカラムのデフォルト値は、カラム定義に紐づくデフォルト情報として保持されます。保持場所は、永続的にはデータ辞書(テーブル定義を格納するシステム領域)、実行時にはテーブルを開いたときメモリへ読み込まれる辞書キャッシュ内のカラム定義です。DEFAULT NULLの場合は、長さをUNIV_SQL_NULLとして登録します。UNIV_SQL_NULLはメモリ上でフィールドの長さとしてNULLを表す定数で、データへのポインタは無効です。オフセット構築やフィールド取得のときに参照されます。

既存レコード(Instant ADD前に挿入されたもの)

既存レコードの物理データには新カラムの領域がありません。レコードを読むたびに作られるオフセット配列(各フィールドの位置やNULLかどうかを保持するメモリ上の構造)を構築する処理で、そのカラムが物理レコード内にないと分かると、インデックス側のデフォルト情報を参照してオフセットを決めます。デフォルトがNULLなら、該当フィールドにデフォルト値であるというフラグREC_OFFS_DEFAULTとNULLであるというフラグREC_OFFS_SQL_NULLが立ちます。物理レコード上のビットマップは書き換えず、オフセット配列のフラグでデフォルトでありNULLであることを表現し、フィールド取得時は長さをUNIV_SQL_NULLとして返します。

このオフセット配列は永続化されず、SELECTで行を返すとき・レコード比較時・特定フィールドの取得時など、物理レコードにアクセスするたびにメモリ上に作り直されます。

新規レコード(Instant ADD後に挿入されたもの)

新規挿入では、新カラムを含めた物理レコードが作られます。InnoDBはページへ書き込む前に、挿入する行を、各フィールドの値とその値がNULLかどうかをまとめたタプルという中間的な形でメモリ上にいったん保持します。NULLのフィールドは長さをUNIV_SQL_NULLとします。これをページ上の物理レコードへ変換するとき、Nullableなフィールドを先頭から順に見ていき、NULLのフィールドがあればNULLビットマップの対応するビットを1にします。DEFAULT NULLの新カラムがNULLで挿入される場合も、通常のNullableフィールドと同じように該当ビットが1になり、データ部にはそのフィールドを置かず、ビットマップだけでNULLを表します。

セカンダリインデックスのNULL

セカンダリインデックスのキーには、Nullableなカラムを含められます。

UNIQUE制約を設定したインデックスでも、キー列にNULLを含む行は何行でも挿入できます。一意性のチェックではNULL同士を等しいとみなさないため、重複違反には該当しません。

インデックスのB-treeを走査・構築する際の比較処理では、メモリ上のフィールド長がUNIV_SQL_NULLかどうかでNULLを判定します。NULLと非NULL値を比較するとき、昇順(ASC)のインデックスではNULLを最小値として扱い、降順(DESC)では最大値として扱います。比較する両方のフィールドがNULLの場合は等価とみなします。