MySQL の query_cache を無効にすべき理由(8.0で廃止)
MySQL のクエリキャッシュ(query_cache)は、一見便利な機能に思えますが、実は多くの問題を抱えており、MySQL 8.0 では完全に廃止されました。この機能の問題点と、代替手段について解説します。
クエリキャッシュとは
クエリキャッシュは、SELECT クエリの結果をメモリにキャッシュし、同一クエリが来たらキャッシュから返す仕組みでした。MySQL 5.7 まで存在していた機能です。
-- MySQL 5.7以前で確認
SHOW VARIABLES LIKE 'query_cache_type';
SHOW VARIABLES LIKE 'query_cache_size';なぜ廃止されたのか
クエリキャッシュは、以下の理由でパフォーマンス上の問題を引き起こすことが多かったのです。
キャッシュへの読み書きに排他ロックが必要でした。高並列環境では、このロック競合がボトルネックになります。
テーブルに対する INSERT / UPDATE / DELETE が発生すると、そのテーブルに関連するすべてのキャッシュが無効化されます。更新が頻繁なテーブルでは、キャッシュがほとんど活用されません。
クエリ文字列が完全に一致しないとキャッシュヒットしません。スペースの違いや、パラメータの違いでも別クエリとして扱われます。
具体的な問題のシナリオ
高頻度で更新されるテーブルがある場合を考えます。
SELECT でキャッシュに登録
INSERT で全キャッシュが無効化
次の SELECT で再度キャッシュに登録
また UPDATE で無効化
このサイクルでは、キャッシュに登録するオーバーヘッドだけがかかり、ヒットする機会がほとんどありません。
MySQL 8.0 での対応
MySQL 8.0 ではクエリキャッシュ関連のパラメータ自体が削除されました。
-- MySQL 8.0 ではエラーになる
SHOW VARIABLES LIKE 'query_cache%';
-- Empty setmy.cnf にクエリキャッシュ関連の設定が残っていると、MySQL 8.0 へのアップグレード時にエラーになることがあるので、事前に削除しておく必要があります。
MySQL 5.7 で無効化する方法
まだ MySQL 5.7 を使っている場合、クエリキャッシュは無効化することをおすすめします。
[mysqld]
query_cache_type = 0
query_cache_size = 0動的に無効化する場合は以下のようにします。
SET GLOBAL query_cache_type = 0;
SET GLOBAL query_cache_size = 0;代替手段
クエリキャッシュに頼らず、以下の方法でパフォーマンスを向上させましょう。
Redis や Memcached を使って、アプリケーション層でキャッシュを管理します。キャッシュの有効期限や無効化をきめ細かく制御できます。
innodb_buffer_pool_size を適切に設定することで、データ自体をメモリにキャッシュします。クエリキャッシュより効率的です。
そもそもクエリ自体を高速化することで、キャッシュの必要性を減らします。
Redis を使ったキャッシュの例
アプリケーションコードでキャッシュを制御する例です(擬似コード)。
def get_user(user_id):
cache_key = f"user:{user_id}"
# キャッシュを確認
cached = redis.get(cache_key)
if cached:
return json.loads(cached)
# DBから取得
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# キャッシュに保存(有効期限5分)
redis.setex(cache_key, 300, json.dumps(user))
return user
この方法なら、ユーザー情報が更新されたときに該当キーだけを無効化できます。テーブル全体のキャッシュが消えることはありません。
ProxySQL のクエリキャッシュ
どうしてもデータベース層でキャッシュしたい場合、ProxySQL のクエリキャッシュ機能が選択肢になります。MySQL 本体のクエリキャッシュより柔軟な制御が可能です。
テーブル更新で全無効化、グローバルロック
TTL ベースの制御、ロック競合なし
まとめ
クエリキャッシュは過去の機能です。現代的なアーキテクチャでは、アプリケーション層でのキャッシュ管理がベストプラクティスとなっています。