PK/Unique KEY が UUID v4 だと INDEX 作成効率が悪い

zenn.dev

プライマリキーにUUIDを採用した場合のINSERT時間のペナルティ

っていう話があったので、へーって思って見てみた。対象の MySQL はただ docker run mysql:8.0 したヤツです。

string が主キーで、PK があるものと無いもの、UUID v4 と v1 とで比べれば良かろう。(v1 は値が偏るので)

create table bar(id varchar(255) not null, primary key(id));
create table baz(id varchar(255) not null, primary key(id));
create table qux(id varchar(255) not null);
INSERT_SQL = <<~SQL
INSERT INTO %s(id) values
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?),
  (?), (?), (?), (?), (?), (?), (?), (?), (?), (?)
SQL

COUNT_NUM = 50_000; # 回 *100 行の INSERT

def execute(table_name, &block)
  COUNT_NUM.times do |i|
    puts "#{table_name}\t#{i}\t#{Time.now.to_i}" if i % 1000 == 0 # 10万行 INSERT ごとに時間を出力
    db.xquery(INSERT_SQL % table_name, *100.times.map{ block.call })
  end
end

uuid = UUID.new
execute("bar") { SecureRandom.uuid } # UUID v4
execute("baz") { uuid.generate }     # UUID v1
execute("qux") { SecureRandom.uuid } # UUID v4 PK なし

100 行ずつ 5 万回 INSERT、1000 INSERT (=10 万行)ごとに時間を取得しました。

結果をグラフにするとこう。横軸が総 INSERT 行数、縦軸は 10 万行の INSERT にかかった秒数。

f:id:onk:20201013163946p:plain

へー。たしかに 100 万レコードを過ぎた辺りから徐々に遅くなってるのが分かる。 もうちょっと右側 (数千万レコード級) でどうなるかも見てみたい……。

UUID 文字列を使うのが間違い (例えば UUID_TO_BIN() 使えよとか) という話もなくはないし、1 行あたりだと 1ms 切るので数千万程度なら大して気にはしないと思うけど、とりあえず。

自分では snowflake 系に慣れきっちゃってるので、コレを見てもなんともだなぁ。

おもしろかった。