Django と MySQL(CHARACTER SET: utf8mb4) を合わせて使う場合、
(1071, 'Specified key was too long; max key length is 767 bytes')
(1709, 'Index column size too large. The maximum column size is 767 bytes.')
などのエラーが出てしまうことがあります。
utf8の場合は Databases | Django documentation | Django らへんに載っている手順通りに設定すれと問題になることはないと思いますが、utf8mb4を使用するとなると、上記のエラーを回避するために特別な手順が必要になるので、初期セットアップの手順から備忘録的にまとめます。
(初期セットアップとかいいから上記の ...767 bytes
のエラーを回避する方法だけ知りたい方は、5の章の内容だけ見ていただけますと。)
今回記事を書くにあたって検証した環境は以下の通りです。
- Python == 3.6.3
- Django == 2.0.2
- mysqlclient == 1.3.12
- MySQL == 5.6.39
1. Django projectを作成する
まずはじめにprojectを開始します。(Djangoはprojectがあり、その中に複数のアプリケーション(app)があるという構成を取ります。)
$ pip install django
...
$ django-admin startproject sampleproj # => これで sampleproj というprojectが作成される
startprojectを行った後は以下のようなディレクトリ構成で初期ファイルが作成されます。
$ tree .
.
└── sampleproj
├── manage.py
└── sampleproj
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-36.pyc
│ └── settings.cpython-36.pyc
├── settings.py
├── urls.py
└── wsgi.py
3 directories, 7 files
2. mysqlclient をインストール
にもある通り、mysqlclient をインストールします。pypiにあるのでpipでパツイチですね。
$ pip install mysqlclient
3. 接続設定を書く
sampleproj/sampleproj/settings.py
からデータベースの接続設定を行います。ファイルをエディタで開くと
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
というdictが書かれている部分があるので、これを以下のように書き換えます。(あくまでも一例なので、利用環境に合わせて適時書き換えてください。)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'sampleproj',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
'sql_mode': 'TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY',
},
},
}
フィールド | 説明 |
---|---|
ENGINE | 利用するデータベース設定。MySQLの場合は "django.db.backends.mysql" を設定する |
NAME | 接続するdatabase名。利用環境に合わせて設定する |
USER | 接続ユーザ。利用環境に合わせて設定する |
PASSWORD | 接続ユーザに設定されているパスワード。利用環境に合わせて設定する |
HOST | データベースの接続先(ホスト)。利用環境に合わせて設定する |
PORT | データベースの接続先(ポート)。利用環境に合わせて設定する |
OPTIONS | データベース接続時に発行されるパラメータを設定するdict。 |
OPTIONS.charset | ここが重要!接続時に charset=utf8mb4 を発行する |
OPTIONS.sql_mode | SQL modeの設定。https://dev.mysql.com/doc/refman/5.6/en/sql-mode.html |
※ 本筋とは関係ないですが、sql_mode
の記述は以下の記事をチェックです。
ルーク!MySQLではkamipo TRADITIONALを使え! | おそらくはそれさえも平凡な日々
これでベーシックな設定ができました。
4. MySQLの設定
my.cnfを以下のように設定し、utf8mb4の利用を有効にします。(※my.cnfを書き換えたらMySQLの再起動は忘れずに)
[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-server = utf8mb4
skip-character-set-client-handshake
collation-server = utf8mb4_general_ci
init-connect = SET NAMES utf8mb4
my.cnfを設定したらmysql cliからアプリケーション用のdatabaseを作成します。
mysql> CREATE DATABASE <NAMEに設定したdb名> CHARACTER SET utf8mb4;
確認します。
mysql> use <NAMEに設定したdb名>;
Database changed
mysql> show variables like '%char%';
+--------------------------+----------------------------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/Cellar/mysql@5.6/5.6.39/share/mysql/charsets/ |
+--------------------------+----------------------------------------------------------+
OKですね。
5. (1071, 'Specified key was too long; max key length is 767 bytes') を回避する
上記の手順までで例えば以下のModelを作成し、migrationを実行してみます。
{sampleapp}/models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=255, null=False, db_index=True)
$ cd sampleproj
$ python manage.py makemigrations
Migrations for '{sampleapp}':
{sampleapp}/migrations/0001_initial.py
- Create model User
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, app, auth, contenttypes, sessions
Running migrations:
Applying app.0001_initial...Traceback (most recent call last):
...
_mysql.connection.query(self, query)
django.db.utils.OperationalError: (1709, 'Index column size too large. The maximum column size is 767 bytes.')
(1709, 'Index column size too large. The maximum column size is 767 bytes.')
というエラーが出るとともにmigrationが失敗していまいました。
このエラーの回避策はDjangoの公式ではサポートされていませんが、以下の記事を参考に回避する設定を加えることにします。
MySQL(InnoDB) で "Index column size too large. The maximum column size is 767 bytes." いわれるときの対策 - かみぽわーる
my.cnfに設定を追加
my.cnf
に以下の3行を追加します。
innodb_large_prefix
を有効にすることでキープレフィックスの制限を3072バイトまで拡張することができるとのこと。
[mysqld]
...
innodb_file_format = Barracuda
innodb_file_per_table = 1
innodb_large_prefix
ただし、これだけではinnodb_large_prefix
は有効にはならず、CREATE TABLE
文にROW_FORMAT
にDYNAMIC
かCOMPRESSED
のどちらかを付けてtableを作成する必要がある。
CREATE TABLE文に " ROW_FORMAT=DYNAMIC" をつけるようにする
manage.py
に以下の2行を追加します。
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sampleproj.settings")
# この以下の2行を追加する!
from django.db.backends.mysql.schema import DatabaseSchemaEditor
DatabaseSchemaEditor.sql_create_table += " ROW_FORMAT=DYNAMIC"
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
この操作はmigrationで新規テーブルを追加する時に発行するCREATE TABLE
文の末尾に ROW_FORMAT=DYNAMIC
というステートメントを追加するというものです。
再度、migrationを実行してみます。
$ python manage.py migrate
Operations to perform:
Apply all migrations: app
Running migrations:
Applying app.0001_initial... OK
これで同じmigrationファイルでもエラーにならずに通るようになりました。