MySQLdb._exceptions.DataError (1406, Data too long for column 'name')の原因調査

背景

前回謎のエラーが発生し、マイグレーションはできているものの、なぜなのかがわからないままでした。

テストデータをテーブルに投入する際に、既存データの破棄のためロールバックを行ったのですが、また同じエラーが発生したので、詳しく調べてみました。

エラー内容の確認

エラーが起こったのはauthアプリケーションのロールバックでした

1
2
3
4
5
6
7
8
9
10
11
12
$ docker-compose run --rm web python manage.py migrate auth zero
...
(省略)
...
Unapplying auth.0002_alter_permission_name_max_length...
DEBUG ALTER TABLE `auth_permission` MODIFY `name` varchar(50) NOT NULL; (params [])
DEBUG (0.021) None; args=[]; alias=default
Traceback (most recent call last):
...
(省略)
...
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 57")

今回はDEBUGメッセージを表示しているので実行されるSQLがわかります。auth_permissionテーブルの変更でエラーが発生していることがわかります。

レコードの確認

実際にauth_permissionテーブルを確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ docker-compose run --rm web python manage.py dbshell
Creating example_web_run ... done
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 92
Server version: 5.7.36 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [example]> select * from auth_permission;
+----+--------------------------------------------------------+-----------------+-----------------------------------------------+
| id | name | content_type_id | codename |
+----+--------------------------------------------------------+-----------------+-----------------------------------------------+
| 1 | Can add log entry | 1 | add_logentry |
| 2 | Can change log entry | 1 | change_logentry |
| 3 | Can delete log entry | 1 | delete_logentry |
...
(省略)
...
| 57 | 51文字の文字列 | 15 | add_長めのモデル名 |
...
(省略)

エラーメッセージは

1
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 57")

なので、57行目を確認したところ、nameカラムの値が51文字ありました。

マイグレーションの内容確認

マイグレーションの内容を確認します。authアプリケーションのマイグレーションファイルはgithubで確認できました。

https://github.com/django/django/tree/main/django/contrib/auth/migrations

この中の

https://github.com/django/django/blob/main/django/contrib/auth/migrations/0002_alter_permission_name_max_length.py

でエラーが発生しています。ファイル名からしてすでに怪しいですね。

内容は以下になっています

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("auth", "0001_initial"),
]

operations = [
migrations.AlterField(
model_name="permission",
name="name",
field=models.CharField(max_length=255, verbose_name="name"),
),
]

最大長を255に変更しているのがわかります。だとすると元々の長さはどれくらいだったのでしょうか。それを知るためにはその前のマイグレーションフィアルである、0001_initial.pyを確認します。

確認すると以下のようになっていました。

1
("name", models.CharField(max_length=50, verbose_name="name")),

最大50文字でした…最大長255から最大長50に戻すには保存されているデータが大きすぎるということで、エラーになっているということですね。

ロールバックの順序で対応できるか

マイグレーションでデータをINSERTしているのであれば、ロールバックでデータをDELETEすれば、ALTER文でエラーが発生しなくなるのでは?と思い、INSERTしているマイグレーションのアプリケーションを探してみると、sessionsのマイグレーションでINSERTしていることがわかった。

しかし、sessionsのマイグレーションをロールバックしてもデータが削除されなかった…

まとめ

長い名前のモデルを作成すると、auth_permissionテーブルのnameカラムに長さが50を超えるデータが入ることがある。その状態でロールバックすると例外が発生してしまいます。

現状思いつく対応としては、auth_permissionテーブルのデータを全て削除してからロールバックを行うという方法です。スマートさはないですが、エラーの回避はできます。

しかし、テーブル作成した後にmax_length=255にするくらいなら、最初からmax_length=255で作成しておいてくれればいいのになと思いました。

参考図書