Open6
Railsプロジェクトにおける未使用インデックスの検出方法

1. MySQL sysスキーマの活用
stgで確認。
MySQLのパフォーマンススキーマが有効になっていることを確認。
SHOW VARIABLES LIKE 'performance_schema';
mysql> SHOW VARIABLES LIKE 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | OFF |
+--------------------+-------+
1 row in set (0.00 sec)
OFF
が返ってきた場合は、MySQLの設定ファイルで有効にしてサーバーを再起動する必要あり。
SELECT
object_name,
index_name
FROM
sys.schema_unused_indexes
WHERE
object_schema = 'データベース名'
ORDER BY
object_name,
index_name;

2. インデックス使用統計の確認
インデックス使用に関するより詳細な情報を得る:
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME,
t.TABLE_ROWS AS table_rows,
s.CARDINALITY,
ROUND( IF(t.TABLE_ROWS > 0, s.CARDINALITY / t.TABLE_ROWS * 100, 0), 2) AS selectivity,
i.COUNT_READ,
i.COUNT_WRITE,
IF(i.COUNT_READ > 0, i.COUNT_WRITE / (i.COUNT_READ + IF(i.COUNT_READ = 0, 1, 0)) * 100, 0) AS change_to_read_ratio
FROM
information_schema.STATISTICS s
JOIN information_schema.TABLES t ON s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage i ON i.OBJECT_SCHEMA = t.TABLE_SCHEMA AND i.OBJECT_NAME = t.TABLE_NAME AND i.INDEX_NAME = s.INDEX_NAME
WHERE
t.TABLE_SCHEMA = 'データベース名' AND t.TABLE_ROWS > 0
ORDER BY
change_to_read_ratio DESC,
table_rows DESC;

未使用のユニーク制約(重複インデックス)がいくつかあるねー

MySQLインデックス分析の各列の説明
クエリ結果の各列についての説明:
列名 | 説明 |
---|---|
TABLE_SCHEMA | データベース名。 |
TABLE_NAME | テーブルの名前。例: versions など。 |
INDEX_NAME | インデックスの名前。例: PRIMARY (主キー)、index_job_prefectures_on_job_id など。 |
COLUMN_NAME | インデックスに含まれる列名。複合インデックスの場合は複数行に分かれて表示されます。 |
table_rows | テーブル内の推定行数。これは厳密な値ではなく、統計情報に基づく推定値です。 |
CARDINALITY | インデックス内のユニーク値の推定数。これも統計情報に基づく推定値です。 |
selectivity | 選択性の値。計算式は (CARDINALITY / table_rows * 100) 。この値が高いほど、インデックスは効率的です。 |
COUNT_READ | Performance Schemaから取得した、インデックスの読み取り回数。現在はNULLが表示されています。 |
COUNT_WRITE | Performance Schemaから取得した、インデックスの書き込み回数。現在はNULLが表示されています。 |
change_to_read_ratio | 書き込みと読み取りの比率。書き込みが多い場合、インデックスのメンテナンスコストが高くなります。 |
選択性(selectivity)の確認方法
選択性はselectivity
列で確認できます。これは以下の計算式で求められています:
ROUND( IF(t.TABLE_ROWS > 0, s.CARDINALITY / t.TABLE_ROWS * 100, 0), 2)
この値はインデックスの有効性を示す重要な指標です:
選択性の範囲 | 解釈 |
---|---|
90-100% | 非常に効率的なインデックス。ほぼユニークな値を持ちます。PrimaryKeyやユニークキーは通常この範囲です。 |
20-90% | 中程度に効率的。多くの場合、このレベルの選択性でもクエリプランナーは使用します。 |
5-20% | 効率が低くなります。大量のデータがある場合はテーブルスキャンより効率的かもしれません。 |
0-5% | 非常に効率が低いインデックス。クエリプランナーに使用されない可能性が高いです。 |
0%に近い値 | ほぼ無意味なインデックス。カテゴリデータや真偽値などでよく見られます。 |
例えば、データからは以下のような選択性の低いインデックスが見られます:
テーブル名 | インデックス名 | 列名 | 選択性(%) |
---|---|---|---|
agent_job_search_query_prefectures | idx_qprofessions_on_prefecture_id | prefecture_id | 0.00 |
agent_job_search_queries | index_agent_job_search_queries_on_agency_id | agency_id | 0.02 |
job_summary_reports | index_job_summary_reports_on_target_date | target_date | 0.02 |
これらのインデックスは、シーケンシャルスキャンと比較してもあまり効果的でない可能性があります。ただし、外部キー制約のために必要な場合もあります。
一方、高い選択性を持つインデックスの例:
テーブル名 | インデックス名 | 列名 | 選択性(%) |
---|---|---|---|
agent_job_search_query_job_type_professions | PRIMARY | id | 99.99 |
versions | PRIMARY | id | 100.00 |
job_page_views | PRIMARY | id | 100.00 |
これらは主キーとして高い選択性を持ち、効率的なインデックスとして機能しています。
選択性の値を使用して、特に大きなテーブルにある低選択性インデックスを特定し、それらが本当に必要かどうかを検討することができます。

