メインコンテンツへスキップ

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
主キーの決定は取り消しにくいものです。早い段階での誤った選択、たとえば 12 のテーブルに外部キーが散らばった後で INT AUTO_INCREMENT から離れるスキーママイグレーションは、苦痛です。このガイドでは、3 つの主要なアプローチ、それぞれが正しいツールとなる場面、そして VillageSQL が加えるものを扱います。

MySQL のデフォルト: AUTO_INCREMENT

AUTO_INCREMENT 整数がデフォルトであるのには理由があります。小さく (4 または 8 バイト)、挿入が速く、自然にソートされ、デバッガーやログファイルの中ですぐに人間が読めます。
InnoDB のクラスタ化インデックスは行を主キーの順序で格納します。自動増分のキーでは、新しい行はすべて B ツリーの末尾に入ります。つまり、ページ分割もインデックスの断片化もなく、順次挿入が高速です。ほとんどの単一データベースのアプリケーションでは、これが正しい選択です。 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 の格納の仕組みと挿入時の断片化の問題の詳細については、MySQL の UUID を参照してください。

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 と一致させる必要があるときにのみ使ってください。

トラブルシューティング

関連項目