午前2時の本番環境の悩み
通常、日常的なデータベース移行やサーバーのアップグレード直後に発生します。さっきまでアプリケーションはスムーズに動いていたのに、次の瞬間、レポートダッシュボードが不可解なSQLエラーを吐き出します。GROUP BYクエリが不正であるというテキストの壁を目の当たりにしていることでしょう。これは、最近のMySQLバージョンでデフォルトで有効になっているストリクトモード、特にONLY_FULL_GROUP_BY設定が原因です。
具体的なエラーメッセージ
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mydb.users.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
なぜこのエラーが発生するのか
MySQL 5.6以前では、エンジンはもっと寛容でした。GROUP BY句に含まれていないカラムでも、SELECTすることができました。MySQLはそのグループ内で最初に見つかった行の値を単に取得していました。これは便利でしたが、非決定的でした。データベースが実質的にどの値が欲しいかを推測していたため、技術的には標準SQLルールに違反していました。
MySQL 5.7から8.0にかけて、ONLY_FULL_GROUP_BYがデフォルトで有効になっています。現在、ルールは厳格です。SELECTリストにあるすべてのカラムは、GROUP BY句に含まれているか、COUNT()、MAX()、SUM()、AVG()などの集計関数で囲まれている必要があります。
解決策1:クエリを書き換える(「正しい」方法)
コードを修正することが、長期的には最も安定した戦略です。これにより、クエリの予測可能性が保たれ、SQL標準に準拠した状態を維持できます。グルーピングロジックの一部ではないカラムが必要な場合、主に2つの選択肢があります。
オプションA:GROUP BYにカラムを追加する
idとnameを選択していてもnameだけでグループ化している場合は、グループにidを含めます。これにより、データの処理方法をMySQLに明示的に指示できます。
-- 失敗例:
SELECT id, name, COUNT(*) FROM products GROUP BY name;
-- 成功例:
SELECT id, name, COUNT(*) FROM products GROUP BY name, id;
オプションB:ANY_VALUE()を使用する
どのidが返されるか実際には気にしない場合もあります。そのグループ内でそれらが同一であると分かっているか、あるいは代表的な値であれば何でも良い場合です。MySQLに対してその意図を伝えるために、ANY_VALUE()関数を使用します。
SELECT ANY_VALUE(id), name, COUNT(*)
FROM products
GROUP BY name;
解決策2:セッションに対してONLY_FULL_GROUP_BYを無効にする
サービスを復旧するために即座の修正が必要で、何十ものクエリをすぐにリファクタリングできない場合があります。その場合、現在の接続に対してのみこのモードを無効にできます。これは、グローバル設定を変更せずにテストを行ったり、レガシーなスクリプトを実行したりするのに最適です。
-- 最初に現在のモードを確認する
SELECT @@sql_mode;
-- 現在のセッションからONLY_FULL_GROUP_BYを除去する
SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
解決策3:グローバルにONLY_FULL_GROUP_BYを無効にする(恒久的な修正)
大規模なレガシーコードベースを管理していますか?その場合、サーバーレベルでこのチェックを無効にする必要があるかもしれません。**警告:**この変更はインスタンス上のすべてのデータベースに影響するため、他の場所で正当なクエリのバグを隠してしまう可能性があります。
ステップ1:MySQL設定ファイルの場所を特定する
ほとんどのLinuxディストリビューションでは、設定ファイルはここにあります:
/etc/mysql/my.cnf(全般)/etc/mysql/mysql.conf.d/mysqld.cnf(Ubuntu/Debian)/etc/my.cnf(CentOS/RHEL)
ステップ2:設定を編集する
root権限でファイルを開きます:sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf。[mysqld]セクションを見つけ、sql_modeの行を追加または修正します。トラブルの原因となっているモード以外のデフォルトモードを含めるようにします。
[mysqld]
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
ステップ3:MySQLを再起動する
変更を適用するためにサービスをリロードします:
sudo systemctl restart mysql
検証:修正を確認する方法
設定変更がうまくいったと決めつけないでください。MySQLターミナルでこのコマンドを実行して、グローバルな状態を確認します:
SELECT @@GLOBAL.sql_mode;
リストからONLY_FULL_GROUP_BYが消えていることを確認してください。消えていれば、レガシーなクエリは1055エラーを出さずに実行されます。
本番環境での実践的なヒント
- クエリのリファクタリングは将来を見据えた対策: モードを無効にするのは一時的なしのぎに過ぎません。
ANY_VALUE()や適切な集計を使用することで、Postgres、SQL Server、および将来のMySQLバージョンとの互換性が保たれます。 - Laravelユーザーの場合: LaravelのEloquent ORMは、独自のセッションモードを設定することがよくあります。
config/database.phpで'strict' => trueを確認してください。これをfalseに設定することで、サーバー設定をいじることなく問題が解決することがよくあります。 - 安全第一: 編集する前に必ず
my.cnfをバックアップしてください。たった一文字の間違いでデータベースが起動しなくなり、軽微なエラーが重大な障害に発展する可能性があります。

