with one click
database-safety
写 SQL、改表结构、做数据迁移时使用。防止锁表、丢数据、慢查询。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
写 SQL、改表结构、做数据迁移时使用。防止锁表、丢数据、慢查询。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | database-safety |
| description | 写 SQL、改表结构、做数据迁移时使用。防止锁表、丢数据、慢查询。 |
| category | backend |
| tags | ["数据库","sql","迁移"] |
规则: 每个 migration 必须配 down 方法(回滚路径);对行数超过百万的表加列或建索引,必须评估锁表风险,使用在线 DDL 工具(pt-online-schema-change、gh-ost 或数据库自带的 ALGORITHM=INPLACE)而非直接 ALTER TABLE。
为什么: AI 生成迁移脚本时几乎从不写 down——"反正一般不需要回滚"。但生产故障时那个"一般"就变成了"现在":新代码引发 panic,需要立刻回滚版本,却发现数据库结构已经变了,应用旧版本跑不起来,只能手动改表,在最紧张的时候操作最危险的事。大表直接 ALTER TABLE ADD COLUMN 在 MySQL 5.x 上会持有表级写锁,百万行意味着分钟级锁表,期间所有写操作排队,直接触发超时告警。
怎么做:
migrate up 后紧接着跑 migrate down 再 migrate up,验证回滚可用。SELECT COUNT(*) 或查 information_schema;超过 50 万行的表结构变更,方案里必须写明用哪种在线 DDL。CREATE INDEX CONCURRENTLY(PostgreSQL)或 ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE(MySQL 8+),不阻塞读写。规则: 任何"先取列表,再对每条记录发一次查询"的模式都是 N+1,必须改为一次批量查询或 JOIN。
为什么: AI 生成 ORM 代码时,N+1 是最高频的性能 bug,而且在小数据集的本地环境下完全感觉不到——10 条记录发 11 次查询,每次 1 ms,总耗时 11 ms,"挺快的"。到了生产环境 1000 条记录,就变成 1001 次查询,慢查询日志被打爆,数据库连接池耗尽,整个服务开始抖动。这类问题在代码 review 时也容易被忽略,因为"逻辑上没错"。
怎么做:
# 反例: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(User.id.in_(user_ids)).all()}
for order in orders:
print(users[order.user_id].name)
joinedload/selectinload,Django ORM 用 select_related/prefetch_related。bulk_insert_mappings 或 INSERT INTO ... VALUES (...),(...) 而非循环单条 INSERT。echo=True 或慢查询日志确认实际 SQL 数量。规则: 涉及多张表或多条记录的写操作必须放在同一个事务里;事务要尽量短;并发写场景需评估隔离级别是否会产生幻读/不可重复读,以及多事务并发时的死锁顺序。
为什么: AI 生成的代码里最常见的是"多个 UPDATE 语句顺序执行但没有事务包裹"——前两条成功、第三条失败,数据进入不一致状态,没有任何报错,只有用户几天后发现账目对不上。另一个常见错误是事务里夹了 HTTP 调用或发邮件,事务持有行锁长达秒级,把并发吞吐量打到个位数。死锁则多发于两个事务以相反顺序锁定同两行的场景,AI 生成时不会自动对齐加锁顺序。
怎么做:
BEGIN/COMMIT。SELECT ... FOR UPDATE 悲观锁,或乐观锁(版本号/CAS);明确写在代码注释里选择了哪种策略及理由。users 再锁 accounts),消除死锁可能。规则: WHERE/JOIN ON/ORDER BY 字段必须有对应索引;禁止 SELECT *,只取需要的列;上线前用 EXPLAIN/EXPLAIN ANALYZE 确认执行计划无全表扫描。
为什么: AI 生成查询时默认写 SELECT *,理由是"方便,反正都要用"。问题在于:拉了大量不需要的字段浪费网络和内存;使用覆盖索引的机会被破坏(数据库无法仅靠索引返回结果,必须回表);表结构新增敏感列后,SELECT * 会意外暴露该列。索引缺失则更直接:百万行表的 WHERE status = 'pending' 没有索引就是全表扫,慢查询日志里最常见的就是这一类。
怎么做:
WHERE 中,建复合索引时高选择度的列放左边。EXPLAIN 输出中 type 列出现 ALL(MySQL)或 Seq Scan(PG)即为全表扫,必须处理。SELECT * 全局禁止:ORM 映射取对象可接受,但原生 SQL 或性能敏感路径必须列出字段名。规则: 对生产库执行 UPDATE/DELETE 前,先用相同条件跑一次 SELECT COUNT(*) 确认影响行数;批量变更先备份目标行;禁止无 WHERE 子句的 UPDATE/DELETE(等同于全表覆盖/清空)。
为什么: AI 生成数据修复脚本时最容易犯的错:UPDATE users SET status = 'disabled'——漏写了 WHERE,全表被覆盖,数百万用户账号被禁用,且没有回滚手段。即使有 WHERE,条件写错(如等号写成不等号)也会造成大范围误伤。这类操作在控制台执行一瞬间完成,但恢复可能需要数小时甚至数天。
怎么做:
BEGIN → SELECT COUNT(*) WHERE <条件> → 确认数字再 UPDATE/DELETE,最后 COMMIT。CREATE TABLE users_backup_20240601 AS SELECT * FROM users WHERE <条件>。WHERE 的 UPDATE/DELETE。-- 反例 — 对百万行表直接 ALTER,持锁期间写操作全部阻塞;没有 down 迁移
-- migrations/V20_add_verified_column.sql
ALTER TABLE users ADD COLUMN is_verified BOOLEAN DEFAULT FALSE;
-- (没有对应的 down 迁移脚本)
-- 正例 — 评估行数,使用在线 DDL;配有 down 迁移
-- migrations/V20_add_verified_column.sql (up)
-- 确认行数 ~500 万,使用 ALGORITHM=INPLACE 避免锁表
ALTER TABLE users
ADD COLUMN is_verified BOOLEAN NOT NULL DEFAULT FALSE,
ALGORITHM=INPLACE, LOCK=NONE;
-- migrations/V20_add_verified_column_down.sql (down)
ALTER TABLE users DROP COLUMN is_verified;
-- 反例 — 漏写 WHERE,全表被覆盖,且无法回滚
UPDATE orders SET status = 'cancelled'; -- ❌ 清空了所有订单状态
-- 正例 — 先 SELECT 确认,事务包裹,分批执行
BEGIN;
-- 第一步:确认影响行数
SELECT COUNT(*) FROM orders
WHERE status = 'pending' AND created_at < '2024-01-01';
-- 确认数字符合预期后继续
-- 第二步:备份目标行
CREATE TABLE orders_cancelled_backup_20240601 AS
SELECT * FROM orders
WHERE status = 'pending' AND created_at < '2024-01-01';
-- 第三步:执行变更
UPDATE orders
SET status = 'cancelled'
WHERE status = 'pending' AND created_at < '2024-01-01'; -- ✅ 有明确 WHERE
COMMIT;
down/回滚脚本,且本地已验证可用。WHERE/JOIN/ORDER BY 字段已有索引,EXPLAIN 输出无全表扫描。SELECT *(性能敏感路径已明确列出字段名)。UPDATE/DELETE 语句都有 WHERE 条件,并已用 SELECT COUNT(*) 确认影响行数。