UUIDって何?SQLiteで苦戦したデータベース初心者の体験記
はじめに
インターンシップの中でデータベースを扱う中で、データを一意に識別するにはIDが必要だと学び、そこでUUID(Universally Unique Identifier) に出会いました。
本記事では、まずUUIDについて簡単に解説し、その後で実際にSQLiteでDMLを作成してUUIDを導入しようとしたときに経験した苦戦と学びをまとめます。特にSQLiteはPostgreSQLやMySQLと比べるとUUIDの扱いが独特で、初心者がハマりやすいポイントが多いと感じました。これから同じように挑戦する方の参考になれば幸いです。
UUIDとは何か
UUIDとは Universally Unique Identifier(汎用一意識別子) の略で、コンピュータ・システム内の情報を識別するために使用される128ビットの数値で構成される一意な識別子です。人が読むときには16進数で表現され、0から9までの数字とAからFまでの文字を使用し、4つのハイフンで区切られ、例えば、以下のように表示されます。
550e8400-e29b-41d4-a716-446655440000
本当に重複しないの?
128ビットなので、2^128 通りの組み合わせが可能であり、天文学的に大きな数ですが、理論上は有限なので重複する可能性がありますが、実際に重複する確率が極めて低いため、実用上は重複しないとされているようです。
UUIDのバージョン
UUIDにはいくつかのバージョンがあり、それぞれ生成方法と利用シーンが異なります。代表的なのは以下です。
-
v1: 時間 + クロックシーケンス + MACアドレスベースで生成(MACアドレスが含まれるため、プライバシー懸念あり)
- 16進表記をすると、
-
のような構造で、
TTTTTTTT-TTTT-1TTT-sSSS-AAAAAAAAAAAA
T
:タイムスタンプ、S
:クロックシーケンス、A
:ノードからなる
-
v3/v5: 名前空間識別子 + その名前空間内で一意な名前(文字列)を連結し、ハッシュアルゴリズムでハッシュ値を計算して(v3 は MD5 ハッシュアルゴリズム / v5 は SHA-1 ハッシュアルゴリズム)
- 16進表記をすると、
-
のような構造で、
HHHHHHHH-HHHH-3HHH-hHHH-HHHHHHHHHHHH HHHHHHHH-HHHH-5HHH-hHHH-HHHHHHHHHHHH
H
:ハッシュ値
-
v4: ランダムベース(最も広く利用されている)
- 16進表記をすると、
-
のような構造で、
RRRRRRRR-RRRR-4RRR-rRRR-RRRRRRRRRRRR
R
:ランダム値
-
v7: 時間 + ランダムベース(最近注目)
- 16進表記をすると、
-
のような構造で、
TTTTTTTT-TTTT-7RRR-rRRR-RRRRRRRRRRRR
T
:タイムスタンプ、R
:ランダム値
つまりUUIDは「ただのランダム文字列」ではなく、バージョンによって内部構造や用途が異なるのです。
UUID v7 をターミナルで生成してみる
最近のUUID v7はターミナルから簡単に試せます。私は Node.js 環境だったので、pnpm dlx
を使ってサクッと生成してみました。
$ pnpm dlx uuidv7
0198d449-c550-7856-b07b-4f8129f9513b
SQLiteでのUUID導入経験
やりたかったこと
今回私がやりたかったのは、アプリの初期データを投入するためのDML(Data Manipulation Language)ファイルを作ることでした。
たとえば以下のように、ユーザーテーブルにあらかじめレコードを登録しておきたかったのです。
insert into "users" ("id", "username", "email") values
('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'alice', 'alice@example.com'),
('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'bob', 'bob@example.com'),
('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', 'charlie', 'charlie@example.com');
id カラムに UUID を使うことで、アプリ側でレコードを生成するたびに衝突の心配なく一意なIDを持たせたい、という狙いがありました。
しかし、PostgreSQL などなら gen_random_uuid()
として UUID 型を使えますが、SQLite には UUID を自動生成する仕組みがないため 、アプリケーション側で生成して INSERT する必要がありました。
対策アプローチ(Drizzle経由で)
私は Node.js 環境で uuidv7 パッケージを使い、Drizzle 経由で drizzle-orm を用いて初期データを INSERT するスクリプト(sample.ts)を書きました。drizzle-orm は、JavaScript / TypeScript で使えるデータベース操作をラクにするライブラリで、簡単に言うと「テーブルやレコードをコード上のオブジェクトとして扱えるようにするもの」です。今回の方法では、以下のライブラリを使用しました(公式サイト):
pnpm add drizzle-orm @libsql/client
@libsql/client は SQLite に接続するためのクライアントライブラリで、データベースの URL を指定するだけで簡単に接続できます。
書いたスクリプト(sample.ts)の実行は以下のようにします:
tsx ./sample.ts
スクリプトコード(sample.ts):
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { uuidv7 } from "uuidv7";
import { users } from "./schema";
const DATABASE_URL =
"file:/Example/Example/......"; // 自分の SQLite ファイルパスに書き換えてください
const client = createClient({ url: DATABASE_URL });
const db = drizzle(client);
const usersList = [
{ username: "alice", email: "alice@example.com" },
{ username: "bob", email: "bob@example.com" },
{ username: "charlie", email: "charlie@example.com" },
];
await db.insert(users).values(
usersList.map((item) => ({
id: uuidv7(),
username: item.username,
email: item.email,
})),
);
こうすることで、SQLiteでもUUIDを使った初期データ投入ができました。Node.js 側で UUID を生成して挿入するので、手で一つずつ作る必要がなくなり、作業がぐっと楽になります。
SQLファイルを生成してみた
さらに、sample.ts を少し工夫して SQLファイル(DML)を生成してみました。
import { writeFileSync } from "fs";
const valuesToInsert = usersList.map((item) => ({
id: uuidv7(),
username: item.username,
email: item.email,
}));
// SQL を文字列として取得
const { sql, params } = db.insert(users).values(valuesToInsert).toSQL();
// プレースホルダ (?) を実際の値に置き換える
let finalSQL = sql;
params.forEach((param) => {
const escaped = typeof param === "string" ? `'${param}'` : param;
finalSQL = finalSQL.replace("?", String(escaped));
});
// ファイルに出力
writeFileSync("sample.sql", finalSQL);
これを実行すると、sample.sql ファイルが生成されます。中身はこんな感じで、すべて 1 行に展開されます。
insert into "users" ("id", "username", "email") values ('0198d508-8296-7d03-8213-4109031b87de', 'alice', 'alice@example.com'), ('0198d449-c550-7856-b07b-4f8129f9513b', 'bob', 'bob@example.com'), ('0198d509-018b-79e8-9552-a264958696a8', 'charlie', 'charlie@example.com');
見ての通り、今の状態だと 1 行にずらーっと並んでしまうのでちょっと読みにくいですが、コードを少し変えれば、各レコードを改行して整形することもできます。(ただしこの記事では省略します)
最後に
インターンでの実装を通して、「UUID をどう扱うか」という点でたくさん学びがありました。特に SQLite では自動生成がないため、アプリ側での工夫が必要なのが大きな発見でした。
これから SQLite で UUID を使った初期データ作成に挑戦する人に、この記事が少しでも参考になれば嬉しいです。
Discussion