数据库账号权限很少被当成独立的安全边界来看待。很多团队在搭建环境时,为了快速跑通业务,常常直接把所有权限授予应用账号,例如执行一条类似GRANT ALL PRIVILEGES ON *.*的语句。短时间内确实省事,但等系统稳定后,这些账号仍然持有删除库表、读写其他业务库甚至管理用户的能力。一旦应用出现SQL注入、配置泄露或内部误操作,影响范围会被无限放大。GRANT和REVOKE语句的真正价值不在于能否授权和回收,而在于它们能否把权限压缩到刚好满足业务运行的程度,并为后续审计提供清晰依据。
本文围绕数据库权限配置中的常见场景,结合MySQL语法,说明如何通过精确的对象范围、主机限制和权限类型来收紧访问控制。随后会分析REVOKE在实际执行中容易失效的原因,并给出一个可落地的业务账号与运维账号分离配置示例。

GRANT语句的对象范围与主机限制
GRANT语句的授权范围由ON后面的对象层级决定。最宽泛的是*.*,它代表所有数据库的所有表;其次是db_name.*,只覆盖某个库的全部表;更细的是db_name.table_name,只针对单表;最细的可以到列级别,例如SELECT (order_id, customer_name),表示只能读取指定列。很多安全策略失败的根源,就是对象范围选得过大。一个只负责统计订单编号和客户名的报表任务,如果被授予app_db.*的SELECT权限,就能读取用户手机号、地址等敏感字段;如果只授予两个列的SELECT权限,即便这个账号被拖库,暴露的数据也会小得多。
主机限制同样不可忽视。MySQL中的账号由用户名和来源主机共同组成,'report_user'@'10.0.2.15'与'report_user'@'%'是两个不同账号。来源主机写%表示允许任意主机连接,这在生产环境几乎不应该出现。更合理的做法是,如果应用部署在固定网段,就写10.0.2.%;如果只有本机备份任务,就写127.0.0.1或localhost。主机部分支持百分号和下划线通配符,但要注意%可以匹配空串,因此'user'@'192.168.%'不仅覆盖C段,还可能匹配以192.168开头的其他主机形式,具体应结合MySQL文档与网络规划确认。
下面是一个创建只读账号的示例,它把权限压缩到指定库,并将来源限制在应用所在网段:
CREATE USER 'readonly_app'@'192.168.10.%' IDENTIFIED BY 'ChangeMe_Str0ng'; GRANT SELECT ON app_db.* TO 'readonly_app'@'192.168.10.%'; FLUSH PRIVILEGES;
这个账号只能对app_db库执行SELECT,不能写入、不能建表,也无法访问mysql系统库。对于报表服务、数据导出任务这类只读场景已经足够。实际配置时还应替换默认密码,避免使用常见弱口令。
GRANT OPTION与通配符带来的权限扩散风险
GRANT语句末尾可以附带WITH GRANT OPTION,表示被授权者能把当前权限再授予其他用户。这个选项非常危险,因为它会让权限管理出现多级扩散。例如一个开发主管被授予了app_db.*上的SELECT、INSERT、UPDATE权限,并附带GRANT OPTION,那么他就可以创建新用户,再把写权限转授给其他成员。安全管理员可能完全不知道这些子账号的存在,后续回收权限时会发现授权关系已经变得复杂,甚至出现某个人同时通过多个路径拥有同一权限的情况。
除非团队有自动化审批和授权平台,否则普通业务账号、开发测试账号都不应该带有GRANT OPTION。需要临时授权时,应由数据库管理员统一创建账号并控制范围。这样可以保持授权路径单一,回收和审计都能做到闭环。
与授权扩散类似的还有通配符误用。除了主机部分,MySQL在授权表中存储的对象名一般不是LIKE模式,通常数据库名、表名中的下划线不会被当作通配符处理,但不同版本和不同权限级别的行为可能存在差异。真正容易出问题的是主机名中的%。例如有人认为'ops_user'@'10.%'只匹配内网10网段,实际上它可能匹配10开头的任意字符串,具体匹配规则需要结合主机解析。因此更稳妥的写法是明确到子网段,例如'ops_user'@'10.0.10.%',而不是用宽泛的10.%或%。
下面演示一个运维账号的最小授权。它只被允许在指定库执行查询和备份相关操作,并且来源IP被严格限制:
CREATE USER 'ops_backup'@'10.0.10.8' IDENTIFIED BY 'B@ckup_P@ss'; GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES ON app_db.* TO 'ops_backup'@'10.0.10.8';
这里没有使用ALL PRIVILEGES,也没有开放任意主机。备份任务如果需要读取库表结构和触发器,就必须给SHOW VIEW和TRIGGER;如果使用mysqldump且涉及锁表,还需要LOCK TABLES。这些权限应当按需授予,而不是简单塞一个ALL。
REVOKE回收权限的细节与失效场景
REVOKE语句的基本格式是REVOKE privilege_list ON object FROM user。它和GRANT一样,回收时也必须指定一致的权限范围和对象层级。比如之前执行的是GRANT SELECT ON app_db.* TO 'readonly_app'@'192.168.10.%',如果回收时使用REVOKE SELECT ON *.* FROM 'readonly_app'@'192.168.10.%',MySQL不会因为*.*包含了app_db.*就自动移除那个权限,因为授权表里记录的是不同层级的独立条目。正确的做法是保持相同的app_db.*范围。
另一个常见误解是认为REVOKE可以删除用户或禁止登录。实际上REVOKE只会移除特定权限,用户本身仍然存在,并且可能还持有其他权限。如果只回收了库级SELECT,但用户还有全局USAGE权限或其他库的权限,应用连接后仍能登录,只是在原库执行SELECT时被拒绝。要彻底禁止登录,可以使用ALTER USER 'username'@'host' ACCOUNT LOCK,或者直接DROP USER 'username'@'host'。
执行完REVOKE后,必须通过SHOW GRANTS验证结果。例如:
REVOKE SELECT ON app_db.* FROM 'readonly_app'@'192.168.10.%'; SHOW GRANTS FOR 'readonly_app'@'192.168.10.%';
有时权限没有被回收,是因为账号在多个授权层级上都存在记录。例如全局有SELECT,库级也有SELECT,只回收库级后全局SELECT仍然生效。类似地,如果用户通过角色继承权限,单独REVOKE用户自身权限可能不影响通过角色获得的权限。因此排查权限残留时,要同时检查全局、库、表和列级授权,以及角色关系。
也可以在回收前先查看当前权限,确认是否存在多余的GRANT OPTION:
SHOW GRANTS FOR 'app_writer'@'10.0.1.%';
业务账号、只读账号与管理账号分离配置
一个较安全的数据库账号体系至少包含三类:业务读写账号、只读账号和备份或运维账号。它们之间不能混用。业务读写账号只授予应用所需库的INSERT、UPDATE、DELETE、SELECT,且通常不授予DDL权限;只读账号只授予SELECT;备份账号按备份工具需要授予SELECT、SHOW VIEW、TRIGGER、LOCK TABLES;管理账号则由DBA单独持有,不配置到业务服务器。
下面给出一个基于MySQL的完整示例,假设业务库名为app_db,应用网段为10.0.1.%,报表服务网段为10.0.2.%,备份主机为10.0.10.8:
-- 业务读写账号 CREATE USER 'app_writer'@'10.0.1.%' IDENTIFIED BY 'Wr!te_P@ss_App'; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_writer'@'10.0.1.%'; -- 报表只读账号 CREATE USER 'report_reader'@'10.0.2.%' IDENTIFIED BY 'Re@d_Only_App'; GRANT SELECT ON app_db.* TO 'report_reader'@'10.0.2.%'; -- 备份账号 CREATE USER 'backup_user'@'10.0.10.8' IDENTIFIED BY 'B@ckup_User_P@ss'; GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES ON app_db.* TO 'backup_user'@'10.0.10.8';
这些账号都遵循了最小权限原则:不做跨库授权,不开放任意主机,不授予DDL或管理权限。应用上线后如果需要对表结构变更,应由DBA使用单独的管理账号执行,而不是把ALTER权限交给应用。这样可以避免应用逻辑漏洞被用来直接删除表或修改字段。
日常审计可以定期检查用户列表和权限分布。以下查询可以快速查看当前存在的用户,过滤掉系统内置账号:
SELECT user, host FROM mysql.user WHERE user != 'mysql.sys' AND user != 'mysql.session' AND user != 'mysql.infoschema';
根据查询结果,再对异常账号使用SHOW GRANTS做进一步排查。安全配置不是一次到位的工作,新上线的服务、人员变动、临时活动都可能引入新的授权,只有把GRANT与REVOKE的粒度、范围、验证流程固定下来,才能持续降低权限失控风险。