Skip to content

sql

Terminal window
# https://archlinux.org/packages/?sort=&q=postgresql&maintainer=&flagged=
sudo pacman -S postgresql
# 初始化数据库
# 确保目录权限正确
sudo chown postgres:postgres /var/lib/postgres/data
sudo chmod 700 /var/lib/postgres/data
# 切换到 postgres 用户并初始化
sudo -iu postgres # -i: 模拟登录(创建完整的登录环境,加载用户的环境变量); -u postgres: 切换到 postgres 用户身份
initdb --locale=C -E UTF8 -D /var/lib/postgres/data # --locale=C: 设置数据库的区域设置为 C(POSIX 标准),这是最通用的设置; -E UTF8: 设置数据库的默认字符编码为 UTF-8,支持多语言字符; -D /var/lib/postgres/data: 指定数据库文件存储目录
# 成功后会显示:成功。你现在可以用下面的命令开启数据库服务器: pg_ctl -D /var/lib/postgres/data -l logfile start
exit # 退出 postgres 用户
# 启动服务
sudo systemctl start postgresql # 启动 PostgreSQL 服务
sudo systemctl enable postgresql # 设置 PostgreSQL 服务开机自启
Terminal window
sudo apt update
sudo apt install postgresql
# 启动服务
sudo systemctl start postgresql
sudo systemctl enable postgresql

默认自带用户 postgres

