MySQLにおけるインデックス最適化の実践テクニック

部分文字列インデックスによるストレージ効率化

長めの文字列カラム(例:email)に対して、先頭数文字のみを対象にインデックスを構築することで、インデックスサイズを削減し、ページあたりのエントリ数を増加させることができます。

ALTER TABLE accounts ADD INDEX idx_login_prefix(login_id(8));

この例では、login_id の先頭8バイトのみがB+ツリーに格納され、メモリとディスク使用量を抑えると同時に、範囲スキャンや等値検索の高速化にも寄与します。ただし、選択率(cardinality)が低下しないよう、十分なユニーク性を持つ長さを選定する必要があります。

カバリングインデックスによる回表回避

以下のようなスキーマを想定します:

CREATE TABLE orders (
  order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  customer_id INT NOT NULL,
  status TINYINT NOT NULL,
  created_at DATETIME,
  INDEX idx_cust_status (customer_id, status)
);

このとき、次のようなクエリはカバリングインデックスを活用できます:

SELECT customer_id, status FROM orders WHERE customer_id = 12345 AND status = 2;

インデックス idx_cust_status のリーフノードには customer_idstatus の両方の値が格納されているため、聚簇インデックス(主キー)への追加アクセス(=回表)を回避でき、I/O負荷を大幅に低減します。

連続した主キーによるページ分割最小化

主キーとしてAUTO_INCREMENTを採用すると、新規行の挿入は常にB+ツリーの右端に追加されます。これにより:

  • 既存データの再配置が不要
  • ページ分割(page split)の発生頻度が抑制される
  • ディスク上の物理配置がより連続的になり、範囲スキャン性能が向上

一方、UUIDやランダムな数値を主キーに用いると、挿入位置が不定となり、頻繁なページ再編成や内部断片化を引き起こす可能性があります。

インデックス無効化を招く代表的なパターン

  • 前方一致不可なLIKE式WHERE name LIKE '%son'WHERE name LIKE '%mi%' は、B+ツリーの左から順に並ぶ性質を活かせないため、フルテーブルスキャンに陥ります。
  • カラムに対する関数適用WHERE UPPER(email) = 'ADMIN@EXAMPLE.COM' は、インデックスに格納された生の値と一致しないため、インデックスが使われません。代わりに生成列+インデックスや、大文字小文字を無視する照合順序(collation)の利用を検討します。
  • 複合インデックスの最左原則違反INDEX (a, b, c) に対して WHERE b = 5 だけではインデックスは使用されません。条件に a が含まれていない限り、ツリーの探索起点を特定できないためです。
  • OR句の非均一なインデックス利用WHERE indexed_col = 1 OR non_indexed_col = 'X' の場合、MySQLのクエリプランナーは、一部の条件でしかインデックスを使えないことを理由に、全件スキャンを選択することがあります。このようなケースでは、UNION ALLによる分割クエリや、補助インデックスの追加を検討します。

タグ: MySQL covering-index composite-index query-optimization b-tree

6月4日 20:16 投稿