Skip to content

データベース仕様

MySQL 8を使用したデータベース設計の詳細仕様


日付・時刻型

2038年問題

2038年問題(Y2K38問題)は、UNIX時間を32ビット符号付き整数で格納するシステムで発生する問題です。

  • UNIX時間は1970年1月1日 00:00:00 UTCからの経過秒数
  • 32ビット符号付き整数の最大値: 2,147,483,647
  • この値に達する日時: 2038年1月19日 03:14:07 UTC

この日時を超えると、整数がオーバーフローし、日付が1901年や1970年に戻るなどの深刻な問題が発生します。

MySQLにおける影響

範囲2038年問題
TIMESTAMP1970-01-01 00:00:01 〜 2038-01-19 03:14:07影響あり
DATETIME1000-01-01 00:00:00 〜 9999-12-31 23:59:59影響なし

日付・時刻型の使い分け

用途Laravelメソッド
DATETIME作成日時、更新日時、イベント日時などdateTime()
DATE誕生日、開始日など日付のみdate()
TIME営業時間など時刻のみtime()

Laravelマイグレーション

php
// timestamps()の代わりに以下を使用
$table->dateTime('created_at')->useCurrent();
$table->dateTime('updated_at')->useCurrent()->useCurrentOnUpdate();

// 個別のカラム
$table->dateTime('published_at')->nullable();
$table->date('birth_date')->nullable();

注意事項

  • DATETIMEはタイムゾーン情報を持たないため、アプリケーション側でタイムゾーン管理が必要
  • config/app.phptimezone設定を確認すること
  • 既存システムのTIMESTAMPカラムは、計画的にDATETIMEへ移行することを推奨
  • 2038年はまだ先だが、長期保存データ(契約終了日、保証期限など)は今すぐ影響を受ける可能性がある

データ型の選択

数値型

用途推奨型備考
主キー・外部キーBIGINT UNSIGNED$table->id() / $table->foreignId()
金額DECIMAL(10, 2)floatは精度の問題があるため使わない
個数・回数INT UNSIGNED負の値がない場合
小さな整数TINYINT UNSIGNED0〜255の範囲
php
// 金額
$table->decimal('price', 10, 2)->default(0);
$table->decimal('total_amount', 12, 2)->default(0);

// 個数
$table->unsignedInteger('quantity')->default(0);

文字列型

用途推奨型備考
短い固定長文字列VARCHAR(n)メールアドレス、電話番号など
長いテキストTEXT説明文、備考など
固定長コードCHAR(n)郵便番号、国コードなど
php
// 文字列長は用途に応じて指定
$table->string('email', 255);
$table->string('phone', 20);
$table->string('postal_code', 10);
$table->string('status', 20)->default('pending');

// 長いテキスト
$table->text('description')->nullable();

真偽値

php
// boolean型を使用
$table->boolean('is_active')->default(true);
$table->boolean('has_verified')->default(false);
$table->boolean('can_edit')->default(false);

JSON型

php
// 構造化データの保存に使用
$table->json('metadata')->nullable();
$table->json('settings')->nullable();

命名規則

テーブル名

  • snake_case(複数形)を使用
  • 例: users, orders, order_items, user_profiles

カラム名

種類規則
一般snake_casetotal_price, first_name
主キーidid
外部キー{テーブル単数形}_iduser_id, order_id
真偽値is_/has_/can_ 接頭辞is_active, has_verified
日時_at 接尾辞created_at, published_at
日付_date または _on 接尾辞birth_date, start_on

中間テーブル

  • アルファベット順で結合
  • 例: order_product, role_user, category_post

必須カラム

すべてのテーブルに以下を含める:

php
$table->id();
$table->dateTime('created_at')->useCurrent();
$table->dateTime('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->softDeletes();  // 必要に応じて

インデックス

基本方針

  • 検索・ソートに使用するカラムにはインデックスを設定
  • WHERE句やORDER BY句で頻繁に使用されるカラム
  • 外部キーカラム(LaravelのforeignId()は自動で設定される)
php
// 単一カラムインデックス
$table->index('email');
$table->index('status');

// 複合インデックス(よく一緒に使われるカラム)
$table->index(['status', 'created_at']);
$table->index(['user_id', 'status']);

// ユニーク制約
$table->unique('email');
$table->unique(['user_id', 'product_id']);  // 複合ユニーク

注意事項

  • インデックスの作りすぎに注意(INSERT/UPDATEのパフォーマンスに影響)
  • カーディナリティの低いカラム(真偽値など)単独でのインデックスは効果が薄い

外部キー制約

php
// 基本形(親レコード削除時にNULLを設定)
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();

// 親レコード削除時に子レコードも削除
$table->foreignId('order_id')->constrained()->cascadeOnDelete();

// 親レコード削除を禁止
$table->foreignId('category_id')->constrained()->restrictOnDelete();

ENUMを使わない

ENUMは将来の変更が困難なため使用しない。

php
// Bad
$table->enum('status', ['draft', 'published', 'archived']);

// Good: 文字列 + バリデーションで制御
$table->string('status', 20)->default('draft');

// Good: マスタテーブルで管理
$table->foreignId('status_id')->constrained('order_statuses');

マイグレーション例

php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('order_number', 20)->unique();
            $table->string('status', 20)->default('pending');
            $table->decimal('subtotal', 10, 2)->default(0);
            $table->decimal('tax', 10, 2)->default(0);
            $table->decimal('total', 10, 2)->default(0);
            $table->text('notes')->nullable();
            $table->dateTime('created_at')->useCurrent();
            $table->dateTime('updated_at')->useCurrent()->useCurrentOnUpdate();
            $table->softDeletes();

            $table->index(['status', 'created_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};

Gridworks Developer Documentation