数据库模式意识技能 database-schema

这是一种软件开发技能,专注于在编写数据库代码之前,通过阅读和理解数据库模式来预防错误,提高代码质量和开发效率。关键词包括数据库模式、类型安全、代码质量、开发效率。

后端开发 0 次安装 0 次浏览 更新于 3/5/2026

数据库模式意识技能

加载方式:base.md + [你的数据库技能]

问题: Claude在会话中忘记了模式细节 - 错误的列名,缺失的字段,错误的类型。TDD在运行时捕获这个问题,但我们可以在更早的阶段预防。


核心规则:在编写数据库代码之前先阅读模式

强制性:在编写任何涉及数据库的代码之前:

┌─────────────────────────────────────────────────────────────┐
│  1. 阅读模式文件(见下方位置)              │
│  2. 验证你即将使用的列/类型是否存在          │
│  3. 在编写查询时参考模式在你的回应中         │
│  4. 使用生成的类型进行类型检查(Drizzle/Prisma等)   │
└─────────────────────────────────────────────────────────────┘

如果模式文件不存在 → 在继续之前创建它。


模式文件位置(按技术栈)

技术栈 模式位置 类型生成
Drizzle src/db/schema.tsdrizzle/schema.ts 内置TypeScript
Prisma prisma/schema.prisma npx prisma generate
Supabase supabase/migrations/*.sql + 类型 supabase gen types typescript
SQLAlchemy app/models/*.pysrc/models.py Pydantic模型
TypeORM src/entities/*.ts 装饰器=类型
Raw SQL schema.sqlmigrations/ 需要手动类型

模式参考文件(推荐)

创建_project_specs/schema-reference.md用于快速查找:

# 数据库模式参考

*自动生成或手动维护。Claude:在数据库工作前阅读此文件。*

## 表

### users
| 列 | 类型 | 可空 | 默认 | 注释 |
|--------|------|----------|---------|-------|
| id | uuid | 否 | gen_random_uuid() | PK |
| email | text | 否 | - | 唯一 |
| name | text | 是 | - | 显示名称 |
| created_at | timestamptz | 否 | now() | - |
| updated_at | timestamptz | 否 | now() | - |

### orders
| 列 | 类型 | 可空 | 默认 | 注释 |
|--------|------|----------|---------|-------|
| id | uuid | 否 | gen_random_uuid() | PK |
| user_id | uuid | 否 | - | FK → users.id |
| status | text | 否 | 'pending' | 枚举:pending/paid/shipped/delivered |
| total_cents | integer | 否 | - | 分数额 |
| created_at | timestamptz | 否 | now() | - |

## 关系
- users 1:N orders (user_id)

## 枚举
- order_status: pending, paid, shipped, delivered

代码前检查表(数据库工作)

在编写任何数据库代码之前,Claude必须:

### 模式验证检查表
- [ ] 阅读模式文件:`[path to schema]`
- [ ] 我使用的列存在:[list columns]
- [ ] 类型与我的代码匹配:[list type mappings]
- [ ] 关系正确:[list FKs]
- [ ] 处理可空字段:[list nullable columns]

实际操作示例:

### TODO-042(添加订单历史端点)的模式验证

- [x] 阅读模式:`src/db/schema.ts`
- [x] 列存在:orders.id, orders.user_id, orders.status, orders.total_cents, orders.created_at
- [x] 类型:id=uuid→string, total_cents=integer→number, status=text→OrderStatus枚举
- [x] 关系:orders.user_id → users.id(多对一)
- [x] 可空:这些列都不是可空的

类型生成命令

Drizzle(TypeScript)

// 模式自动定义类型
// src/db/schema.ts
import { pgTable, uuid, text, integer, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').notNull().defaultNow(),
});

export const orders = pgTable('orders', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').notNull().references(() => users.id),
  status: text('status').notNull().default('pending'),
  totalCents: integer('total_cents').notNull(),
  createdAt: timestamp('created_at').notNull().defaultNow(),
});

// 推断类型 - 使用这些
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Order = typeof orders.$inferSelect;
export type NewOrder = typeof orders.$inferInsert;

Prisma

// prisma/schema.prisma
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  orders    Order[]
  createdAt DateTime @default(now()) @map("created_at")

  @@map("users")
}

model Order {
  id         String   @id @default(uuid())
  userId     String   @map("user_id")
  user       User     @relation(fields: [userId], references: [id])
  status     String   @default("pending")
  totalCents Int      @map("total_cents")
  createdAt  DateTime @default(now()) @map("created_at")

  @@map("orders")
}
# 生成类型后模式更改
npx prisma generate

Supabase

# 从实时数据库生成TypeScript类型
supabase gen types typescript --local > src/types/database.ts

# 或从远程
supabase gen types typescript --project-id your-project-id > src/types/database.ts
// 使用生成的类型
import { Database } from '@/types/database';

type User = Database['public']['Tables']['users']['Row'];
type NewUser = Database['public']['Tables']['users']['Insert'];
type Order = Database['public']['Tables']['orders']['Row'];

SQLAlchemy(Python)

# app/models/user.py
from sqlalchemy import Column, String, DateTime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from app.db import Base
import uuid

class User(Base):
    __tablename__ = "users"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    email = Column(String, nullable=False, unique=True)
    name = Column(String, nullable=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    # 关系
    orders = relationship("Order", back_populates="user")
# app/schemas/user.py - Pydantic用于API验证
from pydantic import BaseModel, EmailStr
from uuid import UUID
from datetime import datetime

class UserBase(BaseModel):
    email: EmailStr
    name: str | None = None

class UserCreate(UserBase):
    pass

class User(UserBase):
    id: UUID
    created_at: datetime

    class Config:
        from_attributes = True

模式感知TDD工作流程

扩展数据库工作的标凈TDD工作流程:

┌─────────────────────────────────────────────────────────────┐
│  0. 模式:在其他任何事之前阅读和验证模式     │
│     └─ 阅读模式文件                                     │
│     └─ 完成模式验证检查表               │
│     └─ 注意任何缺失的列/表所需的               │
├─────────────────────────────────────────────────────────────┤
│  1. RED:使用正确的列名编写测试          │
│     └─ 导入生成的类型                               │
│     └─ 在测试中使用类型安全的查询                       │
│     └─ 测试应在逻辑上失败,而不是模式错误        │
├─────────────────────────────────────────────────────────────┤
│  2. GREEN:用类型安全的查询实现                 │
│     └─ 使用ORM类型,而不是原始字符串                       │
│     └─ TypeScript/mypy捕获列不匹配            │
├─────────────────────────────────────────────────────────────┤
│  3. VALIDATE:类型检查捕获模式漂移               │
│     └─ tsc --noEmit / mypy捕获错误的列            │
│     └─ 测试验证运行时行为                      │
└─────────────────────────────────────────────────────────────┘

常见模式错误(及如何预防)

错误 示例 预防
错误的列名 user.userName vs user.name 阅读模式,使用生成的类型
错误的类型 totalCents作为字符串 类型生成捕获这个
缺少可空检查 user.name!当可空时 模式显示可空字段
错误的FK关系 order.userId vs order.user_id 检查模式列名
缺少列 使用不存在的user.avatar 编写代码前阅读模式
错误的枚举值 status: 'complete' vs 'completed' 在模式参考中记录枚举

类型安全查询示例

Drizzle(在编译时捕获错误):

// ✅ 正确 - 使用模式定义的列
const user = await db.select().from(users).where(eq(users.email, email));

// ❌ 错误 - TypeScript错误:'userName'不存在
const user = await db.select().from(users).where(eq(users.userName, email));

Prisma(在编译时捕获错误):

// ✅ 正确
const user = await prisma.user.findUnique({ where: { email } });

// ❌ 错误 - TypeScript错误
const user = await prisma.user.findUnique({ where: { userName: email } });

原始SQL(无保护 - 避免):

// ❌ 危险 - 没有类型检查,容易出错
const result = await db.query('SELECT * FROM users WHERE user_name = $1', [email]);
// 应该是'email'而不是'user_name' - 直到运行时才会捕获

迁移工作流程

当需要模式更改时:

┌─────────────────────────────────────────────────────────────┐
│  1. 更新模式文件(Drizzle/Prisma/SQLAlchemy)          │
├─────────────────────────────────────────────────────────────┤
│  2. 生成迁移                                       │
│     └─ Drizzle: npx drizzle-kit generate                    │
│     └─ Prisma: npx prisma migrate dev --name add_column     │
│     └─ Supabase: supabase migration new add_column          │
├─────────────────────────────────────────────────────────────┤
│  3. 重新生成类型                                         │
│     └─ Prisma: npx prisma generate                          │
│     └─ Supabase: supabase gen types typescript              │
├─────────────────────────────────────────────────────────────┤
│  4. 更新schema-reference.md                               │
├─────────────────────────────────────────────────────────────┤
│  5. 运行类型检查 - 找到所有破损的代码                    │
│     └─ npm run typecheck                                    │
├─────────────────────────────────────────────────────────────┤
│  6. 修复类型错误,更新测试,运行完整验证       │
└─────────────────────────────────────────────────────────────┘

会话开始协议

当开始涉及数据库工作的会话时:

  1. 立即阅读模式文件
  2. 如果存在,阅读_project_specs/schema-reference.md
  3. 在会话状态中注明相关的表/列
  4. 在编写代码时明确参考模式

会话状态示例:

## 当前会话 - 数据库上下文

**模式已读:**✓ src/db/schema.ts
**范围内的表:** users, orders, order_items
**关键列:**
- users: id, email, name, created_at
- orders: id, user_id, status, total_cents
- order_items: id, order_id, product_id, quantity, price_cents

反模式

  • 猜测列名 - 总是先阅读模式
  • 使用原始SQL字符串 - 使用带类型生成的ORM
  • 不验证就硬编码 - 使用任何列之前检查模式
  • 忽略类型错误 - 模式漂移显示为类型错误
  • 不重新生成类型 - 迁移后总是重新生成
  • 假设可空 - 检查模式中的可空列

清单

设置

  • [ ] 模式文件存在于标准位置
  • [ ] 类型生成配置
  • [ ] 创建_project_specs/schema-reference.md
  • [ ] 模式更改时重新生成类型

每项任务

  • [ ] 在编写数据库代码之前阅读模式
  • [ ] 完成模式验证检查表
  • [ ] 使用生成的类型(不是原始字符串)
  • [ ] 类型检查通过(捕获列错误)
  • [ ] 测试使用正确的模式