実例テストMySQLのenumタイプ

3867 ワード

プロジェクトを開発する時、通常いくつかのステータスフィールドがあります。例えば、注文の状態は支払いが必要です。すでに支払いました。すでに閉じました。払い戻しました。以前やっていたプロジェクトはこれらの状態をデジタルでデータベースに保存して、phpコードの中で定数でマッピングテーブルを維持します。例えば:

const STATUS_PENDING = 0;
const STATUS_PAID = 1;
const STATUS_CLOSED = 2;
const STATUS_REFUNDED = 3;
しかし、実際の使用過程では、あまり良くないことが分かりました。様々な原因(追跡バグ、臨時の統計需要など)で、私達は常にmysqlサーバに登録して、手動でsqlクエリを実行します。注意しないと、他の表の状態を数字で混ぜてしまうと大きな問題になります。
そこで私は新しいプロジェクトの中でmysqlのenumタイプを使って様々な状態を格納したいです。使う過程で、Laravelのmigrationファイルの中でenumタイプを使ったテーブルを変更すると、エラーが発生します。

[Doctrine\DBAL\DBALException]
Unknown database type enum requested, Doctrine\DBAL\Platforms\MySQL57Platform may not support it.
検索したら、doctrineがmysqlのenumをサポートしていないことが分かりました。この文にはenumの3つの欠点が挙げられています。
enum値を追加するには、表全体を再構築する必要があります。データ量が大きい場合は数時間がかかります。
enum値の並び替え規則は、文字面の大きさではなく、表構造を作成するときに指定される順序である。
mysqlに依存してenum値をチェックする必要はありません。標準設定で不正値を挿入すると最終的には空の値になります。
新しいプロジェクトの実際の状況によっては、状態フィールドをソートする必要がありますが、私たちがテーブル構造を設計する時に順序を決めてもいいです。ですから、欠点2は無視できます。欠点3はコード仕様、挿入/更新前検査などで回避できます。欠点1については、いくつかのテストが必要です。
テストの準備
まずテーブルを作成します。

CREATE TABLE `enum_tests` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `status` enum('pending','success','closed') COLLATE utf8mb4_unicode_ci NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
100 Wのデータを挿入します。

$count = 1000000;
$bulk = 1000;
$data = [];
foreach (['pending', 'success', 'closed'] as $status) {
  $data[$status] = [];
  for ($i = 0; $i < $bulk; $i++) {
    $data[$status][] = ['status' => $status];
  }
}

for ($i = 0; $i < $count; $i += $bulk) {
  $status = array_random(['pending', 'success', 'closed']);
  EnumTest::insert($data[$status]);
}
テストプロセス
テスト1〓〓
enum値リストに最後に値refundedを追加します。

ALTER TABLE `enum_tests` CHANGE `status` `status` ENUM('pending','success','closed','refunded') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
出力:

Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
結論:エンム値を末尾に追加するとほとんどコストがかかりません。
テスト2:菵
先ほど追加した値を削除します。

ALTER TABLE `enum_tests` CHANGE `status` `status` ENUM('pending','success','closed') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
出力:

Query OK, 1000000 rows affected (5.93 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
結論:未使用のENum値を削除するにはまだ全表スキャンが必要で、コストが高いが、まだ許容範囲内にある。
テスト3:菷
最後ではなく、値リストの中間にrefundedを挿入します。

ALTER TABLE `enum_tests` CHANGE `status` `status` ENUM('pending','success','refunded', 'closed') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
出力:

Query OK, 1000000 rows affected (6.00 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
結論:元のenum値リストの中で新たな増値は全表スキャンと更新が必要で、コストが高い。
テスト4:菵
値リストの中央の値を削除します。

ALTER TABLE `enum_tests` CHANGE `status` `status` ENUM('pending','success','closed') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
出力:

Query OK, 1000000 rows affected (4.23 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
結論:全表スキャンが必要で、コストが高い。
テスト5:菵
statusフィールドにインデックスを追加してから上記のテストを実行します。

ALTER TABLE `enum_tests` ADD INDEX(`status`);
テスト2-4の時間がかかると、逆に増加します。インデックスを更新する必要があります。
結語:菵
私の新しいプロジェクトにはenum値が追加された場合しかありません。将来は個別の状態で廃棄しても、enumの値リストを調整する必要がないので、プロジェクトにenumタイプを入れて格納状態のデータタイプにすることにしました。