AgentSkillsCN

Database Anti-Patterns

数据库反模式

SKILL.md

Database Anti-Patterns

Detection and fix patterns for common database performance issues.

Template Usage: Customize the code patterns and grep commands for your ORM (Prisma, Drizzle, TypeORM, Sequelize, etc.).

N+1 Query Detection

What is N+1?

typescript
// BAD: N+1 Query - 1 query for users + N queries for posts
const users = await db.user.findMany(); // 1 query

for (const user of users) {
  // N queries - one per user!
  user.posts = await db.post.findMany({
    where: { userId: user.id }
  });
}

Detection Patterns (grep/search)

bash
# Pattern 1: Query inside forEach/map
grep -rn "\.forEach.*await.*\.(find|select|query)" src/
grep -rn "\.map.*await.*\.(find|select|query)" src/

# Pattern 2: Query inside for loop
grep -rn "for.*of.*\n.*await.*\.(find|select|query)" src/

# Pattern 3: Query inside loop with common ORMs
# Prisma
grep -rn "for.*\n.*await.*prisma\." src/
grep -rn "\.forEach.*await.*prisma\." src/

# Drizzle
grep -rn "for.*\n.*await.*db\.(select|query)" src/

# TypeORM
grep -rn "for.*\n.*await.*\.find\(.*where" src/

Fix Patterns

typescript
// GOOD: Batch query + in-memory join
const users = await db.user.findMany();
const userIds = users.map(u => u.id);

// Single query for all posts
const posts = await db.post.findMany({
  where: { userId: { in: userIds } }
});

// Join in memory
const postsByUser = new Map<string, Post[]>();
for (const post of posts) {
  const userPosts = postsByUser.get(post.userId) || [];
  userPosts.push(post);
  postsByUser.set(post.userId, userPosts);
}

const usersWithPosts = users.map(user => ({
  ...user,
  posts: postsByUser.get(user.id) || [],
}));
typescript
// ALSO GOOD: Use ORM includes/joins
// Prisma
const users = await db.user.findMany({
  include: { posts: true }
});

// Drizzle
const users = await db.query.users.findMany({
  with: { posts: true }
});

// TypeORM
const users = await userRepo.find({
  relations: ['posts']
});

Sequential Queries Detection

What is it?

typescript
// BAD: Sequential queries that could be parallel
const user = await db.user.findUnique({ where: { id } });
const posts = await db.post.findMany({ where: { userId: id } });
const comments = await db.comment.findMany({ where: { userId: id } });
// Total time: user + posts + comments

Detection Patterns

bash
# Multiple awaits in sequence (same function)
grep -rn "await.*\n.*await.*\n.*await" src/

# Look for patterns like:
# const a = await ...
# const b = await ...
# const c = await ...

Fix Pattern

typescript
// GOOD: Parallel queries with Promise.all
const [user, posts, comments] = await Promise.all([
  db.user.findUnique({ where: { id } }),
  db.post.findMany({ where: { userId: id } }),
  db.comment.findMany({ where: { userId: id } }),
]);
// Total time: max(user, posts, comments)

Unbounded Fetches Detection

What is it?

typescript
// BAD: No limit - could return millions of rows
const allUsers = await db.user.findMany();
const allPosts = await db.post.findMany({
  where: { status: 'published' }
});

Detection Patterns

bash
# findMany without take/limit
grep -rn "findMany\(\s*\)" src/
grep -rn "findMany\(\s*{[^}]*}\s*\)" src/ | grep -v "take:"

# SELECT without LIMIT
grep -rn "SELECT.*FROM" src/ | grep -v -i "limit"

Fix Pattern

typescript
// GOOD: Always use pagination
const PAGE_SIZE = 50;

const users = await db.user.findMany({
  take: PAGE_SIZE,
  skip: page * PAGE_SIZE,
  orderBy: { createdAt: 'desc' },
});

// For processing all records, use cursor pagination
async function* getAllUsers() {
  let cursor: string | undefined;

  while (true) {
    const batch = await db.user.findMany({
      take: 100,
      skip: cursor ? 1 : 0,
      cursor: cursor ? { id: cursor } : undefined,
      orderBy: { id: 'asc' },
    });

    if (batch.length === 0) break;

    yield* batch;
    cursor = batch[batch.length - 1].id;
  }
}

Missing Index Detection

Symptoms

