ぶちのブログ

競プロとCTFが趣味なWebエンジニアのアウトプットの場

RDBのprimary keyについてのまとめ

はじめに

最近、RDBのprimary keyについて議論することが多かったため、それぞれのメリット・デメリットをまとめておきます。

この記事で扱うprimary keyの選択肢

この記事では、以下のprimary keyについて扱います。

  • natural key
  • surrogate key
    • RDBのauto incrementを使ったもの
      • RDBでのID発行
      • 事前採番方式
    • UUID
    • ULID
    • その他のID採番サービス

まずは各論を書いていきます。

natural key

natural keyが使えるのであれば、最もシンプルになるかと思います。
ただし、natural keyにできるカラムの制約は厳しく、

  • NOT NULL
  • unique
  • 不変性

の3つが必要です。
特に不変性の条件が厳しく、例えばユーザ名などをprimary keyにしてしまうと、ユーザ名の変更が難しくなってしまいます。

また、多くのカラムの複合キーになると、運用上も少し辛いのかもしれません。

RDBでのauto incrementのID発行

おそらく一番普通のやつで、Rails出身の私はこれしか知らなかった時期もあります。
数値型で扱いやすく、パフォーマンスも良いです。

デメリットとしては、型のある言語を書いている場合に、初期値が未定義になってしまい扱いが面倒になること(idがOptionになるなど)が挙げられます。
また、RDBの機能に依存してしまっているのも嫌な感じです。

RDBでのauto incrementを用いた事前採番方式

Postgresqlであればシーケンスを使うと、事前採番を実現できます。
MySQLなどを使う場合に、なるべく標準SQLで実装するのであれば、ID採番テーブルを使うことになるかと思います。

初期値が未定義になる問題が解決できる一方、単純に実装がちょっと複雑になり、IO回数が増えてパフォーマンスにやや懸念が出ます。

実装の詳細は、以下の記事を参考にしてみてください。
MySQL で採番テーブル - Qiita

UUIDを用いる

UUIDを用いる場合は、アプリケーション側でIDを発行できます。
auto incrementのデメリットを解消できますが、文字列になってしまう分、パフォーマンスの懸念が出てしまいます。

https://kccoder.com/mysql/uuid-vs-int-insert-performance/

また、デメリットとして、ページング処理が少しやりにくいかと思います。
例えば、最新順に取ろうとする場合などは、created_atが重複したレコードの返ってくる順番が不定なので、毎回primary keyでもソートする必要が出てきます。(実装依存でうまくいく場合も多そうですが)

ULID

ULIDを用いるとUUIDのソート周りの不便さを解消することができます。
しかし、パフォーマンスについてはUUIDと同程度の懸念があります。

やや新しく、枯れていない技術という印象を受けるのが不安です。
ライブラリがない場合は、単純ですが自分で実装する必要があります。

また、発番されるIDがアプリケーションサーバの時刻に依存するため、時刻の同期ズレの問題が発生する可能性があります。

その他のID採番サービス

IDを採番するためのサービスを別で運用することも考えられます。
数値型のIDをRDBとは別のところで発行することができるので、これまでに挙げたデメリットは解消できます。

ただ、どうしても仕組みが大掛かりになってしまうことと、ID採番サービス自体が障害点となりえることがデメリットです。

TwitterのSnowflakeが有名です。

総論

まず、大掛かりになってもよいのであれば、ID採番サービスを作っても良いでしょう。

そうでない場合には、

まずRDBが単一障害点になったり、RDB自体にロックインされてもよいか考えましょう。
そこが気になるのであれば、パフォーマンスを測定して検討した上で、UUIDかULIDを採用します。

サーバ間の時刻ズレがシビアになるような要件(入札など)があり、ULIDを使う場合には、時刻ズレに関しては注意を払う必要があります。
他にも、ライブラリがなくて自前実装を避けたい場合などには、UUIDを使う道理もあるかと思います。

特にデータもSELECT回数も多い場合で、UUIDやULIDではパフォーマンスに懸念があるのであれば、事前採番を行うこともできます。
ただし、ちゃんと測定しないと事前採番が不利になる可能性もあるので、やはりパフォーマンス測定は必要です。

RDBが単一障害点になっても、RDBにロックインされても問題なく、型のない言語で開発して困っていないのであれば、RDBのauto incrementをそのまま使っても問題ないです。
自分は、Railsで開発する場合には雑にauto incrementでやってしまう場合がほとんどです。

結論

IDの設計は、機能要件にも非機能要件にも関わる箇所で、後からの変更は難しいです。
にもかかわらず、どの方法にもデメリットが存在するので、銀の弾丸はありません。

結局は、事前に要件定義をちゃんとし、パフォーマンス測定などの検証も行った上で設計するしかないかと考えています。

参考記事

この記事を書くにあたり、以下の記事を大いに参考にさせていただきました。
ありがとうございました。

blog.j5ik2o.me