SQL 数据库助手

sql-database-assistant
分类写作
作者Alireza Rezvani
许可MIT
评分4.50/5
使用6.4K

SQL 数据库助手 - POWERFUL 级技能

概述

数据库设计的运行伴侣。如果说 database-designer 专注于架构设计,database-schema-designer 处理 ERD 建模,那么本技能则涵盖日常操作:编写查询、优化性能、生成迁移以及弥合应用程序代码与数据库引擎之间的鸿沟。

核心能力

  • 自然语言转 SQL — 将需求转化为正确且高效的查询
  • 架构探索 — 对 PostgreSQL, MySQL, SQLite, SQL Server 等实时数据库进行内省
  • 查询优化 — EXPLAIN 分析、索引建议、N+1 问题检测、重写模式
  • 迁移生成 — 编写 up/down 脚本、零停机策略、回滚计划
  • ORM 集成 — Prisma, Drizzle, TypeORM, SQLAlchemy 的模式应用及逃逸口 (escape hatches)
  • 多数据库支持 — 具备方言感知能力的 SQL 及兼容性指导

工具

| 脚本 | 用途 |
|--------|---------|
| scripts/query_optimizer.py | 对 SQL 查询进行性能问题静态分析 |
| scripts/migration_generator.py | 根据变更描述生成迁移文件模板 |
| scripts/schema_explorer.py | 根据内省查询生成架构文档 |

---

自然语言转 SQL

转换模式

将需求转换为 SQL 时,请遵循以下顺序:

1. 识别实体 — 将名词映射到表
2. 识别关系 — 将动词映射到 JOIN 或子查询
3. 识别过滤器 — 将形容词/条件映射到 WHERE 子句
4. 识别聚合 — 将“总计”、“平均”、“计数”映射到 GROUP BY
5. 识别排序 — 将“前 N 个”、“最新”、“最高”映射到 ORDER BY + LIMIT

常用查询模板

每组前 N 名 (窗口函数)

sql
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
) ranked WHERE rn <= 3;

累计总额

sql
SELECT date, amount,
SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM transactions;

空隙检测

sql
SELECT curr.id, curr.seq_num, prev.seq_num AS prev_seq
FROM records curr
LEFT JOIN records prev ON prev.seq_num = curr.seq_num - 1
WHERE prev.id IS NULL AND curr.seq_num > 1;

UPSERT (PostgreSQL)

