MySQL的权限体系是数据库安全的第一道防线,但真正把它配置明白的人并不多。常见的情况是:图省事直接用root账号连接业务程序,或者干脆给一个账号授予ALL PRIVILEGES加通配符,一旦程序出现SQL注入漏洞,攻击者就等于拿到了数据库的完全控制权。这篇文章从权限验证流程讲起,一步步说明如何针对不同场景配置合理的运行权限,并给出可以直接套用的授权语句。

先弄懂MySQL权限验证的两个阶段
MySQL判断一个用户能做什么,分为连接验证和请求验证两步。第一步发生在建立连接时,服务器会检查你输入的主机名、用户名和密码是否与mysql库中的用户记录匹配。这里有个容易被忽略的细节:MySQL中的用户是由“用户名@主机地址”共同定义的,appuser@localhost和appuser@192.168.1.%是两个完全独立的账号,权限也互不影响。如果不了解这一点,很容易出现本地测试正常、远程连接却报错ACCESS DENIED的困惑。
第二步发生在每一条SQL语句执行前。连接建立后,你发出的每个请求都会被权限系统逐一检查,比如执行SELECT时检查是否有目标表的SELECT权限,执行DELETE时检查DELETE权限。这个检查过程依赖内存中的权限缓存,MySQL启动时会把mysql库下的user、db、tables_priv、columns_priv等权限表全部加载进内存,之后通过GRANT或REVOKE修改权限时会自动更新缓存。
权限检查遵循从大到小、逐层覆盖的原则:先看user表中的全局权限,如果全局权限已经满足,就不再继续查下层表;如果不能满足,再去db表看数据库级权限,然后是表级、列级。这也是为什么授权时要遵循“权限最小化”原则——user表里的权限是对所有库生效的,能不给就不给。
用GRANT语句完成精细化授权
授权的核心语句是GRANT,基本语法为:GRANT 权限列表 ON 数据库.表 TO '用户'@'主机' IDENTIFIED BY '密码'。给Web应用创建账号时,推荐只授予业务库的增删改查权限,禁止任何管理类权限:
-- 创建应用账号,只允许从应用服务器网段连接 CREATE USER 'webapp'@'192.168.1.%' IDENTIFIED BY 'Str0ng_Pass!2024'; -- 只授予业务库的DML权限,不给DROP、ALTER等危险权限 GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'webapp'@'192.168.1.%'; -- 开发或运维账号,可查看所有库但不能修改数据 GRANT SELECT ON *.* TO 'readonly'@'10.0.0.%' IDENTIFIED BY 'R3ad0nly!';
权限列表可以用逗号分隔多个权限,也可以使用ALL PRIVILEGES代表全部权限(除了GRANT OPTION本身)。授权对象支持通配符:*.*表示所有数据库的所有表,shop.*表示shop库下的全部对象,也可以精确到shop.orders某张表甚至某几列。列级授权适合数据脱敏场景,比如只允许查询用户表的昵称和注册时间,不开放手机号字段。
需要特别说明主机地址的写法:localhost只允许本机通过socket连接;%表示任意主机,风险很高,生产环境应尽量避免;192.168.1.%限定网段;也可以精确到单个IP。主机匹配时MySQL会按user表记录排序取最精确的匹配,如果同时存在user@%和user@192.168.1.10,后者优先级更高。
回收权限与FLUSH PRIVILEGES的正确用法
权限给出去之后,回收使用REVOKE语句,语法结构与GRANT基本对称:
-- 回收某个账号对shop库的删除权限 REVOKE DELETE ON shop.* FROM 'webapp'@'192.168.1.%'; -- 彻底删除一个账号及其全部权限 DROP USER 'olduser'@'%'; -- 查看指定账号的当前权限 SHOW GRANTS FOR 'webapp'@'192.168.1.%';
关于FLUSH PRIVILEGES,网上很多教程把它当成必备步骤,其实这是误解。通过GRANT、REVOKE、CREATE USER、DROP USER这些语句修改权限时,服务器会自动同步更新内存缓存,并不需要手动刷新。只有当你直接用INSERT、UPDATE、DELETE去改mysql.user等底层权限表时(老版本MySQL的常见做法),才必须执行FLUSH PRIVILEGES让改动生效。日常运维中建议养成习惯:永远用GRANT体系语句管理权限,不直接操作权限表。
排查权限问题时,SHOW GRANTS是最常用的命令。如果遇到“命令被拒绝”的错误,先确认当前连接的用户身份(SELECT CURRENT_USER()),注意CURRENT_USER()返回的是权限验证时匹配的账号,而USER()返回的是客户端尝试连接时使用的账号,两者在主机匹配歧义时可能不同,这是定位授权问题的关键线索。
生产环境权限安全清单
最后整理一份可直接执行的加固清单。第一,禁止root账号远程登录,root只保留localhost连接,业务程序绝不使用root。第二,每个应用使用独立账号,账号命名与项目对应,方便审计追溯。第三,定期清理无用账号,可以用下面这条SQL找出存在安全隐患的记录:
-- 检查是否有匿名账号或任意主机可登录的账号
SELECT user, host FROM mysql.user
WHERE user = '' OR host = '%';
-- 检查使用弱密码插件的账号(MySQL 5.7+)
SELECT user, host, plugin FROM mysql.user
WHERE plugin NOT IN ('mysql_native_password', 'caching_sha2_password');
第四,敏感权限单独管控:FILE权限允许读写服务器文件,SUPER权限可以干预其他会话,PROCESS权限能查看所有会话的SQL文本,这三类权限除非明确需要,否则一律不给。第五,密码策略要配合账号管理,MySQL 5.7以后可以安装validate_password组件,强制要求密码长度和复杂度。
权限配置本质上是在可用性和安全性之间找平衡。原则很简单:默认不给权限,需要什么给什么,给的时候限定到最小范围——能限定到库就不给全局,能限定到表就不给整库,能限定到列就不给整表。按照这个思路逐步收紧,即使将来某个账号泄露,损失也能被控制在一个库甚至一张表的范围内,这比事后补救要划算得多。