本质是连接串未暴露环境标识,应使用语义化域名(如mysql-prod.example.com)替代IP硬编码,配合DNS解析、环境变量、视觉提示、权限切片及safe-updates等四层防护。

开发账号连错生产库,本质是环境标识不暴露
最常见的情况不是权限太大,而是连接串本身没告诉人这是生产库。硬写 10.10.10.10 或 localhost 的连接配置,等于把油门和刹车都藏在黑盒里。
实操建议:
- 所有项目根目录放
.env,内容设为DB_HOST=mysql-prod.example.com,并确保.gitignore包含它 - DNS 层解析
mysql-prod.example.com到生产 IP,mysql-dev.example.com解析到开发 IP,禁止任何环境共用一个域名 - 终端提示符或 IDE 标签页加颜色/文字前缀(如 [PROD]),靠视觉强提醒
- CI/CD 流水线脚本中检查变量名是否含
prod、live等关键词,命中即退出
权限必须按环境切片,不能“一套账号走天下”
给开发人员发一个带 SELECT, INSERT, UPDATE, DELETE 的生产账号,就等于默认允许他手抖删表——哪怕他本意只是查数据。
实操建议:
- 生产环境只建两类账号:
app-prod-ro(仅SELECT)和app-prod-dml(仅白名单UPDATE/DELETE,且限定库+表+字段) - 执行
SHOW GRANTS FOR CURRENT_USER;必须成为开发连接后的第一行命令,CI 脚本可加断言校验返回中不含DROP、TRUNCATE - 禁用
FILE、PROCESS、SUPER权限;CREATE TEMPORARY TABLES和EXECUTE可保留在开发账号,但生产账号一律剔除
客户端强制启用 safe-updates,但得知道它不覆盖所有场景
safe-updates 是防手抖最便宜有效的手段,但它只对 MySQL 命令行客户端和部分 GUI 工具生效,ORM 或 shell 脚本可能绕过。
实操建议:
- 每个开发机的
~/.my.cnf里配好:[client] safe-updates
- Django 的
dbshell启动时手动加--safe-updates参数;其他 ORM 检查文档是否支持该 flag - 生产环境的
my.cnf服务端配置里 不要 设sql_safe_updates=1,否则运维脚本会失败 - 该模式下
DELETE FROM users;会被拒绝,但DELETE FROM users WHERE id IN (SELECT ...)可能仍被允许——需配合 SQL 审核工具补漏
同步开发数据到生产时,mysql 库必须显式跳过
误删往往发生在“我以为只是同步业务表”,结果 mysqldump --all-databases 把 mysql.user 表也刷进去了,生产账号全被开发库权限覆盖。
实操建议:
- 永远不用
--all-databases往生产推;改用显式列出业务库:mysqldump --databases app_db billing_db - 若必须用
--ignore-table,注意它对--all-databases无效,只能搭配单库 dump 使用 - dump 前检查是否含权限语句:
head -n 50 dump.sql | grep -E "(CREATE USER|GRANT)",有则重加--skip-extended-insert --no-create-info --skip-triggers --skip-routines - 所有权限变更必须走独立版本化 SQL 文件(如
prod-grants-v202609.sql),由 DBA 审批后单独执行
真正难防的不是“删”,而是“删了还不知道删的是哪”。环境隔离、权限切片、客户端防护、同步规则这四层,缺一不可。最容易被忽略的是 DNS 域名语义化和 .env 文件的 .gitignore 配置——这两处一旦漏掉,前面所有权限控制都形同虚设。











