diff --git a/account/sql/20251001-migrate_to_uuid.md b/account/sql/20251001-migrate_to_uuid.md new file mode 100644 index 0000000..2a775de --- /dev/null +++ b/account/sql/20251001-migrate_to_uuid.md @@ -0,0 +1,68 @@ +# 前置准备 + +1. 确认扩展 +登录 PostgreSQL,执行: \dx 看看是否已经有 uuid-ossp 或 pgcrypto。 +如果没有,就执行: + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +或者: +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + + +⚠️ 推荐用 pgcrypto(函数是 gen_random_uuid()),更现代; +如果你已经用了 uuid-ossp,脚本里的 uuid_generate_v4() 就能直接用。 + +2. 备份数据库 + +这是最关键的保险: + +pg_dump -U -d > backup_before_uuid.sql + +3. 执行迁移 + +假设你的 schema 在 schema.sql 文件里,迁移脚本在 migrate_to_uuid.sql 文件里。 + +执行: psql -U -d -f migrate_to_uuid.sql + + +这个脚本会做三步: + +- 新增 uuid 列,填充数据。 +- 新建 user_uuid 外键字段,保持和旧 id 外键并存。 +- 删除旧的 id、user_id,把 uuid 设为主键。 + +4. 验证结果 + +迁移后,进入数据库: psql -U -d + +查看表结构: + +\d users +\d identities +\d sessions + + +应该看到: + +users(uuid PRIMARY KEY, username, password, email, created_at …) +identities(uuid PRIMARY KEY, user_uuid REFERENCES users(uuid), provider, external_id …) +sessions(uuid PRIMARY KEY, user_uuid REFERENCES users(uuid), token, expires_at …) + +再验证外键是否正确: + +SELECT constraint_name, table_name, column_name, foreign_table_name +FROM information_schema.key_column_usage +WHERE table_schema = 'public'; + +回滚策略 + +如果迁移出错,可以用之前的备份恢复: + +psql -U -d < backup_before_uuid.sql + +总结: + +先启用扩展 +再运行迁移脚本 +验证新表结构和外键 +保留备份,随时可回滚 diff --git a/account/sql/20251001-migrate_to_uuid.sql b/account/sql/20251001-migrate_to_uuid.sql new file mode 100644 index 0000000..2d6b7f3 --- /dev/null +++ b/account/sql/20251001-migrate_to_uuid.sql @@ -0,0 +1,117 @@ +------------------------------------------------ +-- STEP 0: 启用 UUID 扩展 +------------------------------------------------ +-- 两种方式任选其一,推荐 pgcrypto(更现代) +-- 如果数据库还没装扩展,可以先运行 CREATE EXTENSION + +-- 方式一:使用 uuid-ossp +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- 方式二:使用 pgcrypto +-- CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- 注意: +-- uuid-ossp 用 uuid_generate_v4() +-- pgcrypto 用 gen_random_uuid() + +------------------------------------------------ +-- STEP 1: 平滑迁移(添加 UUID 字段并建立外键) +------------------------------------------------ + +-- ========== Users ========== +ALTER TABLE users + ADD COLUMN IF NOT EXISTS uuid UUID DEFAULT uuid_generate_v4(); + +UPDATE users SET uuid = uuid_generate_v4() WHERE uuid IS NULL; + +ALTER TABLE users + ALTER COLUMN uuid SET NOT NULL; + +ALTER TABLE users + ADD CONSTRAINT users_uuid_unique UNIQUE (uuid); + +-- ========== Identities ========== +ALTER TABLE identities + ADD COLUMN IF NOT EXISTS uuid UUID DEFAULT uuid_generate_v4(); + +UPDATE identities SET uuid = uuid_generate_v4() WHERE uuid IS NULL; + +ALTER TABLE identities + ALTER COLUMN uuid SET NOT NULL; + +ALTER TABLE identities + ADD CONSTRAINT identities_uuid_unique UNIQUE (uuid); + +-- 新增 user_uuid 外键字段 +ALTER TABLE identities + ADD COLUMN IF NOT EXISTS user_uuid UUID; + +UPDATE identities i +SET user_uuid = u.uuid +FROM users u +WHERE i.user_id = u.id; + +ALTER TABLE identities + ALTER COLUMN user_uuid SET NOT NULL; + +ALTER TABLE identities + ADD CONSTRAINT identities_user_uuid_fk FOREIGN KEY (user_uuid) REFERENCES users(uuid) ON DELETE CASCADE; + +-- ========== Sessions ========== +ALTER TABLE sessions + ADD COLUMN IF NOT EXISTS uuid UUID DEFAULT uuid_generate_v4(); + +UPDATE sessions SET uuid = uuid_generate_v4() WHERE uuid IS NULL; + +ALTER TABLE sessions + ALTER COLUMN uuid SET NOT NULL; + +ALTER TABLE sessions + ADD CONSTRAINT sessions_uuid_unique UNIQUE (uuid); + +-- 新增 user_uuid 外键字段 +ALTER TABLE sessions + ADD COLUMN IF NOT EXISTS user_uuid UUID; + +UPDATE sessions s +SET user_uuid = u.uuid +FROM users u +WHERE s.user_id = u.id; + +ALTER TABLE sessions + ALTER COLUMN user_uuid SET NOT NULL; + +ALTER TABLE sessions + ADD CONSTRAINT sessions_user_uuid_fk FOREIGN KEY (user_uuid) REFERENCES users(uuid) ON DELETE CASCADE; + + +------------------------------------------------ +-- STEP 2: 清理(彻底切换到 UUID 主键) +------------------------------------------------ + +-- 删除原有的主键约束 +ALTER TABLE users DROP CONSTRAINT users_pkey; +ALTER TABLE identities DROP CONSTRAINT identities_pkey; +ALTER TABLE sessions DROP CONSTRAINT sessions_pkey; + +-- 删除旧的 id 外键 +ALTER TABLE identities DROP CONSTRAINT IF EXISTS identities_user_id_fkey; +ALTER TABLE sessions DROP CONSTRAINT IF EXISTS sessions_user_id_fkey; + +-- 设置 uuid 为新的主键 +ALTER TABLE users ADD CONSTRAINT users_pkey PRIMARY KEY (uuid); +ALTER TABLE identities ADD CONSTRAINT identities_pkey PRIMARY KEY (uuid); +ALTER TABLE sessions ADD CONSTRAINT sessions_pkey PRIMARY KEY (uuid); + +-- 删除旧的 id 字段 +ALTER TABLE users DROP COLUMN id; +ALTER TABLE identities DROP COLUMN id; +ALTER TABLE sessions DROP COLUMN id; + +-- 删除旧的 user_id 外键字段 +ALTER TABLE identities DROP COLUMN user_id; +ALTER TABLE sessions DROP COLUMN user_id; + +------------------------------------------------ +-- Done: 所有表都只用 UUID 作为主键/外键 +------------------------------------------------ diff --git a/account/sql/schema.sql b/account/sql/schema.sql index a3b4524..5fe3970 100644 --- a/account/sql/schema.sql +++ b/account/sql/schema.sql @@ -1,5 +1,10 @@ +-- 启用扩展(只需执行一次) +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +-- 或者:CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, + id SERIAL UNIQUE, -- 保留自增 id 作为内部用途 + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- 业务主键 username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, email TEXT, @@ -7,16 +12,18 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS identities ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + id SERIAL UNIQUE, + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES users(uuid) ON DELETE CASCADE, provider TEXT NOT NULL, external_id TEXT NOT NULL, UNIQUE(provider, external_id) ); CREATE TABLE IF NOT EXISTS sessions ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + id SERIAL UNIQUE, + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES users(uuid) ON DELETE CASCADE, token TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL );