VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
INT AUTO_INCREMENT から離れるスキーママイグレーションは、苦痛です。このガイドでは、3 つの主要なアプローチ、それぞれが正しいツールとなる場面、そして VillageSQL が加えるものを扱います。
MySQL のデフォルト: AUTO_INCREMENT
AUTO_INCREMENT 整数がデフォルトであるのには理由があります。小さく (4 または 8 バイト)、挿入が速く、自然にソートされ、デバッガーやログファイルの中ですぐに人間が読めます。
AUTO_INCREMENT の問題は、スケールしたり統合したりするときに現れます。
- 行数の露出: ID の連番から
SELECT COUNT(*)を取れば、API クライアントに注文がいくつ存在するかが分かります。URL 内の連番の ID は、ユーザーにレコードの列挙を許します。 - 分散生成: 複数のライターをまたいで一意な ID を生成するには、シーケンスサーバー、指定されたライター、または手動の ID 範囲といった調整が必要です。
- データのマージ: 異なるデータベースの 2 つのテーブルをマージすると ID が衝突します。番号の振り直しが必要になります。
AUTO_INCREMENT がおそらく正しい答えです。そのままにしてください。
グローバルに一意な ID が必要なとき
標準的な解決策は UUID です。128 ビットのランダムな識別子で、調整なしにどのノードでも独立して生成でき、実際には衝突しません。 MySQL の組み込みのUUID() は v1 の UUID を CHAR(36) 文字列として返します。そのまま保存できますが、コストがあります。CHAR(36) は、バイナリの同等物の 16 バイトに対して 36 バイトです。また、ランダムな UUID (v4) は InnoDB のインデックスの断片化を引き起こします。新しい行がインデックスの末尾ではなく任意の場所に入り、ページ分割を強いるからです。
標準的な回避策は、手動の変換を伴う BINARY(16) です。
UUID_TO_BIN() の 1 フラグはタイムスタンプのビットを並べ替え、v1 の UUID が時系列順にソートされるようにします。これは特に v1 の UUID の断片化を減らしますが、手動のエンコードとデコードはすべてのクエリに摩擦を加えます。
VillageSQL の場合: ネイティブの UUID 型とソート可能なバージョン
VillageSQL のvsql_uuid 拡張機能は、ネイティブの UUID 列型と、UUID v1 から v7 までの生成関数を追加します。手動のエンコードとデコードは不要で、UUID v7 は挿入時の断片化の問題をきれいに解決します。
UUID 型はディスク上に 16 バイトを格納し、自動的に標準の 36 文字の文字列形式で表示されます。クエリに UUID_TO_BIN も BIN_TO_UUID も必要ありません。
決定論的な ID (同じ入力が常に同じ UUID を生む) には、v3 または v5 を使います。
UUID バージョンのリファレンス
追加の関数:
UUID_IS_VALID(string) は、非 NULL の文字列が整形式の UUID かどうかに応じて 1 または 0 を返し、引数自体が NULL のときは NULL を返します。テキストを受け取り、決してエラーを発生させません。これは外部システムから届く ID に対して望ましい性質です。UUID_VERSION(uuid) はバージョン番号を返し、すでに UUID として保存された ID を振り分けるのに使えます。UUID_COMPARE(uuid1, uuid2) は明示的な辞書順比較のために -1、0、または 1 を返します。
戦略の選択
新しいプロジェクトを始めるほとんどのチームには、内部テーブルには
BIGINT AUTO_INCREMENT、公開向けの ID を持つテーブルやシステムをまたいでマージされ得るデータには UUID_V7() を使うネイティブの UUID を推奨します。
よくある質問
UUID_V7 と UUID_V4 はいつ使い分けるべきですか?
書き込みの多いテーブルの主キーにはUUID_V7() を使ってください。上位ビットにタイムスタンプを埋め込むため、新しい UUID は常に既存のものの後にソートされ、挿入時に InnoDB がインデックスページを分割するのを防ぎます。UUID_V4() は、順序のセマンティクスを持たないランダムな ID が必要なとき (ワンタイムトークンなど) にのみ使ってください。
UUID を DEFAULT 式として使えますか?
MySQL 自体はUUID()、RAND()、NOW() のような非決定論的な関数を DEFAULT 式で許しています。本当の制限は VillageSQL 固有のものです。非決定論的として登録された拡張機能の関数 (UUID_V7() など) は DEFAULT 式から締め出され、UUID_V3()/UUID_V5() のような決定論的なものは許されます。ただし、その許可はここでの主キーには役立ちません。DEFAULT 式の中の UUID_V3()/UUID_V5() は固定の引数を取る (DEFAULT は他の列を読めない) ため、すべての行が同一の UUID を得て、2 回目の INSERT が主キーで 1 回目と衝突します。代わりに各 INSERT ステートメントで UUID_V7() を生成してください。
外部キーにも UUID を使うべきですか?
はい、ただしストレージコストに注意してください。UUID の外部キーは、参照元のテーブルで行あたり 16 バイトを占め、整数の 4〜8 バイトと比べて大きくなります。数百万行と複数の UUID 外部キーを持つテーブルでは、それが積み重なります。純粋に内部の参照には、AUTO_INCREMENT と整数の外部キーのほうが軽量です。
UUID_V3 と UUID_V5 の違いは何ですか?
どちらも名前空間と名前から決定論的な UUID を生成します。同じ入力は常に同じ出力を生みます。v3 は MD5 を、v5 は SHA-1 を使います。SHA-1 のほうがわずかに強いため、新しいコードでは v5 を優先してください。v3 は、v3 を使うシステムが生成した ID と一致させる必要があるときにのみ使ってください。トラブルシューティング
関連項目
- MySQL の UUID — MySQL での UUID のバージョンと格納の詳細
- InnoDB のデータ格納方法 — 主キーの選択が物理的なストレージレイアウトに影響する理由
- MySQL のインデックスの仕組み — 主キーは常にクラスタ化された B ツリーインデックスである
- MySQL のページネーション: キーセットと OFFSET の比較 — キーはページネーションのカーソルを兼ねる