postgres-drizzle

postgres-drizzle

在创建API、后端或数据模型时主动应用。触发条件包括PostgreSQL、Postgres、Drizzle、drizzle-orm、drizzle-kit、数据库、模式、pgTable、表、列、索引、查询、迁移、ORM、关系、关系查询、连接、事务、SQL、连接池、PgBouncer、N+1、JSONB、RLS、全文搜索、分区。在编写数据库模式、查询、迁移、连接设置或任何与数据库相关的代码时使用。PostgreSQL和Drizzle ORM最佳实践。

57Star
3Fork
更新于 2026/8/27
SKILL.md
只读
名称
postgres-drizzle
描述

在创建API、后端或数据模型时主动应用。触发条件包括PostgreSQL、Postgres、Drizzle、drizzle-orm、drizzle-kit、数据库、模式、pgTable、表、列、索引、查询、迁移、ORM、关系、关系查询、连接、事务、SQL、连接池、PgBouncer、N+1、JSONB、RLS、全文搜索、分区。在编写数据库模式、查询、迁移、连接设置或任何与数据库相关的代码时使用。PostgreSQL和Drizzle ORM最佳实践。

PostgreSQL + Drizzle ORM

使用PostgreSQL 17/18和Drizzle ORM构建类型安全的数据库应用程序。

版本检查(首先执行)

Drizzle的API在稳定的0.x系列和v1.0之间发生了重大变化。在编写代码之前检查package.json,因为两种关系API不兼容,不能混用:

drizzle-orm版本 关系API 查询过滤器
^0.x(npm latest 每个表使用relations()drizzle(client, { schema }) where: eq(users.id, id)
1.0.0-beta.* / 1.0.0-rc.* 一次性为所有表使用defineRelations()drizzle(client, { relations }) where: { id: userId }(对象风格)

官方文档站点(orm.drizzle.team)在其主要页面上记录了v1.0语法。本技能默认使用稳定的0.x语法;对于v1.0项目,请阅读references/RELATIONS.md中的“关系查询v2”部分。
表明项目使用v1.0的信号:defineRelations导入、对象风格的where、关系中的r.many.posts()、使用from/to键而不是fields/references

基本命令

npx drizzle-kit generate   # 从模式更改生成SQL迁移
npx drizzle-kit migrate    # 应用待处理的迁移
npx drizzle-kit push       # 直接推送模式(仅限开发/原型设计)
npx drizzle-kit pull       # 将现有数据库反向工程为模式文件
npx drizzle-kit studio     # 打开数据库浏览器
npx drizzle-kit check      # 检测迁移冲突(竞争条件)

快速决策树

“如何建模这种关系?”

关系类型?
├─ 一对多(用户有帖子)     → 在“多”侧添加外键 + relations()
├─ 多对多(帖子有标签)     → 连接表,使用复合主键 + relations()
├─ 一对一(用户有个人资料) → 外键加唯一约束
└─ 自引用(评论)           → 指向同一表的外键(将引用类型设为AnyPgColumn)

“为什么我的查询慢?”

查询慢?
├─ WHERE/JOIN列缺少索引  → 添加索引(Postgres不会自动为外键创建索引)
├─ 循环中逐行查询(N+1)        → 使用关系查询(`with:`)或连接
├─ 全表扫描                      → EXPLAIN (ANALYZE, BUFFERS),添加索引
├─ 大OFFSET分页              → 切换到游标/键集分页
└─ 每个请求的连接开销      → 连接池(pg Pool / postgres.js / PgBouncer)

“使用哪个drizzle-kit命令?”

我需要什么?
├─ 模式已更改,需要版本化SQL   → drizzle-kit generate,审查SQL,然后migrate
├─ 应用迁移(CI、生产)          → drizzle-kit migrate(或在代码中使用migrate())
├─ 快速本地迭代,临时数据库  → drizzle-kit push
├─ 在现有数据库上采用Drizzle      → drizzle-kit pull
└─ 手写SQL(触发器、回填)→ drizzle-kit generate --custom

连接设置

// node-postgres — 传入URL,Drizzle会为你创建Pool
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema';

export const db = drizzle(process.env.DATABASE_URL!, { schema });
// postgres.js — 内置连接池;在事务模式连接池(PgBouncer/Supavisor)后面设置prepare: false,除非它支持预处理语句
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

const client = postgres(process.env.DATABASE_URL!, { max: 20 });
export const db = drizzle(client, { schema });

传入schema是启用db.query.*关系查询的关键——忘记传入是“类型上不存在属性'users'”的最常见原因。

可选:drizzle(url, { schema, casing: 'snake_case' })将camelCase的TS键映射到snake_case列,这样你可以编写pgTable('users', { createdAt: timestamp() })而无需重复列名。在drizzle.config.ts中设置相同的casing

模式模式

带时间戳的基本表

import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
  updatedAt: timestamp('updated_at', { withTimezone: true })
    .defaultNow()
    .notNull()
    .$onUpdate(() => new Date()),
});

优先使用timestamp(..., { withTimezone: true })(timestamptz)——无时区的时间戳会导致静默时区错误。对于整数主键,优先使用integer().primaryKey().generatedAlwaysAsIdentity()而不是serial()(PostgreSQL推荐的形式;serial是遗留的)。

带索引的外键

import { index } from 'drizzle-orm/pg-core';

export const posts = pgTable('posts', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
  title: varchar('title', { length: 255 }).notNull(),
}, (table) => [
  // Postgres不会为外键列创建索引——添加一个,否则JOIN或级联操作会扫描全表
  index('posts_user_id_idx').on(table.userId),
]);

第三个pgTable参数返回一个数组(旧的对象形式已弃用)。

关系(稳定的0.x API)

import { relations } from 'drizzle-orm';

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.userId], references: [users.id] }),
}));

relations()db.query.*的应用程序级元数据——它不会创建外键约束。两者都要定义(.references()用于数据库,relations()用于查询)。

查询模式

import { eq } from 'drizzle-orm';

// 关系查询——一次往返获取嵌套数据,无N+1
const usersWithPosts = await db.query.users.findMany({
  with: { posts: true },
});

// SQL风格查询——过滤器、连接、聚合
const activeUsers = await db
  .select()
  .from(users)
  .where(eq(users.status, 'active'));

// 事务——所有语句一起提交或回滚
await db.transaction(async (tx) => {
  const [user] = await tx.insert(users).values({ email }).returning();
  await tx.insert(profiles).values({ userId: user.id });
});

在事务内部,始终使用tx,而不是db——在db上的查询会逃出事务,不会回滚。

性能检查清单

优先级 检查项 影响
严重 索引所有外键 防止JOIN和级联删除的全表扫描
严重 对嵌套数据使用关系查询或连接 避免N+1
生产环境使用连接池 每个PG连接消耗约MB内存
对慢查询使用EXPLAIN (ANALYZE, BUFFERS) 识别缺失的索引
对过滤子集使用部分索引 更小、更快的索引
使用UUIDv7(uuidv7(),PG18+)或身份列作为主键 比UUIDv4更好的索引局部性

反模式

反模式 问题 修复
外键无索引 JOIN和级联操作慢 在每个外键列上添加索引
循环中的N+1 每行查询 with:关系查询或连接
每个请求一个连接 连接风暴,内存耗尽 pg Pool / postgres.js max / PgBouncer
生产环境使用push 无历史记录,数据丢失提示 generate + migrate
混用0.x relations()和v1.0 defineRelations 类型错误,db.query损坏 每个项目选择一种(参见版本检查)
将JSON存储为text 无验证,无索引 jsonb()列 + GIN索引
无时区的timestamp 静默时区错误 { withTimezone: true }
编辑已应用的迁移文件 校验和不匹配,漂移 新迁移(generate / generate --custom

参考文档

阅读此文档 当你...
references/SCHEMA.md 定义表:列类型、约束、索引、枚举、生成列
references/QUERIES.md 编写select、insert、upsert、事务、预处理语句
references/RELATIONS.md 建模关系或使用db.query.*——包括v1.0 RQB v2 API
references/MIGRATIONS.md 配置drizzle-kit,生成/应用迁移,自定义SQL
references/POSTGRES.md 使用PG17/18特性,RLS,分区,JSONB操作,全文搜索
references/PERFORMANCE.md 索引策略,EXPLAIN,连接池,分页,批量操作
references/CHEATSHEET.md 需要上述任何内容的紧凑语法提醒

资源