selectivityが低いインデックスの評価と対応
selectivityが低いインデックスの削除については、単純に値だけで判断するのではなく、以下の観点から総合的に評価する必要があります:
判断基準 | 説明 |
---|---|
インデックスの目的 | 外部キー制約やユニーク制約(unique: true )のために作成されたインデックスは、選択性が低くても削除すべきではありません。これらはデータ整合性のために必要です。 |
テーブルサイズ | 小さなテーブル(数千行以下)では、インデックスの有無による実行速度の差はわずかなため、選択性が低くても問題にならないことが多いです。 |
クエリパターン | そのインデックスがアプリケーションで頻繁に使用されるクエリをサポートしている場合、選択性が低くても保持すべき場合があります。 |
複合インデックス | 複合インデックスの一部として含まれる列は、その位置によって実効的な選択性が変わります。先頭の列の選択性が重要です。 |
削除を検討すべきケース
以下の条件をすべて満たす場合、削除を検討できます:
- selectivityが1%未満
- 大規模テーブル(数十万行以上)に存在する
- 外部キー制約やユニーク制約に関連していない
- Performance Schemaで使用頻度が低いことが確認できている(現在はNULLのため確認不可)
具体的なアプローチ
- まずPerformance Schemaを有効にして、実際の使用状況データを収集
- 使用頻度が低く、選択性も低いインデックスを特定
- テスト環境でインデックスを削除し、主要クエリのパフォーマンスをモニタリング
- 問題がなければ本番環境でも削除を実施
注意点
インデックスの削除は、特に大規模なテーブルでは重要なパフォーマンス改善につながる可能性がありますが、アプリケーションの特定のクエリパターンを考慮せずに行うと、予期せぬパフォーマンス低下を引き起こす可能性があります。
まずはPerformance Schemaを有効にして実際の使用状況を把握することが、安全な最適化の第一歩です。

selectivityが5%以下のものだけ抽出
SELECT
ROUND( IF(t.TABLE_ROWS > 0, s.CARDINALITY / t.TABLE_ROWS * 100, 0), 2) AS selectivity,
t.TABLE_SCHEMA,
t.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME,
t.TABLE_ROWS AS table_rows,
s.CARDINALITY,
i.COUNT_READ,
i.COUNT_WRITE,
IF(i.COUNT_READ > 0, i.COUNT_WRITE / (i.COUNT_READ + IF(i.COUNT_READ = 0, 1, 0)) * 100, 0) AS change_to_read_ratio
FROM
information_schema.STATISTICS s
JOIN information_schema.TABLES t ON s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage i ON i.OBJECT_SCHEMA = t.TABLE_SCHEMA AND i.OBJECT_NAME = t.TABLE_NAME AND i.INDEX_NAME = s.INDEX_NAME
WHERE
t.TABLE_SCHEMA = 'データベース名' AND t.TABLE_ROWS > 0
HAVING
selectivity <= 5
ORDER BY
selectivity ASC,
change_to_read_ratio DESC,
table_rows DESC;