Appearance
データベース仕様
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年問題 |
|---|---|---|
TIMESTAMP | 1970-01-01 00:00:01 〜 2038-01-19 03:14:07 | 影響あり |
DATETIME | 1000-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.phpのtimezone設定を確認すること- 既存システムの
TIMESTAMPカラムは、計画的にDATETIMEへ移行することを推奨 - 2038年はまだ先だが、長期保存データ(契約終了日、保証期限など)は今すぐ影響を受ける可能性がある
データ型の選択
数値型
| 用途 | 推奨型 | 備考 |
|---|---|---|
| 主キー・外部キー | BIGINT UNSIGNED | $table->id() / $table->foreignId() |
| 金額 | DECIMAL(10, 2) | floatは精度の問題があるため使わない |
| 個数・回数 | INT UNSIGNED | 負の値がない場合 |
| 小さな整数 | TINYINT UNSIGNED | 0〜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_case | total_price, first_name |
| 主キー | id | id |
| 外部キー | {テーブル単数形}_id | user_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');
}
};