typescript
// Queries on non-indexed columns are slow
await db.user.findMany({
  where: { email: 'test@example.com' }  // Is email indexed?
});

await db.post.findMany({
  where: { status: 'published', createdAt: { gte: lastWeek } }
  // Composite index on (status, createdAt)?
});

Detection

sql
-- PostgreSQL: Find slow queries
SELECT query, calls, mean_time, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 20;

-- Find missing indexes (columns frequently in WHERE)
SELECT
  schemaname,
  relname as table,
  seq_scan,
  idx_scan,
  seq_scan - idx_scan as diff
FROM pg_stat_user_tables
WHERE seq_scan > idx_scan
ORDER BY diff DESC;

Fix Pattern

sql
-- Add indexes for frequently queried columns
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- Composite index for multi-column queries
CREATE INDEX CONCURRENTLY idx_posts_status_created
ON posts(status, created_at DESC);

-- Partial index for filtered queries
CREATE INDEX CONCURRENTLY idx_posts_published
ON posts(created_at DESC)
WHERE status = 'published';

Connection Pool Exhaustion

What is it?

typescript
// BAD: Creating new connections instead of using pool
async function getUser(id: string) {
  const client = new DatabaseClient(); // New connection each time!
  const user = await client.query('SELECT * FROM users WHERE id = $1', [id]);
  await client.close();
  return user;
}

// BAD: Long-running transactions holding connections
await db.$transaction(async (tx) => {
  const user = await tx.user.findUnique({ where: { id } });
  await sendEmail(user.email); // Slow external call inside transaction!
  await tx.user.update({ where: { id }, data: { notified: true } });
});

Detection Patterns

bash
# New client creation in functions
grep -rn "new.*Client\(\)" src/
grep -rn "createConnection\(\)" src/

# External calls inside transactions
grep -rn "\$transaction.*await.*fetch\|axios\|sendEmail" src/

Fix Pattern

typescript
// GOOD: Use singleton/pooled connection
// lib/db.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const db = globalForPrisma.prisma ?? new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
});

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = db;
}

// GOOD: Keep transactions short
const user = await db.user.findUnique({ where: { id } });
await sendEmail(user.email); // Outside transaction

await db.user.update({
  where: { id },
  data: { notified: true }
});

Select Only What You Need

Detection

typescript
// BAD: Selecting all columns
const users = await db.user.findMany(); // Returns all columns

// BAD: SELECT * in raw queries
const users = await db.$queryRaw`SELECT * FROM users`;

Fix Pattern

typescript
// GOOD: Select only needed columns
const users = await db.user.findMany({
  select: {
    id: true,
    name: true,
    email: true,
    // Don't select: passwordHash, internalNotes, etc.
  }
});

// GOOD: Explicit columns in raw queries
const users = await db.$queryRaw`
  SELECT id, name, email FROM users
`;

Anti-Pattern Scanning Script

bash
#!/bin/bash
# scan-db-antipatterns.sh

echo "🔍 Scanning for database anti-patterns..."

echo ""
echo "=== N+1 Queries ==="
grep -rn --include="*.ts" --include="*.tsx" \
  -E "(forEach|map|for\s+\(.*of).*\n.*await.*(find|select|query|prisma)" src/ || echo "✅ None found"

echo ""
echo "=== Unbounded Fetches ==="
grep -rn --include="*.ts" --include="*.tsx" \
  "findMany\(\s*\)" src/ || echo "✅ None found"

echo ""
echo "=== SELECT * ==="
grep -rn --include="*.ts" --include="*.tsx" \
  "SELECT \*" src/ || echo "✅ None found"

echo ""
echo "=== New DB Connections ==="
grep -rn --include="*.ts" --include="*.tsx" \
  -E "new.*(PrismaClient|Pool|Client)\(" src/ | grep -v "lib/db" || echo "✅ None found"

echo ""
echo "Done!"

Checklist

Query Patterns

  • No queries inside loops (N+1)
  • Independent queries use Promise.all
  • All list queries have LIMIT/take
  • Only needed columns selected
  • Proper indexes on filtered columns

Connection Management

  • Using connection pooling
  • Singleton pattern for DB client
  • Transactions are short
  • No external calls inside transactions

Performance

  • Slow queries identified and optimized
  • EXPLAIN ANALYZE on complex queries
  • Proper indexes for common queries
  • Monitoring in place for query times