database-safetylisted
Install: claude install-skill Wade-DevCode/awesome-coding-skills-cn
# 数据库安全
## 何时用
- 新增或修改数据库迁移脚本(migration),上线前评估影响时。
- 写查询逻辑,需要确认性能是否可接受、是否存在 N+1 时。
- 写更新/删除语句,操作生产数据或批量变更前。
- 遇到慢查询告警、锁等待、数据库 CPU 飙高时定位根因。
## 核心规则
### 1. 迁移可回滚,大表结构变更用在线 DDL
**规则:** 每个 migration 必须配 `down` 方法(回滚路径);对行数超过百万的表加列或建索引,必须评估锁表风险,使用在线 DDL 工具(`pt-online-schema-change`、`gh-ost` 或数据库自带的 `ALGORITHM=INPLACE`)而非直接 `ALTER TABLE`。
**为什么:** AI 生成迁移脚本时几乎从不写 `down`——"反正一般不需要回滚"。但生产故障时那个"一般"就变成了"现在":新代码引发 panic,需要立刻回滚版本,却发现数据库结构已经变了,应用旧版本��不起来,只能手动改表,在最紧张的时候操作最危险的事。大表直接 `ALTER TABLE ADD COLUMN` 在 MySQL 5.x 上会持有表级写锁,百万行意味着分钟级锁表,期间所有写操作排队,直接触发超时告警。
**怎么做:**
- 框架约定(Flyway/Liquibase/Alembic/Rails migrations)每个文件都要有回滚逻辑,CI 跑 `migrate up` 后紧接着跑 `migrate down` 再 `migrate up`,验证回滚可用。
- 估算表行数:`SELECT COUNT(*)` 或查 `information_schema`;超过 50 万行的表结构变更,方案里必须写明用哪种在线 DDL。
- 建索引用 `CREATE INDEX CONCURRENTLY`(PostgreSQL)或 `ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE`(MySQL 8+),不阻塞读写。
---
### 2. 禁止在循环里查库,用批量/JOIN/预加载
**规则:** 任何"先取列表,再对每条记录发一次查询"的模式都是 N+1,必须改为一次批量查询或 JOIN。
**为什么:** AI 生成 ORM 代码时,N+1 是最高频的性能 bug,而且在小数据集的本地环境下完全感觉不到——10 条记录发 11 次查询,每次 1 ms,总耗时 11 ms,"挺快的"。到了生产环境 1000 条记录,就变成 1001 次查询,慢查询日志被打爆,数据库连接池耗尽,整个服务开始抖动。这类问题在代码 review 时也容易被忽略,因为"逻辑上没错"。
**怎么做:**
```python
# 反例:N+1
orders = Order.query.all()
for order in orders:
user = User.query.get(order.user_id) # ❌ 每次循环一次查询
print(user.name)
# 正例:预加载 / 批量查询
orders = Order.query.options(joinedload(Order.user)).all() # ✅ 一次 JOIN
# 或者
user_ids = [o.user_id for o in orders]
users = {u.id: u for u in User.query.filter(Use