应为每个功能模块创建独立数据库并授予最小必要权限,如订单模块用db_order库和app_order账号,仅授权SELECT/INSERT/UPDATE,禁用DROP/ALTER;跨库访问须通过只读视图或API,严禁直连授全表权限;权限变更必须通过Git托管的SQL脚本执行;连接池需限制单账号最大连接数并与应用配置对齐。

给模块专用数据库分配最小权限
直接为每个功能模块建独立数据库,再只授该库的必要权限,是最清晰、最易审计的做法。比如订单模块用 db_order,用户模块用 db_user,不共用 mysql 或 information_schema。
常见错误是用一个库加前缀(如 order_*、user_* 表),然后给账号全库 SELECT,INSERT,UPDATE,DELETE —— 一旦某模块被注入或误操作,整个库都遭殃。
- 创建模块库:
CREATE DATABASE db_order CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 建专用账号并限制来源:
CREATE USER 'app_order'@'10.20.%.%' IDENTIFIED BY 'strong-pass-2024'; - 只授本库权限:
GRANT SELECT,INSERT,UPDATE ON db_order.* TO 'app_order'@'10.20.%.%'; - 立即生效:
FLUSH PRIVILEGES;
注意:不要省略主机段(如 '10.20.%.%'),否则可能意外开放给公网;也不要授 DROP 或 ALTER,除非模块真有在线 DDL 需求。
避免跨库查询导致权限失控
模块间偶尔要查其他库数据(比如订单页显示用户昵称),但若直接授 SELECT ON db_user.* 给 app_order,就等于把用户库裸露了。
更安全的做法是:在 db_order 里建只读视图,或由用户服务提供 API,而不是让订单服务直连用户库。
- 如果必须视图,只包含必需字段:
CREATE VIEW order_user_info AS SELECT id, nickname FROM db_user.users; - 然后只授视图权限:
GRANT SELECT ON db_order.order_user_info TO 'app_order'@'10.20.%.%'; - 严禁用
DEFINER = 'root'@'%'创建视图——这会让低权限账号借 root 身份执行逻辑
MySQL 视图权限不继承底层表权限,所以必须显式授权视图本身;且视图不能替代行级控制,敏感字段(如手机号、密码)绝不能进视图。
权限变更必须走 SQL 脚本,别靠 phpMyAdmin 点
线上环境改权限,靠图形界面点几下,既没记录、也没回滚路径,出事根本不知道谁干的、什么时候改的。
所有权限操作必须写成可执行、可 review 的 SQL 脚本,和应用代码一起进 Git,走发布流程。
- 脚本开头加注释说明用途和模块归属,例如:
-- [ORDER-SERVICE] add INSERT privilege for order_items table - 用
REVOKE显式收回旧权限,再GRANT新权限,避免叠加残留 - 禁止在脚本里写明文密码;密码应由部署系统注入或从密钥管理服务获取
开发本地测试时用 localhost,上线却忘了改成内网 IP 段,导致权限只对本地生效——这种配置漂移,只有脚本化才能暴露。
连接池复用账号会放大权限风险
Java 应用用 HikariCP、Go 用 sqlx 连接池时,常把模块账号全局复用。结果订单服务某个 bug 导致连接泄漏,占满 app_order 连接数,用户服务也连不上——不是因为数据库扛不住,而是账号并发数被耗尽。
MySQL 的 max_user_connections 默认是 0(无限制),但线上必须设硬上限。
- 限制单账号最大连接:
ALTER USER 'app_order'@'10.20.%.%' WITH MAX_USER_CONNECTIONS 50; - 检查当前连接占用:
SELECT user, host, COUNT(*) FROM information_schema.processlist GROUP BY user, host; - 连接池配置里的
maximumPoolSize必须 ≤ 数据库侧限制值,否则多余连接会卡在“connecting”状态
不同模块的连接池参数(超时、重试、最大连接数)最好隔离配置,避免一个模块抖动拖垮整个数据库访问链路。










