このセクションでは、InnoDB テーブルの圧縮に関する一部の内部実装について詳細に説明します。 ここで示す情報は、パフォーマンスを調整する際に役立つことがありますが、圧縮の基本的な使用を理解する必要はありません。
圧縮アルゴリズム
一部のオペレーティングシステムでは、ファイルシステムのレベルで圧縮が実装されています。 一般に、ファイルは、可変サイズのブロックに圧縮される固定サイズのブロックに分割されるため、簡単に断片化されます。 ブロック内部で何かが変更されるたびに、ブロック全体が再圧縮されてからディスクに書き込まれます。 これらのプロパティーを使用すると、この圧縮方法が更新の多いデータベースシステムでの使用には適さなくなります。
MySQL では、LZ77 圧縮アルゴリズムが実装されている有名な zlib ライブラリの支援を得て、圧縮が実装されています。 この圧縮アルゴリズムは十分に発達し、強固であり、CPU の使用率とデータサイズの削減の両方の点で効率的です。 このアルゴリズムは「損失なし」であるため、常に、元の非圧縮データを圧縮形式から再構築できます。 LZ77 圧縮は、圧縮されるデータ内で繰り返される一連のデータを見つけることで動作します。 データ内の値のパターンによって、圧縮の効率性が決定されますが、多くの場合、一般的なユーザーデータは 50% 以上圧縮されます。
InnoDB
は、MySQL 8.0 にバンドルされているバージョンであるバージョン 1.2.11 までの zlib
ライブラリをサポートしています。
アプリケーションで実行される圧縮や、その他の一部のデータベース管理システムの圧縮機能とは異なり、InnoDB の圧縮は、ユーザーデータとインデックスの両方に適用されます。 多くの場合、インデックスがデータベースの合計サイズの 40-50% 以上を占める可能性があるため、この相違点は重要です。 データセットの圧縮が適切に機能している場合、InnoDB データファイル (file-per-table テーブルスペースまたは general tablespace .ibd
ファイル) のサイズは、圧縮されていないサイズの 25% から 50% 以下になります。 ワークロードによっては、このようにデータベースを小さくすることにより、CPU 使用率を少し増加させるだけで I/O を削減してスループットを増加できます。 innodb_compression_level
構成オプションを変更すると、圧縮のレベルと CPU のオーバーヘッド間のバランスを調整できます。
InnoDB データストレージと圧縮
InnoDB テーブル内のすべてのユーザーデータは、B ツリーインデックス (クラスタ化されたインデックス) を構成しているページに格納されます。 その他の一部のデータベースシステムでは、このタイプのインデックスは「インデックス編成テーブル」と呼ばれます。 インデックスノード内の各行には、(ユーザーが指定した、またはシステムで生成された) 主キーの値およびテーブルのその他のすべてのカラムが含まれています。
InnoDB テーブル内のセカンダリインデックスは、値のペア (インデックスキーと、クラスタ化されたインデックス内の行へのポインタ) を含む B ツリーでもあります。 実際は、ポインタはテーブルの主キーの値であり、インデックスキーおよび主キー以外のカラムが必要な場合に、クラスタ化されたインデックスにアクセスする際に使用されます。 常に、セカンダリインデックスのレコードは、B ツリーページ上に収容される必要があります。
次のセクションで説明するように、(クラスタ化インデックスとセカンダリインデックスの両方の) B ツリーノードの圧縮は、長い VARCHAR
、BLOB
、または TEXT
カラムを格納するために使用されるオーバーフローの圧縮とは異なる方法で処理されます。
B ツリーページの圧縮
B ツリーページは頻繁に更新されるため、特別な処理が必要です。 B ツリーノードが分割される回数を最小限にし、それらの内容を圧縮解除および再圧縮する必要性も最小限にすることが重要となります。
MySQL で使用される技術の 1 つでは、一部のシステム情報が非圧縮形式で B ツリーノード内に保持されるため、特定のインプレース更新が容易になります。 たとえば、これにより、圧縮操作なしで行に削除のマークを付け、その行を削除できます。
さらに、MySQL では、インデックスページが変更されたときに、不要な圧縮解除および再圧縮を回避しようと試みられます。 システムの各 B ツリーページ内には、ページに行われた変更を記録するための非圧縮の「変更ログ」が保持されます。 小さいレコードの更新および挿入は、ページ全体を完全に再構築する必要なしで、この変更ログに書き込まれる場合があります。
変更ログ用の領域を使い果たすと、InnoDB によってページが圧縮解除され、変更が適用され、ページが再圧縮されます。 再圧縮に失敗すると (圧縮の失敗と呼ばれる状況)、B ツリーノードが分割され、更新または挿入に成功するまでプロセスが繰り返されます。
OLTP アプリケーションなどで、書き込み負荷の高いワークロードでの頻繁な圧縮の失敗を回避するために、MySQL では、ページ内にいくつかの空のスペース (パディング) が予約されている場合があります。これにより、変更ログがより早く埋められ、分割を回避するための十分な空き領域がまだある間にページが再圧縮されます。 各ページに残されるパディングスペースの量は、システムでページ分割の頻度が追跡されるにつれて変化します。 圧縮テーブルへの書き込みが頻繁に行われる高負荷のサーバー上では、innodb_compression_failure_threshold_pct
および innodb_compression_pad_pct_max
構成オプションを調整すると、このメカニズムを微調整できます。
一般に、MySQL では、InnoDB テーブル内の各 B ツリーページに 2 つ以上のレコードを収容できます。 圧縮テーブルに対しては、この要件が緩和されました。 B ツリーノードのリーフページには (主キーとセカンダリインデックスのどちらでも)、1 つのレコードのみが収容される必要がありますが、そのレコードはページごとの変更ログに非圧縮形式で収まる必要があります。 innodb_strict_mode
が ON
の場合は、CREATE TABLE
または CREATE INDEX
の実行中に、MySQL によって行の最大サイズがチェックされます。 行が収まらない場合は、「ERROR HY000: Too big row」
というエラーメッセージが発行されます。
innodb_strict_mode
が OFF のときにテーブルを作成した場合に、後続の INSERT
または UPDATE
ステートメントで圧縮済みページのサイズに収まらないインデックスエントリの作成が試みられると、その操作に失敗し、「ERROR 42000: Row size too large」
というエラーが表示されます。 (このエラーメッセージは、レコードが長すぎるインデックスの名前を示すものでも、その特定のインデックスページ上のインデックスレコードの長さや最大レコードサイズを示すものでもありません。) この問題を解決するには、ALTER TABLE
を使用してテーブルを再構築し、より大きな圧縮済みページサイズ (KEY_BLOCK_SIZE
) を選択して、任意のカラムプリフィクスのインデックスを短くするか、ROW_FORMAT=DYNAMIC
または ROW_FORMAT=COMPACT
を使用して圧縮を完全に無効にします。
innodb_strict_mode
は、圧縮テーブルもサポートする一般的なテーブルスペースには適用できません。 一般的なテーブルスペースのテーブルスペース管理ルールは、innodb_strict_mode
とは無関係に厳密に適用されます。 詳細は、セクション13.1.21「CREATE TABLESPACE ステートメント」を参照してください。
BLOB、VARCHAR、および TEXT カラムの圧縮
InnoDB テーブルでは、主キーの一部ではない BLOB
、VARCHAR
、および TEXT
カラムが、個別に割り当てられたオーバーフローページに格納される場合があります。 このようなカラムは、オフページカラムと呼ばれています。 これらの値は、オーバーフローページの片方向リストに格納されます。
ROW_FORMAT=DYNAMIC
または ROW_FORMAT=COMPRESSED
で作成されたテーブルでは、カラムの長さおよび行全体の長さによっては、BLOB
、TEXT
、または VARCHAR
カラムの値が完全にオフページに格納される場合もあります。 オフページに格納されるカラムでは、クラスタ化されたインデックスのレコードに、オーバーフローページへの 20 バイトのポインタのみがカラムごとに 1 つずつ含まれます。 カラムがオフページに格納されるかどうかは、ページサイズおよび行の合計サイズによって異なります。 行がクラスタ化されたインデックスのページ内に完全に収まらないほど長い場合は、クラスタ化されたインデックスページ上に行が収まるまで、MySQL によってオフページストレージに合った最長のカラムが選択されます。 前述の注で示したように、行自体が圧縮済みページ上に収まらない場合は、エラーが発生します。
ROW_FORMAT=DYNAMIC
または ROW_FORMAT=COMPRESSED
で作成されたテーブルでは、40 バイト以下の TEXT
および BLOB
カラムは、常にインラインに格納されます。
ROW_FORMAT=REDUNDANT
および ROW_FORMAT=COMPACT
を使用するテーブルでは、BLOB
、VARCHAR
および TEXT
カラムの最初の 768 バイトが主キーとともにクラスタインデックスレコードに格納されます。 768 バイトのプリフィクスのあとには、残りのカラム値を含むオーバーフローページへの 20 バイトのポインタが続きます。
テーブルの形式が COMPRESSED
である場合は、オーバーフローページに書き込まれるすべてのデータが「そのまま」圧縮されます。つまり、MySQL では、データ項目全体に zlib 圧縮アルゴリズムが適用されます。 圧縮済みのオーバーフローページには、データ以外では特に、ページチェックサムを構成する非圧縮のヘッダーとトレーラ、および次のオーバーフローページへのリンクが含まれます。 したがって、テキストデータを使用した場合に多く見られるように、データの圧縮性が高い場合は、長い BLOB
、TEXT
、または VARCHAR
カラムで非常に大幅なストレージの節約が実現されます。 一般に、JPEG
などのイメージデータはすでに圧縮されているため、圧縮テーブルに格納される利点がほとんど得られません。領域の節約がほとんどない、またはまったくない場合は、二重圧縮によって CPU サイクルが無駄になる可能性があります。
オーバーフローページのサイズは、その他のページと同じです。 カラムの合計長が 8K バイトのみである場合でも、オフページに格納される 10 個のカラムを含む行で、10 個のオーバーフローページが占有されます。 非圧縮テーブルでは、10 個の非圧縮オーバーフローページで 160K バイトが占有されます。 ページサイズが 8K の圧縮テーブルでは、80K バイトのみが占有されます。 そのため、長いカラム値を含むテーブルでは、圧縮テーブル形式を使用すると効率性が高くなることが多くあります。
file-per-table テーブルスペースでは、BLOB
、VARCHAR
または TEXT
カラムの記憶域および I/O コストを 16K 圧縮ページサイズを使用すると削減できます。これは、これらのデータが圧縮されることが多いため、B ツリーノード自体が圧縮されていない形式と同じ数のページを使用しても、オーバーフローページが必要になる場合があるためです。 一般テーブルスペースでは、16K 圧縮ページサイズ (KEY_BLOCK_SIZE
) はサポートされていません。 詳細は、セクション15.6.3.3「一般テーブルスペース」を参照してください。
圧縮と InnoDB バッファープール
圧縮された InnoDB
テーブルでは、すべての圧縮ページ (1K、2K、4K または 8K) が 16K バイト (innodb_page_size
が設定されている場合は小さいサイズ) の圧縮されていないページに対応します。 ページ内のデータにアクセスするために、MySQL は、圧縮済みページがバッファープール内にすでに存在しない場合、そのページをディスクから読み取ってから、その元の形式に圧縮解除します。 このセクションでは、InnoDB
が圧縮テーブルのページに関してバッファプールを管理する方法について説明します。
I/O を最小限にして、ページを圧縮解除する必要性を削減するために、バッファープールに圧縮済み形式と非圧縮形式の両方のデータベースページが含まれることがあります。 その他の必要なデータベースページ用の空き領域を作成するために、MySQL ではメモリー内に圧縮済みページを残しながら、バッファープールから非圧縮ページをエビクションできます。 また、しばらくの間ページがアクセスされていない場合は、その他のデータ用に領域を解放するために、圧縮形式のページがディスクに書き込まれることもあります。 したがって、そのときどきで、バッファープールに圧縮形式と非圧縮形式の両方のページが含まれている場合、圧縮形式のページのみが含まれている場合、どちらも含まれていない場合があります。
MySQL では、ホット (頻繁にアクセスされる) データがメモリー内に滞在する傾向となるように、最近もっとも使用されていない (LRU) リストを使用して、メモリー内に保持されるページおよび削除されるページが追跡されます。 圧縮テーブルにアクセスすると、MySQL は適応型 LRU アルゴリズムを使用して、メモリー内の圧縮済みページと非圧縮ページの適切なバランスを実現します。 この適応型アルゴリズムは、システムが I/O バウンドと CPU バウンドのどちらの方式で実行されているかどうかの影響を受けやすくなります。 この目的は、CPU の負荷が高いときにページを圧縮解除するために要する処理時間が長くなりすぎることを回避すること、および (メモリー内にすでに存在する可能性のある) 圧縮済みページを圧縮解除するために使用できる予備のサイクルが CPU に備わっているときに過剰な I/O が発生することを回避することです。 システムが I/O バウンドの場合、このアルゴリズムでは、その他のディスクページ用により多くの空き領域を作成することでメモリーが常駐になるように、ページの両方のコピーではなく、非圧縮コピーを削除することが優先されます。 システムが CPU バウンドの場合、MySQL では、「ホット」ページ用に使用できるメモリーが多くなり、圧縮形式でのみメモリー内のデータを圧縮解除する必要性が少なくなるように、圧縮済みページと非圧縮ページの両方を削除することが優先されます。
圧縮と InnoDB の Redo ログファイル
圧縮済みページがデータファイルに書き込まれる前に、MySQL によってページのコピーが Redo ログに書き込まれます (最後にデータベースに書き込まれた以降に再圧縮された場合)。 これは、zlib
ライブラリがアップグレードされ、その変更によって圧縮済みデータとの互換性の問題が発生する可能性が低い場合でも、クラッシュリカバリ時に Redo ログを使用できるかどうかを確認するために行われます。 したがって、圧縮の使用時に、ログファイルのサイズを多少大きくすること、またはより頻繁にチェックポイントを発生させる必要性を多少多くすることが要求される可能性があります。 ログファイルのサイズを大きくする量またはチェックポイントの頻度を多くする数は、再構成および再圧縮が必要となる方法で圧縮済みページが変更される回数によって異なります。
file-per-table テーブルスペースに圧縮テーブルを作成するには、innodb_file_per_table
が有効になっている必要があります。 一般的なテーブルスペースに圧縮テーブルを作成する場合、innodb_file_per_table
設定には依存しません。 詳細は、セクション15.6.3.3「一般テーブルスペース」を参照してください。