6.

Django mysql.W002「Strict Mode is not set」警告の原因と対処

編集
この記事の要点
  • Django startup / check 時に出る警告(エラーではないが本番では必ず潰す)
  • MySQL の sql_modeSTRICT_TRANS_TABLES が含まれていないと出る
  • 厳密モードが OFF だと VARCHAR(5) に 10 文字突っ込んでも黙って切り詰めるような事故が起きる
  • 対処A: settings.py の OPTIONSinit_command を指定(接続毎に設定)
  • 対処B: my.cnf の [mysqld] に sql_mode(サーバー全体に反映)
  • MySQL 5.7+ はデフォルトで STRICT_TRANS_TABLES 入り。5.6 以下 or 設定を上書きした環境で出やすい

警告メッセージの全文

System check identified some issues:

WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
        HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL,
        such as data truncation upon insertion, by escalating warnings into errors.
        It is strongly recommended you activate it.
        See: https://docs.djangoproject.com/en/4.2/ref/databases/#mysql-sql-mode

そもそも Strict Mode とは

MySQL の sql_mode は SQL の挙動を制御する変数で、STRICT_TRANS_TABLES(トランザクション対応テーブル)または STRICT_ALL_TABLES が含まれていると厳密モードになります。

挙動厳密モード OFF(既定で許容)厳密モード ON
VARCHAR(5) に "abcdef" 入れる警告のみ、"abcde" に切り詰めて INSERT 成功ERROR 1406 Data too long
NOT NULL カラムに NULLデフォルト値で INSERTERROR 1048 cannot be null
INT に "abc" 文字列0 で INSERT、警告のみERROR 1366 Incorrect integer value
0000-00-00 日付許容エラー

厳密モード OFF は「データを黙って書き換える」最悪の状態。本番では必ず ON にすべきです。

対処A: settings.py の OPTIONS で指定(推奨)

Django 公式が推奨する方法。接続ごとに MySQL に対して SET します:

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypass',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'OPTIONS': {
            # 接続ごとに sql_mode を設定
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
            'charset': 'utf8mb4',
        },
    }
}

サーバー全体の設定を変えられない場合(マネージド DB など)はこちらで対処します。

対処B: my.cnf / mysqld.cnf で指定

サーバー全体に反映させる場合:

# /etc/mysql/my.cnf  or /etc/my.cnf.d/server.cnf
[mysqld]
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

# MySQL 8 のデフォルト相当
# sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
# 反映
sudo systemctl restart mysql

# 確認
mysql -u root -p -e "SHOW VARIABLES LIKE 'sql_mode'"

対処C: SET GLOBAL で即時変更(再起動なし)

-- MySQL に root で接続して
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

-- 確認(新しい接続から有効)
SELECT @@GLOBAL.sql_mode;

-- 現在のセッションのみ
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';

注意: SET GLOBAL は MySQL 再起動で失われます。恒久化には my.cnf も必要。

Django プロジェクト側で警告を抑止したい場合(非推奨)

どうしても警告を消したいが厳密モードは入れない、というケース:

# settings.py
SILENCED_SYSTEM_CHECKS = ['mysql.W002']

本番では絶対やめてください。データ破壊リスクを抱えたまま運用することになります。

厳密モードを ON にしたら既存コードが動かない場合

厳密モード OFF 前提で書かれたコードは、ON にすると例外が出ます:

# 例1: 桁あふれ → models.py で max_length を見直す
class User(models.Model):
    name = models.CharField(max_length=20)  # 20 文字までしか入らないことを保証

# フォームバリデーション側で先に弾く
class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['name']
    def clean_name(self):
        name = self.cleaned_data['name']
        if len(name) > 20:
            raise forms.ValidationError("20文字以内で入力してください")
        return name

# 例2: NULL を許す場合は null=True を明示
class Post(models.Model):
    summary = models.TextField(null=True, blank=True)

FAQ

Q: MySQL 5.7 / 8 のデフォルトは?
A: 5.7 以降は STRICT_TRANS_TABLES がデフォルトで含まれています。古いマイグレーション設定が残っていたり、独自の my.cnf で上書きしている場合に W002 が出ます。

Q: マネージド DB (RDS / Cloud SQL) で sql_mode を変えたい
A: AWS RDS ならパラメータグループsql_mode を編集 → DB 再起動。GCP Cloud SQL はフラグから設定。または settings.py の init_command が手軽。

Q: TRADITIONAL モードでもよい?
A: TRADITIONAL は STRICT_TRANS_TABLES,STRICT_ALL_TABLES などを含む厳しめプリセット。Django でも警告は消えますが、互換性に注意。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Invalid HTTP_HOST header: '...'. You may need to add '...' to ALLOWED_HOSTS
  2. CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
  3. django.utils.datastructures.MultiValueDictKeyError
  4. Forbidden (403) CSRF verification failed. Request aborted.
  5. ModuleNotFoundError: No module named 'MySQLdb'
  6. WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection
  7. Unknown column 'table_name.id' in 'field list'
  8. RuntimeError: Model class ~ doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
  9. get() returned more than one MynumberRegist -- it returned 2!
  10. django.db.utils.OperationalError: (2006, "Can't connect to MySQL server")
  11. 'include' is not defined