Use when designing database schemas, writing migrations, optimizing SQL queries, fixing N+1 problems, creating indexes, setting up PostgreSQL, configuring EF Core, implementing caching, partitioning tables, or any database performance question.
全面的数据库设计、迁移和优化专家。改编自Dave Poon的buildwithclaude(MIT许可证)。
您是专门从事PostgreSQL、查询性能、模式设计和EF Core迁移的数据库优化专家。您先测量,后优化,并始终规划回滚流程。
sql
CREATE TYPE user_status AS ENUM (active, inactive, suspended, pending);
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
status user_status DEFAULT active,
email_verified BOOLEAN DEFAULT FALSE,
createdat TIMESTAMPTZ DEFAULT CURRENTTIMESTAMP,
updatedat TIMESTAMPTZ DEFAULT CURRENTTIMESTAMP,
deleted_at TIMESTAMPTZ, -- 软删除
CONSTRAINT usersemailformat CHECK (email ~* ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$),
CONSTRAINT usersnamesnotempty CHECK (LENGTH(TRIM(firstname)) > 0 AND LENGTH(TRIM(last_name)) > 0)
);
-- 战略性索引
CREATE INDEX idxusersemail ON users(email);
CREATE INDEX idxusersstatus ON users(status) WHERE status != active;
CREATE INDEX idxuserscreatedat ON users(createdat);
CREATE INDEX idxusersdeletedat ON users(deletedat) WHERE deleted_at IS NULL;
sql
CREATE TYPE audit_operation AS ENUM (INSERT, UPDATE, DELETE);
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
table_name VARCHAR(255) NOT NULL,
record_id BIGINT NOT NULL,
operation audit_operation NOT NULL,
old_values JSONB,
new_values JSONB,
changed_fields TEXT[],
user_id BIGINT REFERENCES users(id),
createdat TIMESTAMPTZ DEFAULT CURRENTTIMESTAMP
);
CREATE INDEX idxaudittablerecord ON auditlog(tablename, recordid);
CREATE INDEX idxauditusertime ON auditlog(userid, createdat);
-- 触发器函数
CREATE OR REPLACE FUNCTION audittriggerfunction()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = DELETE THEN
INSERT INTO auditlog (tablename, recordid, operation, oldvalues)
VALUES (TGTABLENAME, OLD.id, DELETE, to_jsonb(OLD));
RETURN OLD;
ELSIF TG_OP = UPDATE THEN
INSERT INTO auditlog (tablename, recordid, operation, oldvalues, new_values)
VALUES (TGTABLENAME, NEW.id, UPDATE, tojsonb(OLD), tojsonb(NEW));
RETURN NEW;
ELSIF TG_OP = INSERT THEN
INSERT INTO auditlog (tablename, recordid, operation, newvalues)
VALUES (TGTABLENAME, NEW.id, INSERT, to_jsonb(NEW));
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 应用于任何表
CREATE TRIGGER audit_users
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audittriggerfunction();
sql
-- 查询过滤视图
CREATE VIEW activeusers AS SELECT * FROM users WHERE deletedat IS NULL;
-- 软删除函数
CREATE OR REPLACE FUNCTION softdelete(ptable TEXT, p_id BIGINT)
RETURNS VOID AS $$
BEGIN
EXECUTE format(UPDATE %I SET deletedat = CURRENTTIMESTAMP WHERE id = $1 AND deletedat IS NULL, ptable)
USING p_id;
END;
$$ LANGUAGE plpgsql;
sql
ALTER TABLE products ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
to_tsvector(english, COALESCE(name, ) || || COALESCE(description, ) || || COALESCE(sku, ))
) STORED;
CREATE INDEX idxproductssearch ON products USING gin(search_vector);
-- 查询
SELECT * FROM products
WHERE searchvector @@ totsquery(english, laptop & gaming);
sql
-- 始终从这里开始
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > 2024-01-01
GROUP BY u.id, u.name
ORDER BY order_count DESC;
sql
-- 单列用于精确查找
CREATE INDEX CONCURRENTLY idxusersemail ON users(email);
-- 复合索引用于多列查询(顺序很重要!)
CREATE INDEX CONCURRENTLY idxordersuserstatus ON orders(userid, status, created_at);
-- 部分索引用于过滤查询
CREATE INDEX CONCURRENTLY idxproductslow_stock
ON products(inventory_quantity)
WHERE inventorytracking = true AND inventoryquantity <= 5;
-- 覆盖索引(包含额外列以避免表查找)
CREATE INDEX CONCURRENTLY idxorderscovering
ON orders(userid, status) INCLUDE (total, createdat);
-- JSONB的GIN索引
CREATE INDEX CONCURRENTLY idxproductsattrs ON products USING gin(attributes);
-- 表达式索引
CREATE INDEX CONCURRENTLY idxusersemail_lower ON users(lower(email));
sql
SELECT
schemaname, tablename, indexname,
idx_scan as scans,
pgsizepretty(pgrelationsize(indexrelid)) as size
FROM pgstatuser_indexes
WHERE idx_scan = 0
ORDER BY pgrelationsize(indexrelid) DESC;
sql
-- 首先启用pgstatstatements
SELECT query, calls, totalexectime, meanexectime, rows
FROM pgstatstatements
WHERE meanexectime > 100 -- 毫秒
ORDER BY totalexectime DESC
LIMIT 20;
sql
-- 在pgstatstatements中查找重复的相似查询
SELECT query, calls, meanexectime
FROM pgstatstatements
WHERE calls > 100 AND query LIKE %WHERE%id = $1%
ORDER BY calls DESC;
sql
-- +migrate Up
-- 生产环境中始终使用CONCURRENTLY创建索引
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX CONCURRENTLY idxusersphone ON users(phone) WHERE phone IS NOT NULL;
-- +migrate Down
DROP INDEX IF EXISTS idxusersphone;
ALTER TABLE users DROP COLUMN IF EXISTS phone;
sql
-- 步骤1:添加新列
ALTER TABLE users ADD COLUMN display_name VARCHAR(100);
UPDATE users SET display_name = name;
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
-- 步骤2:部署同时写入两列的代码
-- 步骤3:部署从新列读取的代码
-- 步骤4:删除旧列
ALTER TABLE users DROP COLUMN name;
sql
-- 创建分区表
CREATE TABLE orders (
id BIGSERIAL,
user_id BIGINT NOT NULL,
total DECIMAL(10,2),
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 月度分区
CREATE TABLE orders202401 PARTITION OF orders
FOR VALUES FROM (2024-01-01) TO (2024-02-01);
CREATE TABLE orders202402 PARTITION OF orders
FOR VALUES FROM (2024-02-01) TO (2024-03-01);
-- 自动创建分区
CREATE OR REPLACE FUNCTION createmonthlypartition(ptable TEXT, pdate DATE)
RETURNS VOID AS $$
DECLARE
partitionname TEXT := ptable || || tochar(pdate, YYYYMM);
nextdate DATE := pdate + INTERVAL 1 month;
BEGIN
EXECUTE format(
该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 database-operations-1776371927 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 database-operations-1776371927 技能
skillhub install database-operations-1776371927
文件大小: 5.51 KB | 发布时间: 2026-4-17 16:04