⛓️

primary key のカラムとは別に auto_increment するカラムがあるテーブルを migrate したい

2020/06/14に公開

前提

  • DB の主キーは連番ではない
  • 連番のカラムが必要
  • Laravel Framework 5.8.36
  • mysql Ver 14.14 Distrib 5.7.25

実行しようとした migration ファイル

Schema::create('tests', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->bigIncrements('counter');
    $table->timestamps();
});

出たエラー

SQLSTATE[42000]: Syntax error or access violation: 1068 Multiple primary key defined (SQL: alter table `tests` add primary key `tests_id_primary`(`id`))

「複数の primary key は設定できないぞ」って感じのメッセージである。
どうみても主キー1個だろ?と思ったが、何か隠された仕様があるのかもしれないと思い、 php artisan migrate --pretend を実行してみると。

create table `tests` (
`id` char(36) not null,
`counter` bigint unsigned not null auto_increment primary key,
`created_at` timestamp null,
`updated_at` timestamp null
) default character set utf8mb4 collate 'utf8mb4_unicode_ci'

alter table `tests` add primary key `posts_id_primary`(`id`)

なんか counterprimary key がついてる!!

そうなんです。
auto_increment を有効にすると自動で primary key がついてきてしまうんです。
実際に sql 文を構築しているクラスを読みに行くと、確かにそう書いてあるのだ。
Laravel migration core
なんてこった。
たしかに mysql の仕様では auto_increment のカラムには index 貼れとあるが、勝手に primary key に勝手にされたら困るんだなぁ。
実際には auto_increment = primary key というケースがほとんどなんだろうけど、明示的に primary key にするメソッドがあるんだからさぁ。

解決策

いくつか対応パターンを考えた。

  1. UUID を諦める
  2. UUID は利用するが primary key は number に譲って、モデルなどの運用でカバー
  3. 生の SQL を書く
  4. migration ファイルで頑張る
  5. このクラスをオーバーライドする方法を探して、 primary key つけないようにする

今回は 4 を採用した。

修正した migration ファイル

Schema::create('tests', function (Blueprint $table) {
    $table->uuid('id');                 // 1
    $table->bigIncrements('counter');
    $table->timestamps();

    $table->unique('counter');          // 2
    $table->dropPrimary();              // 3
    $table->primary('id');              // 4
});

やってること

  1. id のカラムから primary() のメソッドを削除
  2. counterunique を付与
  3. primary key を削除
  4. idprimary key に指定

確認

mysql> desc tests;
+-------------------+---------------------+------+-----+---------+----------------+
| Field             | Type                | Null | Key | Default | Extra          |
+-------------------+---------------------+------+-----+---------+----------------+
| id                | char(36)            | NO   | PRI | NULL    |                |
| counter           | bigint(20) unsigned | NO   | UNI | NULL    | auto_increment |
| created_at        | timestamp           | YES  |     | NULL    |                |
| updated_at        | timestamp           | YES  |     | NULL    |                |
+-------------------+---------------------+------+-----+---------+----------------+

おつかれさまでした

なかなかこんな変なことする人はいないと思うけれども、宗教上の理由で自分は uuid(ulid) を使い続けるので、またこれに出くわすかもしれないので防備しておく。

Discussion