LoginSignup
2
3

More than 3 years have passed since last update.

Djangoのmigrateでerrno: 150 "Foreign key constraint is incorrectly formed"

Posted at

TL; DR

  • DBがmigrate以外の方法でMODIFYされていてテーブルの型の不一致により外部キーが張れなかった
  • 型の不一致は desc table_name では判断できないので注意
  • あくまで今回当たった事象であり、同様のエラーがでる問題はたくさんある

はじめに

開発環境では普通にうまくいっていた Django の migrate が本番適用時にコケる。
幸いテーブルの追加のみのマイグレートだったため影響は最小限だったが、なかなか解決までに時間がかかったのでメモ。

なお、Pythonは3.6.7、Djangoは2.1系だが、バージョンに関わらず発生する内容だと思う。

調査

開発環境で事前実施した通り python manage.py migrate にてマイグレーションを実施。
……すると、errno: 150 "Foreign key constraint is incorrectly formed" としてエラーメッセージが表示される。

正直、ローカルと本番でDBのマイナーバージョンまでそろえてやっているのでこの時点で「面倒なことになった…」と思っていましたが、まずは落ち着いてグーグル先生にメッセージをコピペ。

調べてみると

  • 外部キー制約がユニークでない場合
  • テーブルのエンジンが異なる場合
  • データの型が異なる場合
  • etc, etc...

など、様々な場合でこの errno: 150 が発生するらしい。
まずは詳細エラー内容を確認していってみると、

こちら を参考にして SHOW ENGINE INNODB STATUS; 内の LATEST FOREIGN KEY ERROR を確認。

すると、

Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.

というエラーメッセージで型が違う、と言われてしまった。
もちろんこれについては先に desc table でお互いの型が char(32) であり、型・バイト数まで同じことを確認していたので、やっぱり混乱した。

紆余曲折(20分ぐらい)色々やってみて、型を別の方法で確認しようとして外部キーを貼る先に SHOW CREATE TABLE を打ってみたときのこと、

...
`user_id` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
...

…んん!?

文字コードも違えば COLLATE されている設定も違う!
これが「型」として違うと認識されているらしいので、この部分を併せてやる必要がある。

外部キーを貼る元のカラムを SHOW CREATE TABLE で見てみると、

 `owner_id` char(32) NOT NULL,

となっているので、ここに COLLATE を指定して型を合わせる。
その後に外部キーを貼ればテーブル上の整合性はOK。

ALTER TABLE table_name MODIFY COLUMN owner_id char(32) COLLATE 'utf8mb4_unicode_ci';
ALTER TABLE table_name ADD FOREIGN KEY(owner_id) REFERENCES user(user_id);

最後に、今回のDjangoマイグレーションを強制的に進めて状態を正として終了。

# 最新のマイグレーションバージョン 00XX まで適用したことをDjangoに認識させる
python manage.py migrate app 00XX --fake

原因

Djangoの管理外でDB構造を修正したか、あるいは、既存のDBをDjangoのプロジェクトとして利用し始めた事だと思う(実際のところは歴史的経緯により闇の中)。
ローカル開発環境では全てDjangoのマイグレーションで作成したため問題は発生しなかったが、何らかの理由でDBにマイグレーション以外の方法で手を加えている場合にこういったことが起こることがある。

終わりに

管理ツールを使っている場合、極力管理ツールで操作するようにしましょう。
あと、型の詳細を知るには desc だけでは不十分でした。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3