sql
INSERT INTO settings (key, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;

UPSERT (MySQL)

sql
INSERT INTO settings (key_name, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at);

> 更多关于 JOIN, CTE, 窗口函数, JSON 操作等内容,请参阅 references/query_patterns.md。

---

架构探索

内省查询

PostgreSQL — 列出表和列

sql
SELECT table_name, column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;

PostgreSQL — 外键

sql
SELECT tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table, ccu.column_name AS foreign_column
F

ROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY';
code
MySQL — 表大小
sql
SELECT table_name, table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length DESC;
code
SQLite — 架构导出
sql
SELECT name, sql FROM sqlite_master WHERE type = 'table' ORDER BY name;
code
SQL Server — 列及其类型
sql
SELECT t.name AS table_name, c.name AS column_name,
ty.name AS data_type, c.max_length, c.is_nullable
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
ORDER BY t.name, c.column_id;
code
### 从架构生成文档

使用 scripts/schema_explorer.py 生成 Markdown 或 JSON 文档:

bash
python scripts/schema_explorer.py --dialect postgres --tables all --format md
python scripts/schema_explorer.py --dialect mysql --tables users,orders --format json --json
code
---

查询优化

EXPLAIN 分析工作流

1. 运行 EXPLAIN ANALYZE (PostgreSQL) 或 EXPLAIN FORMAT=JSON (MySQL)
2. 识别成本最高的节点 — 大表的全表扫描 (Seq Scan)、行估算值较高的嵌套循环 (Nested Loop)
3. 检查缺失的索引 — 过滤列上出现顺序扫描
4. 查找估算错误 — 计划行数与实际行数的分歧通常意味着统计信息已过期
5. 评估 JOIN 顺序 — 确保结果集最小的表驱动连接

索引建议清单

  • WHERE 子句中选择性高的列
  • JOIN 条件中的列(外键)
  • 与 LIMIT 结合使用的 ORDER BY 列
  • 匹配多列 WHERE 谓词的复合索引(最具有选择性的列在前)
  • 针对带有常量过滤查询的部分索引(例如 WHERE status = 'active'
  • 覆盖索引,以避免读密集型查询进行表回查

查询重写模式

| 反模式 | 重写方案 |
|-------------|---------|
| SELECT * FROM orders | SELECT id, status, total FROM orders (明确指定列) |
| WHERE YEAR(created_at) = 2025 | WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01' (可利用索引/Sargable) |
| SELECT 中的相关子查询 | 使用 LEFT JOIN 配合聚合 |
| 包含 NULL 的 NOT IN (SELECT ...) | NOT EXISTS (SELECT 1 ...) |
| 不需要去重时使用 UNION | UNION ALL |
| LIKE '%search%' | 全文检索索引 (GIN/FULLTEXT) |
| ORDER BY RAND() | 应用端随机采样或使用 TABLESAMPLE |

N+1 问题检测

症状:

  • 应用程序循环中,每行父记录执行一次查询

  • ORM 在循环内部延迟加载相关实体

  • 查询日志显示数百个模式相同但 ID 不同的 SELECT 语句

解决方案:

  • 使用预加载(Prisma 中的 include,SQLAlchemy 中的 joinedload

  • 使用 WHERE id IN (...) 进行批量查询

  • 为 GraphQL 解析器使用 DataLoader 模式

静态分析工具

bash python scripts/query_optimizer.py --query "SELECT * FROM orders WHERE status = 'pending'" --dialect postgres python scripts/query_optimizer.py --query queries.sql --dialect mysql --json
code
> 详见 references/optimization_guide.md 以了解 EXPLAIN 执行计划阅读、索引类型和连接池。

---

迁移生成

零停机 (Zero-Dow)

运行时迁移模式

添加列(安全)

sql
-- Up
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Down
ALTER TABLE users DROP COLUMN phone;

code
重命名列(扩容-收缩模式)
sql
-- 步骤 1:添加新列
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- 步骤 2:数据回填
UPDATE users SET full_name = name;
-- 步骤 3:部署支持同时读取两列的应用
-- 步骤 4:部署仅写入新列的应用
-- 步骤 5:删除旧列
ALTER TABLE users DROP COLUMN name;
code
添加 NOT NULL 列(安全序列)
sql
-- 步骤 1:添加可为空的列
ALTER TABLE orders ADD COLUMN region VARCHAR(50);
-- 步骤 2:使用默认值回填
UPDATE orders SET region = 'unknown' WHERE region IS NULL;
-- 步骤 3:添加约束
ALTER TABLE orders ALTER COLUMN region SET NOT NULL;
ALTER TABLE orders ALTER COLUMN region SET DEFAULT 'unknown';
code
创建索引(非阻塞,PostgreSQL)
sql
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
code
### 数据回填策略

  • 分批更新 — 每批处理 1000-10000 行,以避免锁竞争
  • 后台任务 — 异步执行回填并跟踪进度
  • 双写 — 在过渡期间同时写入旧列和新列
  • 验证查询 — 每批次后验证行数和数据完整性

回滚策略

每次迁移必须配备可逆的 down 脚本。对于不可逆的变更:

1. 执行前备份 — 对受影响的表执行 pg_dump
2. 特性开关 (Feature flags) — 应用可在新旧 Schema 读取之间切换
3. 影子表 — 在迁移窗口期间保留原表的副本

迁移生成工具

bash python scripts/migration_generator.py --change "add email_verified boolean to users" --dialect postgres --format sql python scripts/migration_generator.py --change "rename column name to full_name in customers" --dialect mysql --format alembic --json
code
---

多数据库支持

方言差异

| 特性 | PostgreSQL | MySQL | SQLite | SQL Server |
|---------|-----------|-------|--------|------------|
| UPSERT | ON CONFLICT DO UPDATE | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE | MERGE |
| 布尔值 | 原生 BOOLEAN | TINYINT(1) | INTEGER | BIT |
| 自增 | SERIAL / GENERATED | AUTO_INCREMENT | INTEGER PRIMARY KEY | IDENTITY |
| JSON | JSONB (可索引) | JSON | 文本 (扩展) | NVARCHAR(MAX) |
| 数组 | 原生 ARRAY | 不支持 | 不支持 | 不支持 |
| CTE (递归) | 完全支持 | 8.0+ | 3.8.3+ | 完全支持 |
| 窗口函数 | 完全支持 | 8.0+ | 3.25.0+ | 完全支持 |
| 全文检索 | tsvector + GIN | FULLTEXT 索引 | FTS5 扩展 | 全文目录 |
| LIMIT/OFFSET | LIMIT n OFFSET m | LIMIT n OFFSET m | LIMIT n OFFSET m | OFFSET m ROWS FETCH NEXT n ROWS ONLY |

兼容性建议

  • 始终使用参数化查询 — 防止所有方言中的 SQL 注入
  • 在共享代码中避免使用特定方言函数 — 将其封装在适配层中
  • 在目标引擎上测试迁移 — 不同引擎的 information_schema 存在差异
  • 使用 ISO 日期格式'YYYY-MM-DD' 在所有环境下通用
  • 对标识符加引号 — 使用双引号(SQL 标准)或反引号(MySQL)

---

ORM 模式

Prisma

Schema 定义

prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
created
code
At DateTime @default(now())
}

model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}

迁移 (Migrations): npx prisma migrate dev --name add_user_email
查询 API: prisma.user.findMany({ where: { email: { contains: '@' } }, include: { posts: true } })
原生 SQL 逃生口: prisma.$queryRaw\SELECT * FROM users WHERE id = ${userId}\`

Drizzle

模式优先定义 (Schema-first definition)

typescript
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});

查询构建器 (Query builder): db.select().from(users).where(eq(users.email, email))
迁移 (Migrations):
npx drizzle-kit generate:pg 然后 npx drizzle-kit push:pg

TypeORM

实体装饰器 (Entity decorators)

typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column({ unique: true })
email: string;

@OneToMany(() => Post, post => post.author)
posts: Post[];
}

仓库模式 (Repository pattern): userRepo.find({ where: { email }, relations: ['posts'] })
迁移 (Migrations):
npx typeorm migration:generate -n AddUserEmail

SQLAlchemy

声明式模型 (Declarative models)

python
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
name = Column(String(255))
posts = relationship('Post', back_populates='author')

会话管理 (Session management): 始终使用 with Session() as session: 上下文管理器
Alembic 迁移:
alembic revision --autogenerate -m "add user email"

> 详见 references/orm_patterns.md 以获取各 ORM 的横向对比和迁移工作流。

---

数据完整性

约束策略

  • 主键 (Primary keys) — 每张表必须有一个;优先使用代理键 (serial/UUID)
  • 外键 (Foreign keys) — 强制执行参照完整性;明确定义 ON DELETE 行为
  • 唯一约束 (UNIQUE constraints) — 用于业务层面的唯一性(如邮箱、slug、API 密钥)
  • 检查约束 (CHECK constraints) — 在数据库层面验证范围、枚举和业务规则
  • 非空约束 (NOT NULL) — 默认设为 NOT NULL;仅在确实可选时设为可空

事务隔离级别

| 级别 | 脏读 | 不可重复读 | 幻读 | 使用场景 |
|-------|-----------|-------------------|-------------|----------|
| READ UNCOMMITTED | 是 | 是 | 是 | 不推荐使用 |
| READ COMMITTED | 否 | 是 | 是 | PostgreSQL 默认,通用 OLTP |
| REPEATABLE READ | 否 | 否 | 是 (InnoDB: 否) | 金融计算 |
| SERIALIZABLE | 否 | 否 | 否 | 关键一致性(计费、库存) |

死锁预防

1. 一致的锁顺序 — 始终按相同的表/行顺序获取锁
2. 短事务 — 尽量缩短从获取第一个锁到提交之间的时间
3. 咨询锁 (Advisory locks) — 使用
pg_advisory_lock() 进行应用级协调
4. 重试逻辑 — 捕获死锁错误并使用指数退避算法重试

---

备份与恢复

PostgreSQL

bash
# 全量备份
pg_dump -Fc --no-owner dbname > backup.dump

恢复

pg_restore -d dbname --clean --no-owner backup.dump

时间点恢复 (PITR):配置 WAL 归档 + restore_command

MySQL

bash
# 全量备份
mysqldump --single-transaction --routines --triggers dbname > backup.sql

恢复

mysql dbname < backup.sql

用于 PITR 的二进制日志: mysqlbi

nlog --start-datetime="2025-01-01 00:00:00" binlog.000001
code
### SQLite
bash

备份(支持并发读取)

sqlite3 dbname ".backup backup.db"
`

备份最佳实践

  • 自动化 — 使用 cron 或 systemd timer,绝不要仅依赖手动备份
  • 测试恢复 — 未经测试的备份不能算作备份
  • 异地副本 — 存储在 S3、GCS 或不同区域
  • 保留策略 — 每日备份保留 7 天,每周备份保留 4 周,每月备份保留 12 个月
  • 监控备份大小和时长 — 突然的变化通常预示着问题

---

反模式 (Anti-Patterns)

| 反模式 | 问题 | 解决方案 |
|-------------|---------|-----|
|
SELECT * | 传输不必要的数据,在模式变更时易崩溃 | 明确列出所需字段 |
| 外键列缺失索引 | 导致 JOIN 和级联删除缓慢 | 为所有外键添加索引 |
| N+1 查询 | 产生 1 + N 次数据库往返 | 使用预加载 (Eager loading) 或批量查询 |
| 隐式类型转换 |
WHERE id = '123' 会导致索引失效 | 谓词中的类型必须匹配 |
| 无连接池 | 高负载下会耗尽连接 | 使用 PgBouncer, ProxySQL 或 ORM 连接池 |
| 无限制查询 | 缺失 LIMIT 可能会返回数百万行数据 | 始终使用分页 |
| 使用 FLOAT 存储金额 | 产生舍入误差 | 使用
DECIMAL(19,4) 或以分为单位的整数 |
| “上帝表” (God tables) | 单表包含 50 多个列 | 进行规范化或垂直分表 |
| 滥用软删除 | 每个查询都需加上
WHERE deleted_at IS NULL`,增加复杂度 | 使用归档表或事件溯源 |
| 原始字符串拼接 | 容易遭受 SQL 注入 | 始终使用参数化查询 |

---

交叉引用

| 技能 | 关联关系 |
|-------|-------------|
| database-designer | 模式架构、规范化分析、ERD 生成 |
| database-schema-designer | 可视化 ERD 建模、关系映射 |
| migration-architect | 复杂的多步骤迁移编排 |
| api-design-reviewer | 确保 API 接口与查询模式一致 |
| observability-platform | 查询性能监控、慢查询告警 |