连接池账号必须仅具USAGE权限,用于连接与健康检查,业务操作须通过DEFINER存储过程执行;禁止授予表级权限及SUPER等高危权限,需显式CREATE USER并设MAX_USER_CONNECTIONS限制。

连接池账号必须只带USAGE权限,不能有业务表操作权
应用连接池账号的核心作用是维持TCP连接和健康检查,不是执行业务SQL。一旦给它授予SELECT、INSERT等表级权限,就等于把数据库操作入口直接暴露在应用层,违背解耦原则。
常见错误是建号后顺手执行GRANT SELECT ON myapp.* TO 'pool_user'@'%',结果所有DAO方法都能绕过存储过程直查表——权限控制形同虚设。
-
USAGE是唯一必需权限,它允许登录但禁止任何数据操作 - 如需健康检查(例如ProxySQL或K8s readiness probe),可额外授权
SELECT到一个轻量视图,比如mysql.health_check,而非真实业务表 - 绝对不要授
SUPER、PROCESS、REPLICATION CLIENT等管理类权限,这些会绕过DEFINER机制
CREATE USER必须显式执行,且带WITH子句设连接数上限
MySQL 8.0+已弃用隐式GRANT建号,生产环境必须先CREATE USER再GRANT,否则无法设置密码策略、SSL要求或连接数限制。
连接池账号最容易漏掉的是并发连接数控制。如果连接池配置maximumPoolSize=20,但数据库没设限,单个应用实例就能耗尽max_connections,导致其他服务连不上。
- 正确写法:
CREATE USER 'pool_user'@'10.20.30.%' IDENTIFIED BY 'pwd' REQUIRE SSL WITH MAX_USER_CONNECTIONS 15; - host必须精确匹配应用服务器IP段,
'%'通配符在云环境极易被拦截或拒绝 - 设为
0不等于禁用,而是退回到全局max_connections限制,实际仍可能超限
业务操作必须走DEFINER存储过程,调用者只需EXECUTE权限
连接池账号本身没有业务权限,所有数据操作靠CALL触发。关键在SQL SECURITY DEFINER——它让语句以定义者身份执行,而非调用者。
例如get_user_by_id()里固定只查id、nickname字段,就算应用传入恶意参数,也不会泄露password_hash;WHERE条件里的status = 'active'也由过程固化,无法被绕过。
- 定义者账号(如
'app_logic'@'localhost')需拥有完整业务库权限,但该账号不暴露给应用 - 连接池账号只需
GRANT EXECUTE ON PROCEDURE myapp.get_user_by_id TO 'pool_user'@'10.20.30.%'; - 禁止在过程体中拼接表名、字段名或
ORDER BY子句,否则DEFINER机制失效,注入风险重现
验证是否真解耦:查表+实测比SHOW GRANTS更可靠
SHOW GRANTS FOR 'pool_user'@'10.20.30.%'看不出MAX_USER_CONNECTIONS值,也看不到DEFINER过程的调用权限是否生效。真正可信的方式只有两个:
- 查
mysql.user表:SELECT User, Host, max_user_connections FROM mysql.user WHERE User = 'pool_user'; - 实测触发限制:用
mysql -u pool_user -p -h db -e "SELECT 1;"开多个终端,第N+1个应报ERROR 1226 (42000) - 尝试直查业务表:
SELECT * FROM users;必须明确报ERROR 1142 (42000),而不是返回空结果——后者说明权限没生效或被其他账号覆盖
最常被忽略的是host匹配细节:'pool_user'@'localhost'和'pool_user'@'127.0.0.1'在MySQL里是两个账号,而多数PHP/Java客户端默认连127.0.0.1,结果权限查不到。


















