状況の説明
以前の開発マシン、または古いMySQLバージョンでは問題なく動いていたINSERT文が、突然エラーになりました:
ERROR 1364 (HY000): Field 'created_by' doesn't have a default value
クエリは何も変えていません。カラムも存在しています。それでもMySQLはINSERTを拒否します。原因はStrictモード(厳格モード)です。サーバーのアップグレードや環境移行後によく発生します。
なぜこうなるのか
MySQL 5.7以降、STRICT_TRANS_TABLESがデフォルトのsql_modeに含まれるようになりました。それ以前は、デフォルト値のないNOT NULLカラムを省略しても、空文字列やゼロが黙って格納されていました。Strictモードが有効な場合、MySQLはINSERTを拒否します。
エラーが発生するカラムは通常、以下の条件を満たしています:
NOT NULLとして定義されているDEFAULT値が設定されていない- INSERT文に含まれていない
カラムの定義を確認してみましょう:
SHOW CREATE TABLE your_table\G
以下のような定義が見つかれば、それが原因です:
`created_by` varchar(100) NOT NULL,
— DEFAULTなし、かつNOT NULL — これが問題の原因です。
手っ取り早い修正:INSERT文にカラムを含める
最も早い修正方法は、不足している値を指定することです。
-- 修正前(失敗する)
INSERT INTO orders (product_id, quantity) VALUES (42, 3);
-- 修正後(成功する)
INSERT INTO orders (product_id, quantity, created_by) VALUES (42, 3, 'system');
そのカラムに常に適切なデフォルト値があるべきなら、スキーマ側で設定しておくことで、クエリを書くたびに意識する必要がなくなります。
恒久的な修正:カラムにDEFAULTを追加する
カラムに常に適切なフォールバック値があるべき場合は、スキーマにそのデフォルトを組み込んでしまいましょう:
ALTER TABLE orders
MODIFY COLUMN created_by VARCHAR(100) NOT NULL DEFAULT 'system';
タイムスタンプ系のカラムではよくこの対応が行われます:
ALTER TABLE orders
MODIFY COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE orders
MODIFY COLUMN updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
ALTER実行後は、元のINSERT文をクエリコードを一切変更せずに実行できるようになります。
代替案:カラムをNULL許容にする
「値なし」がそのカラムとして完全に有効な状態である場合もあります。NOT NULL制約を外しましょう:
ALTER TABLE orders
MODIFY COLUMN created_by VARCHAR(100) DEFAULT NULL;
INSERT文から省略すると、NULLが黙って格納されます。Strictモードはこれを許容します。
緊急の回避策:セッション単位でStrictモードを無効化する
マイグレーションの途中や、テーブルを変更できない状況で、今すぐINSERTを通す必要がある場合があります。現在のセッションのみStrictモードを無効化できます。他の接続には影響しません:
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', ''));
-- 失敗していたINSERTを実行
INSERT INTO orders (product_id, quantity) VALUES (42, 3);
-- Strictモードを元に戻す(または接続を閉じる)
SET SESSION sql_mode = @@GLOBAL.sql_mode;
**これを恒久的な修正として扱わないでください。**Strictモードはデータの問題をデータベースに入る前に検出するために存在します。グローバルに無効化すると、本来空であってはいけないフィールドに空文字列やゼロが黙って格納されるようになります。
グローバルsql_modeの確認と変更
現在有効な設定を確認するには:
SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;
すべての接続に対して変更を恒久的に適用するには、MySQLの設定ファイルを編集します。ディストリビューションによって通常は/etc/mysql/mysql.conf.d/mysqld.cnfまたは/etc/my.cnfです:
[mysqld]
sql_mode = "ONLY_FULL_GROUP_BY,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
その後、再起動します:
sudo systemctl restart mysql
STRICT_TRANS_TABLESを外すことはトレードオフです。データの安全性を互換性と引き換えにすることになります。「黙って格納される」INSERTが実際にどんな値をテーブルに書き込むかを監査してから、この方法を選択してください。
アプリケーション側:INSERT前にデフォルト値をセットする
このエラーが発生しているアプリケーションコードでは、クエリを回避するのではなく、クエリを実行する前に値をセットする必要があります。PythonとSQLAlchemyの場合:
from datetime import datetime
new_order = Order(
product_id=42,
quantity=3,
created_by=current_user.username or 'system',
created_at=datetime.utcnow()
)
db.session.add(new_order)
db.session.commit()
PHP PDOの場合:
$stmt = $pdo->prepare(
"INSERT INTO orders (product_id, quantity, created_by) VALUES (?, ?, ?)"
);
$stmt->execute([42, 3, $user ?? 'system']);
修正の確認
以下を実行して、すべて正しく反映されているか確認しましょう:
-- 元の失敗していたINSERTを再実行
INSERT INTO orders (product_id, quantity) VALUES (42, 3);
-- 結果: Query OK, 1 row affected となるはずです
-- レコードを確認
SELECT * FROM orders ORDER BY id DESC LIMIT 1;
カラムを変更した場合は、新しいデフォルト値がスキーマに反映されているか確認してください:
SHOW COLUMNS FROM orders LIKE 'created_by';
-- Field | Type | Null | Key | Default | Extra
-- created_by | varchar(100) | NO | | system |
根本原因のまとめ
- MySQL 5.7以降でStrictモードがデフォルトで有効になった — これが最も一般的な原因
DEFAULTなしでNOT NULLと宣言されたカラムは、すべてのINSERT文に含める必要がある- 最善の修正:カラムにDEFAULTを追加するか、クエリに明示的に含める
- セッション単位のsql_modeオーバーライドは最後の手段です — 恒久的な解決策ではありません