Terminal window
# 使用 postgres 超级用户创建一个名为 "aa" 的用户,并赋予其创建数据库、角色和超级用户权限
sudo -u postgres createuser -d -r -s aa
# 创建用户并设置密码
sudo -u postgres psql -c "CREATE USER myuser WITH PASSWORD 'mypassword';"
# 或修改已有用户密码
sudo -u postgres psql -c "ALTER USER myuser WITH PASSWORD 'mypassword';"
Terminal window
# 创建数据库 (使用 createdb 工具,推荐)
## 以 postgres 用户身份运行
sudo -u postgres createdb mcc # postgresql://postgres@localhost:5432/mcc
sudo -u postgres createdb -O labrinth labrinth # postgresql://labrinth:labrinth@localhost/labrinth
# 验证
psql -U labrinth -d labrinth -h localhost -W
# 连接到指定数据库
sudo -u postgres psql -d mcc
Terminal window
sudo -u postgres dropdb labrinth
Terminal window
for f in $(ls migrations/*.sql | sort); do
psql -U labrinth -d labrinth -f "$f"
done
-- 1. 去掉旧的默认值
ALTER TABLE "project_member"
ALTER COLUMN "permissions" DROP DEFAULT;
-- 2. 修改类型,并把已有数据转换为数组
ALTER TABLE "project_member"
ALTER COLUMN "permissions" SET DATA TYPE text[]
USING CASE
WHEN permissions = '[]' THEN '{}'::text[] -- 把 '[]' 映射为空数组
ELSE (SELECT jsonb_array_elements_text(permissions::jsonb)) -- '["read","write"]' -> ["read","write"]
END;
-- 3. 设置新的默认值为空数组
ALTER TABLE "project_member"
ALTER COLUMN "permissions" SET DEFAULT '{}'::text[];
-- 1. 删除默认值
ALTER TABLE project
ALTER COLUMN game_versions DROP DEFAULT;
-- 2. 创建转换函数
CREATE OR REPLACE FUNCTION jsonb_to_text_array(input jsonb) RETURNS text[] AS $$
BEGIN
-- 如果是空 jsonb 数组,返回空 text[];否则展开聚合成 text[]
IF input = '[]'::jsonb OR input IS NULL THEN
RETURN '{}'::text[];
ELSE
RETURN ARRAY(SELECT jsonb_array_elements_text(input));
END IF;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 3. 修改列类型并转换已有数据
ALTER TABLE project
ALTER COLUMN game_versions
SET DATA TYPE text[]
USING jsonb_to_text_array(game_versions);
-- 4. 添加默认值
ALTER TABLE project
ALTER COLUMN game_versions
SET DEFAULT '{}'::text[];
-- SERIAL 实际上是 INTEGER + DEFAULT nextval('sequence_name')
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
-- 等价于:
CREATE SEQUENCE users_id_seq;
CREATE TABLE users (
id INTEGER NOT NULL DEFAULT nextval('users_id_seq') PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE logs (
id BIGSERIAL PRIMARY KEY, -- 支持更大范围的ID
message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE categories (
id SMALLSERIAL PRIMARY KEY, -- 适用于较小的表
name VARCHAR(50)
);

2. IDENTITY 列 (SQL 标准方式,推荐)

Section titled “2. IDENTITY 列 (SQL 标准方式,推荐)”
CREATE TABLE products (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2)
);
-- 插入时不能指定 id 值
INSERT INTO products (name, price) VALUES ('iPhone', 999.99);
CREATE TABLE orders (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
customer_id INTEGER,
total DECIMAL(10,2)
);
-- 可以手动指定 id 值,也可以让系统自动生成
INSERT INTO orders (customer_id, total) VALUES (1, 100.00); -- 自动生成 id
INSERT INTO orders (id, customer_id, total) VALUES (1000, 2, 200.00); -- 手动指定 id
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 或者使用内置函数 (PostgreSQL 13+)
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TABLE sessions (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
-- 或使用内置函数 (PostgreSQL 13+)
-- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id INTEGER,
token VARCHAR(255),
expires_at TIMESTAMP
);
  • 全局唯一,适合分布式系统
  • 不会泄露表的记录数量
  • 更好的安全性

4. NanoID 类型(推荐的现代替代方案)

Section titled “4. NanoID 类型(推荐的现代替代方案)”

NanoID 是一个小巧、安全、URL友好的唯一ID生成器,由 Andrey Sitnik 开发,是 UUID 的现代替代方案。

  • 更小的体积: 默认21个字符,比UUID的36个字符更紧凑
  • URL友好: 不包含特殊字符,可直接用于URL
  • 更高性能: 生成速度比UUID快60%
  • 可定制: 可自定义长度和字符集
  • 更安全: 使用更好的随机数生成算法
  • 无冲突: 即使每秒生成1000个ID,需要1千年才可能出现重复
-- 安装 nanoid 扩展 (需要先从源码编译安装)
CREATE EXTENSION IF NOT EXISTS nanoid;
-- 创建表使用 nanoid
CREATE TABLE posts (
id TEXT DEFAULT nanoid() PRIMARY KEY,
title VARCHAR(255),
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 自定义长度的 nanoid
CREATE TABLE short_urls (
id TEXT DEFAULT nanoid(10) PRIMARY KEY, -- 10个字符长度
original_url TEXT,
clicks INTEGER DEFAULT 0
);
-- 在应用层生成 NanoID,数据库只存储
CREATE TABLE articles (
id TEXT PRIMARY KEY, -- 应用层生成的 nanoid
title VARCHAR(255),
content TEXT,
author_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据 (ID 由应用层生成)
-- JavaScript: import { nanoid } from 'nanoid'
-- Python: from nanoid import generate
-- 然后插入: INSERT INTO articles (id, title, content) VALUES ('V1StGXR8_Z5jdHi6B-myT', 'My Article', 'Content...');
-- 创建简单的 nanoid 生成函数
CREATE OR REPLACE FUNCTION generate_nanoid(size integer DEFAULT 21)
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
alphabet text := '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
id text := '';
i integer;
BEGIN
FOR i IN 1..size LOOP
id := id || substr(alphabet, floor(random() * length(alphabet) + 1)::integer, 1);
END LOOP;
RETURN id;
END;
$$;
-- 使用自定义函数
CREATE TABLE users (
id TEXT DEFAULT generate_nanoid() PRIMARY KEY,
email VARCHAR(255) UNIQUE,
username VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
特性NanoIDUUID
长度21字符36字符
URL友好❌ (包含-)
性能更快较慢
数据库支持需要扩展/函数原生支持
碰撞概率极低极低
人类可读性更好较差
import { nanoid } from 'nanoid';
const id = nanoid(); // "V1StGXR8_Z5jdHi6B-myT"
const shortId = nanoid(10); // "IRFa-VaY2b"
// 自定义字符集
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('1234567890abcdef', 10);
const id = nanoid(); // "4f90d13a42"
from nanoid import generate
id = generate() # "V1StGXR8_Z5jdHi6B-myT"
short_id = generate(size=10) # "IRFa-VaY2b"
# 自定义字符集
from nanoid import generate
id = generate(alphabet='1234567890abcdef', size=10) # "4f90d13a42"
use Hidehalo\Nanoid\Client;
$client = new Client();
$id = $client->generateId(); // "V1StGXR8_Z5jdHi6B-myT"
$shortId = $client->generateId(10); // "IRFa-VaY2b"
-- 1. 为 API 资源使用 NanoID
CREATE TABLE api_keys (
id TEXT DEFAULT generate_nanoid() PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
name VARCHAR(100),
key_hash TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 为短链接服务使用短 NanoID
CREATE TABLE short_links (
id TEXT DEFAULT generate_nanoid(8) PRIMARY KEY,
original_url TEXT NOT NULL,
clicks INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 3. 为会话使用 NanoID
CREATE TABLE user_sessions (
id TEXT DEFAULT generate_nanoid() PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
ip_address INET,
user_agent TEXT,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 4. 索引优化
CREATE INDEX idx_posts_id_hash ON posts USING hash(id);
CREATE INDEX idx_articles_author_id ON articles(author_id);

选择 NanoID 当:

  • 需要URL友好的ID
  • 希望更短的ID长度
  • 性能要求较高
  • 构建现代Web应用
  • 需要在前端生成ID

选择 UUID 当:

  • 需要数据库原生支持
  • 与老系统集成
  • 需要标准化的解决方案
  • 团队更熟悉UUID
import { pgTable, serial, bigserial, text, timestamp } from 'drizzle-orm/pg-core';
// 使用 serial
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});
// 使用 bigserial
export const logs = pgTable('logs', {
id: bigserial('id', { mode: 'number' }).primaryKey(), // mode: 'number' | 'bigint'
message: text('message'),
createdAt: timestamp('created_at').defaultNow(),
});
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
// 使用 UUID
export const sessions = pgTable('sessions', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
userId: uuid('user_id').notNull(),
token: text('token'),
expiresAt: timestamp('expires_at'),
});
// 或者在应用层生成
export const posts = pgTable('posts', {
id: uuid('id').primaryKey(), // 应用层生成
title: text('title').notNull(),
content: text('content'),
authorId: uuid('author_id').notNull(),
});
import { pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core';
import { nanoid } from 'nanoid';
// 应用层生成 NanoID (推荐方式)
export const articles = pgTable('articles', {
id: text('id').primaryKey().$defaultFn(() => nanoid()),
slug: varchar('slug', { length: 255 }).unique(),
title: text('title').notNull(),
content: text('content'),
createdAt: timestamp('created_at').defaultNow(),
});
// 短 ID 用于短链接
export const shortUrls = pgTable('short_urls', {
id: text('id').primaryKey().$defaultFn(() => nanoid(8)),
originalUrl: text('original_url').notNull(),
clicks: serial('clicks').default(0),
});
// 自定义字符集的 NanoID
import { customAlphabet } from 'nanoid';
const nanoidCustom = customAlphabet('1234567890abcdef', 10);
export const apiKeys = pgTable('api_keys', {
id: text('id').primaryKey().$defaultFn(() => nanoidCustom()),
userId: serial('user_id').notNull(),
name: text('name'),
keyHash: text('key_hash'),
});
CUID2 在 Drizzle 中 (另一个现代选择)
Section titled “CUID2 在 Drizzle 中 (另一个现代选择)”
import { createId } from '@paralleldrive/cuid2';
export const orders = pgTable('orders', {
id: text('id').primaryKey().$defaultFn(() => createId()),
customerId: text('customer_id').notNull(),
total: decimal('total', { precision: 10, scale: 2 }),
status: text('status').default('pending'),
});
import { db } from './db';
import { users, posts } from './schema';
import { eq } from 'drizzle-orm';
// 插入用户 (自动生成 serial ID)
const newUser = await db.insert(users).values({
email: 'user@example.com',
name: 'John Doe',
}).returning();
// 插入文章 (自动生成 NanoID)
const newPost = await db.insert(posts).values({
title: 'My Article',
content: 'Article content...',
authorId: newUser[0].id,
}).returning();
// 查询
const userPosts = await db
.select()
.from(posts)
.where(eq(posts.authorId, newUser[0].id));
-- UUID 在 PostgreSQL 中的存储
-- UUID 类型:16字节二进制存储 (比 TEXT 的 36字节更高效)
CREATE TABLE uuid_test (
id UUID PRIMARY KEY,
data TEXT
);
-- 对比 TEXT 存储
CREATE TABLE text_test (
id TEXT PRIMARY KEY, -- 36字节 + 变长开销
data TEXT
);
-- 查看存储大小
SELECT pg_size_pretty(pg_total_relation_size('uuid_test')) as uuid_size,
pg_size_pretty(pg_total_relation_size('text_test')) as text_size;
-- UUID 的 B-tree 索引性能
CREATE INDEX idx_uuid_btree ON uuid_test(id);
-- Hash 索引更适合等值查询
CREATE INDEX idx_uuid_hash ON uuid_test USING hash(id);
-- 部分索引优化
CREATE INDEX idx_recent_uuid ON uuid_test(id)
WHERE created_at > (CURRENT_DATE - INTERVAL '30 days');
-- 性能测试脚本
DO $$
DECLARE
start_time TIMESTAMP;
end_time TIMESTAMP;
i INTEGER;
BEGIN
-- 测试 gen_random_uuid()
start_time := clock_timestamp();
FOR i IN 1..100000 LOOP
PERFORM gen_random_uuid();
END LOOP;
end_time := clock_timestamp();
RAISE NOTICE 'gen_random_uuid: %', end_time - start_time;
-- 测试 uuid_generate_v4() (需要 uuid-ossp 扩展)
start_time := clock_timestamp();
FOR i IN 1..100000 LOOP
PERFORM uuid_generate_v4();
END LOOP;
end_time := clock_timestamp();
RAISE NOTICE 'uuid_generate_v4: %', end_time - start_time;
END $$;
-- 实际存储空间测试
CREATE TABLE storage_test (
uuid_col UUID,
nanoid_col TEXT,
data TEXT
);
-- 插入测试数据
INSERT INTO storage_test
SELECT
gen_random_uuid(),
substr(md5(random()::text), 1, 21), -- 模拟 21字符 nanoid
'test data'
FROM generate_series(1, 1000000);
-- 查看存储空间
SELECT
pg_size_pretty(pg_column_size(uuid_col)) as uuid_size,
pg_size_pretty(pg_column_size(nanoid_col)) as nanoid_size,
pg_size_pretty(pg_total_relation_size('storage_test')) as total_size
FROM storage_test LIMIT 1;
-- 创建索引
CREATE INDEX idx_uuid ON storage_test(uuid_col);
CREATE INDEX idx_nanoid ON storage_test(nanoid_col);
-- 查看索引大小
SELECT
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) as index_size
FROM pg_indexes
WHERE tablename = 'storage_test';
-- 查询性能测试
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM storage_test WHERE uuid_col = gen_random_uuid();
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM storage_test WHERE nanoid_col = 'some_nanoid_value_here';
特性UUIDNanoID (TEXT)赢家
存储空间16字节21字节 + 开销 (~25字节)🏆 UUID
索引大小更小更大🏆 UUID
B-tree 查询优化的二进制比较字符串比较🏆 UUID
Hash 索引原生支持文本hash🏆 UUID
生成性能数据库原生应用层生成🏆 UUID
网络传输36字符21字符🏆 NanoID
URL友好包含-字符完全友好🏆 NanoID
人类可读较差更好🏆 NanoID
// 高性能数据库密集型应用
export const highVolumeTable = pgTable('transactions', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
amount: decimal('amount', { precision: 15, scale: 2 }),
userId: uuid('user_id').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
// 需要数据库层面的引用完整性
export const users = pgTable('users', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
email: text('email').unique(),
});
export const userProfiles = pgTable('user_profiles', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
userId: uuid('user_id').notNull().references(() => users.id),
bio: text('bio'),
});
// API 资源,需要URL友好
export const blogPosts = pgTable('blog_posts', {
id: text('id').primaryKey().$defaultFn(() => nanoid()),
slug: text('slug').unique(),
title: text('title').notNull(),
});
// 短链接服务
export const shortLinks = pgTable('short_links', {
id: text('id').primaryKey().$defaultFn(() => nanoid(8)),
url: text('url').notNull(),
});
// 需要在前端生成ID的场景
export const clientGenerated = pgTable('uploads', {
id: text('id').primaryKey(), // 前端生成 nanoid
filename: text('filename'),
uploadedAt: timestamp('uploaded_at').defaultNow(),
});
// 内部关系使用 UUID (性能优先)
export const users = pgTable('users', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
email: text('email').unique(),
publicId: text('public_id').$defaultFn(() => nanoid()), // 对外暴露的ID
});
export const posts = pgTable('posts', {
id: uuid('id').default(sql`gen_random_uuid()`).primaryKey(),
publicId: text('public_id').$defaultFn(() => nanoid()),
authorId: uuid('author_id').notNull().references(() => users.id),
title: text('title'),
});
// API 路由使用 publicId,数据库查询使用内部 UUID
// GET /api/posts/V1StGXR8_Z5jdHi6B-myT
-- 创建带 OID 的表 (不推荐,已废弃)
CREATE TABLE old_table (
name VARCHAR(100)
) WITH OIDS;
-- 查询 OID
SELECT oid, * FROM old_table;
-- ctid: 物理位置标识符
SELECT ctid, * FROM users;
-- xmin, xmax: 事务ID
SELECT xmin, xmax, * FROM users;
-- tableoid: 表的OID
SELECT tableoid, * FROM users;
-- 创建序列
CREATE SEQUENCE custom_id_seq
START WITH 1000
INCREMENT BY 1
MINVALUE 1000
MAXVALUE 999999999
CACHE 1;
-- 使用序列
CREATE TABLE custom_table (
id INTEGER DEFAULT nextval('custom_id_seq') PRIMARY KEY,
data TEXT
);
-- 序列操作
SELECT nextval('custom_id_seq'); -- 获取下一个值
SELECT currval('custom_id_seq'); -- 获取当前值
SELECT setval('custom_id_seq', 5000); -- 设置序列值
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
quantity INTEGER,
price DECIMAL(10,2),
PRIMARY KEY (order_id, product_id)
);
-- 小表使用 SMALLSERIAL 或 SMALLINT IDENTITY
CREATE TABLE status (
id SMALLINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(20)
);
-- 中等表使用 INTEGER IDENTITY
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 大表或需要全局唯一使用 BIGINT IDENTITY 或 UUID
CREATE TABLE events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- 或
-- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
event_type VARCHAR(50),
data JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 为外键创建索引
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- UUID 索引优化
CREATE INDEX idx_sessions_id_hash ON sessions USING hash(id);
-- 查看所有序列
SELECT * FROM information_schema.sequences;
-- 查看特定序列的详细信息
SELECT * FROM pg_sequences WHERE schemaname = 'public';
-- 查看序列当前值
SELECT sequence_name, last_value, increment_by
FROM information_schema.sequences s
JOIN pg_sequences ps ON s.sequence_name = ps.sequencename;
-- 查看表的IDENTITY列
SELECT table_name, column_name, is_identity, identity_generation
FROM information_schema.columns
WHERE is_identity = 'YES'
AND table_schema = 'public';
-- 查看表的默认值
SELECT table_name, column_name, column_default
FROM information_schema.columns
WHERE column_default IS NOT NULL
AND table_schema = 'public'
ORDER BY table_name, ordinal_position;
t1