LaravelのマイグレーションでTIMESTAMP型カラムが意図せず自動更新される

発生した問題

Laravelのマイグレーションで作成したTIMESTAMP型カラムが、明示的に値を指定していないUPDATE実行時に自動更新されるという事象に遭遇しました。原因を調査したので、その内容を共有します。

環境

  • PHP(Laravel)
  • MariaDB 10.10未満

再現コード

以下のマイグレーションでカラムを作成します。

Schema::create('your_table', function (Blueprint $table) {
    $table->id();
    $table->timestamp('token_expires');
});

このテーブルに対してレコードをUPDATEすると、token_expiresを指定していないにもかかわらず値が更新されます。

原因

MariaDBの自動付与仕様

MariaDB 10.10未満では、システム変数explicit_defaults_for_timestampが未設定の場合(これがデフォルト値)、テーブル内で最初に定義されたTIMESTAMP型カラムに対し、NULL属性、DEFAULT句、ON UPDATE句のいずれも明示していなければ、次の属性が自動的に付与されます。

  • DEFAULT current_timestamp()
  • ON UPDATE current_timestamp()

この仕様には、押さえておくべきポイントが2つあります。

  1. 対象になるのは最初のTIMESTAMP型カラムのみです。2番目以降のTIMESTAMP型カラムは、DEFAULT句を省略していても自動付与の対象にはなりません。
  2. 「肩代わり」は発生しません。先頭のTIMESTAMP型カラムが何らかの理由(NULL属性の明示など)で対象外だった場合でも、その特別扱いが次のカラムに移ることはありません。

今回のケースでは、token_expiresがテーブル内で最初に定義されたTIMESTAMP型カラムだったため、この自動付与の対象になったと考えられます。

なお、$table->timestamps()created_at/updated_atを先に定義していれば、話は変わります。Laravelはこれらをnullable()付き、つまりNULL属性を明示した状態で生成するため、自動付与の対象から除外され、後続のカラムにも影響しません。

Laravel側の挙動

$table->timestamp('token_expires')と書いた場合、Laravelはnullable()を指定しない限りNOT NULLを付与しますが、DEFAULT句については何も出力しません。

結果として、MariaDBに送られるSQLは次のようになります。

CREATE TABLE `your_table` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `token_expires` TIMESTAMP NOT NULL,
  PRIMARY KEY (`id`)
);

NOT NULLは指定されていても、DEFAULT句、ON UPDATE句が明示されていないため、token_expiresが最初のTIMESTAMP型カラムであるこのテーブルでは、MariaDB(10.10未満)側がDEFAULT current_timestamp() ON UPDATE current_timestamp()を自動で埋め込んでしまいます。

まとめ

  • MariaDB 10.10未満は、explicit_defaults_for_timestampが未設定だと、テーブル内最初のTIMESTAMP型カラムにDEFAULT/ON UPDATE句を自動付与する仕様を持っています。
  • 対象になるのは先頭のTIMESTAMP型カラムのみで、対象外になった分が後続カラムに引き継がれることはありません。
  • Laravelの$table->timestamp()はDEFAULT句を出力しないため、意図せずこの自動付与仕様の対象になり得ます。
  • $table->timestamps()created_at/updated_atを先に定義しておくと、これらがNULL許容で生成されるため、後続のTIMESTAMP型カラムを自動付与仕様から守ることができます。

参考

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください