drizzle
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDrizzle Core Knowledge
Drizzle核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.drizzle
深度知识:如需完整文档,请调用并指定技术为mcp__documentation__fetch_docs。drizzle
When NOT to Use This Skill
本技能不适用场景
- Existing Prisma Projects: Use skill for Prisma-based codebases
prisma - TypeORM Projects: Use skill for TypeORM-based applications
typeorm - Raw SQL Execution: Use MCP server for direct SQL queries
database-query - NoSQL Databases: Use skill for MongoDB operations
mongodb - Complex ORM Features: Drizzle is lightweight; consider Prisma/TypeORM for advanced features
- Database Architecture: Consult or
sql-expertfor schema designarchitect-expert
- 现有Prisma项目:基于Prisma的代码库请使用技能
prisma - TypeORM项目:基于TypeORM的应用请使用技能
typeorm - 原生SQL执行:直接运行SQL查询请使用MCP服务
database-query - NoSQL数据库:MongoDB操作请使用技能
mongodb - 复杂ORM特性需求:Drizzle是轻量级ORM,如需高级特性请考虑Prisma/TypeORM
- 数据库架构设计:模式设计请咨询或
sql-expertarchitect-expert
Schema Definition
模式定义
typescript
// schema.ts
import { pgTable, serial, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 100 }),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: varchar('content'),
published: boolean('published').default(false),
authorId: integer('author_id').references(() => users.id),
});typescript
// schema.ts
import { pgTable, serial, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 100 }),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: varchar('content'),
published: boolean('published').default(false),
authorId: integer('author_id').references(() => users.id),
});CRUD Operations
CRUD操作
typescript
import { eq, and, like, desc } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';
// Create
const [user] = await db.insert(users)
.values({ email: 'user@example.com', name: 'John' })
.returning();
// Read
const allUsers = await db.select().from(users)
.where(like(users.email, '%@example.com'))
.orderBy(desc(users.createdAt))
.limit(10);
const user = await db.select().from(users)
.where(eq(users.id, 1))
.limit(1);
// Update
await db.update(users)
.set({ name: 'Jane' })
.where(eq(users.id, 1));
// Delete
await db.delete(users).where(eq(users.id, 1));typescript
import { eq, and, like, desc } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';
// Create
const [user] = await db.insert(users)
.values({ email: 'user@example.com', name: 'John' })
.returning();
// Read
const allUsers = await db.select().from(users)
.where(like(users.email, '%@example.com'))
.orderBy(desc(users.createdAt))
.limit(10);
const user = await db.select().from(users)
.where(eq(users.id, 1))
.limit(1);
// Update
await db.update(users)
.set({ name: 'Jane' })
.where(eq(users.id, 1));
// Delete
await db.delete(users).where(eq(users.id, 1));Joins
联表查询
typescript
const usersWithPosts = await db
.select({
userId: users.id,
userName: users.name,
postTitle: posts.title,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(posts.published, true));typescript
const usersWithPosts = await db
.select({
userId: users.id,
userName: users.name,
postTitle: posts.title,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(posts.published, true));Migrations
数据迁移
bash
npx drizzle-kit generate
npx drizzle-kit migrate
npx drizzle-kit studiobash
npx drizzle-kit generate
npx drizzle-kit migrate
npx drizzle-kit studioAnti-Patterns
反模式
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Not using connection pooling | Connection exhaustion, poor performance | Use |
Selecting all columns with | Unnecessary data transfer | Specify only needed columns in select object |
| Manual SQL string concatenation | SQL injection risk, type unsafety | Use Drizzle query builder with parameterization |
| No transaction for related operations | Data inconsistency | Use |
| Missing indexes on filter columns | Slow queries | Add |
| Not reusing prepared statements | Slower execution, resource waste | Use |
| Hardcoded connection strings | Security risk | Use environment variables |
| No error handling on queries | Poor UX, silent failures | Wrap queries in try-catch |
Using | No migration history, risky | Use |
| Not defining foreign key constraints | Data integrity issues | Use |
| 反模式 | 弊端 | 优化方案 |
|---|---|---|
| 不使用连接池 | 连接耗尽、性能不佳 | 使用带合理限制的 |
使用 | 不必要的数据传输 | 在select对象中仅指定需要的列 |
| 手动拼接SQL字符串 | 存在SQL注入风险、无类型安全保障 | 使用Drizzle查询构建器的参数化查询能力 |
| 关联操作不使用事务 | 数据不一致 | 使用 |
| 过滤列未加索引 | 查询速度慢 | 给高频查询的列添加 |
| 不复用预处理语句 | 执行速度慢、资源浪费 | 重复执行的查询使用 |
| 硬编码连接字符串 | 存在安全风险 | 使用环境变量存储敏感信息 |
| 查询未做错误处理 | 用户体验差、错误静默失败 | 用try-catch包裹查询逻辑 |
生产环境使用 | 无迁移历史、风险高 | 使用 |
| 未定义外键约束 | 数据完整性问题 | 在模式中使用 |
Quick Troubleshooting
快速排障
| Issue | Likely Cause | Solution |
|---|---|---|
| "relation does not exist" | Schema not migrated or wrong DB | Run |
| "column does not exist" | Schema out of sync with code | Regenerate and apply migrations |
| Type errors on queries | Schema types not matching DB | Run |
| Slow queries | Missing indexes, N+1 queries | Add indexes, use joins instead of separate queries |
| Connection timeouts | Pool exhausted or network issues | Check pool size, increase timeout limits |
| "Cannot find module 'drizzle-orm'" | Missing dependency | Run |
| Migration conflicts | Multiple devs generating migrations | Coordinate migration naming, merge carefully |
| "ECONNREFUSED" | Database not running or wrong URL | Verify DATABASE_URL, start database |
| Foreign key violations | Inserting with invalid references | Ensure referenced records exist first |
| Duplicate key errors | Unique constraint violation | Check for existing record before insert |
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| "relation does not exist" | 模式未迁移或连接了错误的数据库 | 运行 |
| "column does not exist" | 模式与代码不同步 | 重新生成并应用迁移 |
| 查询出现类型错误 | 模式类型与数据库不匹配 | 运行 |
| 查询速度慢 | 缺少索引、存在N+1查询 | 添加索引,用联表代替多次独立查询 |
| 连接超时 | 连接池耗尽或网络问题 | 检查连接池大小,调高超时限制 |
| "Cannot find module 'drizzle-orm'" | 缺少依赖 | 运行 |
| 迁移冲突 | 多个开发者同时生成迁移 | 统一迁移命名规范,谨慎合并迁移文件 |
| "ECONNREFUSED" | 数据库未启动或URL错误 | 校验DATABASE_URL,启动数据库服务 |
| 外键违规 | 插入了无效的关联引用 | 先确保被关联的记录已存在 |
| 重复键错误 | 违反唯一约束 | 插入前检查是否已存在对应记录 |
Production Readiness
生产就绪指南
Database Connection
数据库连接
typescript
// db.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
// SECURITY: Use proper CA certificate in production instead of disabling verification
// ssl: { rejectUnauthorized: false } is INSECURE - vulnerable to MITM attacks
ssl: process.env.NODE_ENV === 'production'
? { ca: process.env.DB_CA_CERT }
: false,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
pool.on('error', (err) => {
console.error('Unexpected pool error:', err);
});
export const db = drizzle(pool, { schema });
// Health check
export async function healthCheck() {
const client = await pool.connect();
try {
await client.query('SELECT 1');
return { status: 'healthy' };
} finally {
client.release();
}
}
// Graceful shutdown
export async function closePool() {
await pool.end();
}typescript
// db.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
// SECURITY: Use proper CA certificate in production instead of disabling verification
// ssl: { rejectUnauthorized: false } is INSECURE - vulnerable to MITM attacks
ssl: process.env.NODE_ENV === 'production'
? { ca: process.env.DB_CA_CERT }
: false,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
pool.on('error', (err) => {
console.error('Unexpected pool error:', err);
});
export const db = drizzle(pool, { schema });
// Health check
export async function healthCheck() {
const client = await pool.connect();
try {
await client.query('SELECT 1');
return { status: 'healthy' };
} finally {
client.release();
}
}
// Graceful shutdown
export async function closePool() {
await pool.end();
}Transaction Handling
事务处理
typescript
import { db } from './db';
async function transferFunds(fromId: string, toId: string, amount: number) {
return await db.transaction(async (tx) => {
const [from] = await tx
.select()
.from(accounts)
.where(eq(accounts.id, fromId))
.for('update');
if (!from || from.balance < amount) {
throw new Error('Insufficient funds');
}
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} - ${amount}` })
.where(eq(accounts.id, fromId));
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} + ${amount}` })
.where(eq(accounts.id, toId));
return { success: true };
});
}typescript
import { db } from './db';
async function transferFunds(fromId: string, toId: string, amount: number) {
return await db.transaction(async (tx) => {
const [from] = await tx
.select()
.from(accounts)
.where(eq(accounts.id, fromId))
.for('update');
if (!from || from.balance < amount) {
throw new Error('Insufficient funds');
}
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} - ${amount}` })
.where(eq(accounts.id, fromId));
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} + ${amount}` })
.where(eq(accounts.id, toId));
return { success: true };
});
}Query Optimization
查询优化
typescript
// Pagination with cursor
async function getUsers(cursor?: string, limit = 20) {
const query = db.select().from(users);
if (cursor) {
query.where(gt(users.id, cursor));
}
const results = await query
.orderBy(asc(users.id))
.limit(limit + 1);
const hasMore = results.length > limit;
const data = hasMore ? results.slice(0, -1) : results;
return {
data,
nextCursor: hasMore ? data[data.length - 1].id : null,
};
}
// Batch inserts
async function bulkInsertUsers(usersData: NewUser[]) {
const batchSize = 100;
for (let i = 0; i < usersData.length; i += batchSize) {
const batch = usersData.slice(i, i + batchSize);
await db.insert(users).values(batch);
}
}
// Select only needed columns
const userNames = await db
.select({ id: users.id, name: users.name })
.from(users)
.where(eq(users.isActive, true));typescript
// Pagination with cursor
async function getUsers(cursor?: string, limit = 20) {
const query = db.select().from(users);
if (cursor) {
query.where(gt(users.id, cursor));
}
const results = await query
.orderBy(asc(users.id))
.limit(limit + 1);
const hasMore = results.length > limit;
const data = hasMore ? results.slice(0, -1) : results;
return {
data,
nextCursor: hasMore ? data[data.length - 1].id : null,
};
}
// Batch inserts
async function bulkInsertUsers(usersData: NewUser[]) {
const batchSize = 100;
for (let i = 0; i < usersData.length; i += batchSize) {
const batch = usersData.slice(i, i + batchSize);
await db.insert(users).values(batch);
}
}
// Select only needed columns
const userNames = await db
.select({ id: users.id, name: users.name })
.from(users)
.where(eq(users.isActive, true));Migration Strategy
迁移策略
typescript
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
strict: true,
verbose: true,
} satisfies Config;
// package.json scripts
// "db:generate": "drizzle-kit generate:pg",
// "db:migrate": "drizzle-kit migrate",
// "db:push": "drizzle-kit push:pg", // Dev only
// "db:studio": "drizzle-kit studio"typescript
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
strict: true,
verbose: true,
} satisfies Config;
// package.json scripts
// "db:generate": "drizzle-kit generate:pg",
// "db:migrate": "drizzle-kit migrate",
// "db:push": "drizzle-kit push:pg", // Dev only
// "db:studio": "drizzle-kit studio"Type-Safe Prepared Statements
类型安全预处理语句
typescript
import { sql } from 'drizzle-orm';
const getUserById = db.query.users
.findFirst({
where: eq(users.id, sql.placeholder('id')),
with: { posts: true },
})
.prepare('get_user_by_id');
// Usage (reuses execution plan)
const user = await getUserById.execute({ id: userId });typescript
import { sql } from 'drizzle-orm';
const getUserById = db.query.users
.findFirst({
where: eq(users.id, sql.placeholder('id')),
with: { posts: true },
})
.prepare('get_user_by_id');
// Usage (reuses execution plan)
const user = await getUserById.execute({ id: userId });Testing
测试
typescript
// tests/db.test.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
describe('Database', () => {
let pool: Pool;
let testDb: ReturnType<typeof drizzle>;
beforeAll(async () => {
pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
testDb = drizzle(pool);
await migrate(testDb, { migrationsFolder: './migrations' });
});
afterAll(async () => {
await pool.end();
});
beforeEach(async () => {
await testDb.delete(users);
});
it('should create user', async () => {
const [user] = await testDb
.insert(users)
.values({ email: 'test@example.com', name: 'Test' })
.returning();
expect(user.email).toBe('test@example.com');
});
});typescript
// tests/db.test.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
describe('Database', () => {
let pool: Pool;
let testDb: ReturnType<typeof drizzle>;
beforeAll(async () => {
pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
testDb = drizzle(pool);
await migrate(testDb, { migrationsFolder: './migrations' });
});
afterAll(async () => {
await pool.end();
});
beforeEach(async () => {
await testDb.delete(users);
});
it('should create user', async () => {
const [user] = await testDb
.insert(users)
.values({ email: 'test@example.com', name: 'Test' })
.returning();
expect(user.email).toBe('test@example.com');
});
});Monitoring Metrics
监控指标
| Metric | Target |
|---|---|
| Query time (p99) | < 100ms |
| Connection pool usage | < 80% |
| Migration success | 100% |
| Transaction rollbacks | < 0.1% |
| 指标 | 目标值 |
|---|---|
| 查询耗时(p99) | < 100ms |
| 连接池使用率 | < 80% |
| 迁移成功率 | 100% |
| 事务回滚率 | < 0.1% |
Checklist
检查清单
- Connection pooling configured
- SSL in production
- Transactions for multi-step operations
- Cursor-based pagination
- Batch operations for bulk data
- Prepared statements for repeated queries
- Migration versioning
- Test database isolation
- Query logging in development
- Health check endpoint
- 已配置连接池
- 生产环境已启用SSL
- 多步操作已使用事务
- 已实现游标分页
- 批量数据操作已使用批处理
- 重复查询已使用预处理语句
- 已做迁移版本管理
- 测试数据库已隔离
- 开发环境已开启查询日志
- 已实现健康检查接口
Reference Documentation
参考文档
- Schema
- Queries
- Schema
- Queries