很多个人站长的数据库里只有一个 root 账号,网站程序连数据库用 root,备份用 root,平时自己管理也懒得建新账号。这种做法短期看不出问题,可一旦网站程序被注入漏洞或者数据库被拖库,攻击者拿到的就是最高权限,整个服务器的数据都保不住。MySQL 的权限体系设计得很完善,只是大多数人没有认真研究过。这篇文章从用户创建、授权、最小权限原则到常见报错排查,把 MySQL 权限管理完整讲一遍,看完你就能给网站、备份、监控分别配上合适的专用账号。
一、理解 MySQL 的账号模型
MySQL 里的"用户"和我们平时理解的不太一样,它由两个部分组成:用户名和允许登录的主机,写成 用户名@主机 的形式,比如 root@localhost。主机部分可以是 localhost(本机)、具体 IP(192.168.1.10)、IP 段(192.168.1.%)或者任意主机(%)。两个账号用户名相同但主机不同,就是两个完全独立的账号,可以有不同的密码和权限。这一点非常重要:很多权限问题的根源就在于搞混了 root@localhost 和 root@'%',给其中一个改了密码,另一个却纹丝不动。
二、权限的五个层级
MySQL 的权限是分层的,从大到小依次是:全局权限(对整个服务器生效)、数据库权限(对某个库生效)、表权限(对某张表生效)、列权限(对某几列生效)和存储过程权限。权限粒度越细,管理越灵活,但实际使用中,个人站长用到数据库级和表级就足够了。所有授权信息都存在系统库 mysql 的几张表里:mysql.user 存全局权限,mysql.db 存数据库权限,mysql.tables_priv 存表权限。理解这一点对排查问题很有帮助,比如修改权限不生效时,可以直接查这些表看数据到底写没写进去。
三、创建用户的基本语法
创建用户的命令是 CREATE USER,可以同时指定密码和认证方式:
CREATE USER 'blog'@'localhost' IDENTIFIED BY '强密码';
CREATE USER 'blog'@'127.0.0.1' IDENTIFIED BY '强密码';
CREATE USER 'blog'@'192.168.1.%' IDENTIFIED BY '强密码';第一条只允许本机登录,第二条允许通过本机回环地址登录(很多程序用 127.0.0.1 连接数据库,需要单独建这个账号),第三条允许内网 192.168.1 网段的机器登录。主机部分不要随便写 %,能用具体地址就用具体地址,这是缩小攻击面的第一步。密码建议用 20 位以上的混合字符,不要和网站后台、服务器登录密码重复。
四、授权:GRANT 语句详解
创建用户之后要授权才能干活,授权用 GRANT 语句,基本格式是 GRANT 权限列表 ON 对象 TO 账号。举几个实际例子:
-- 网站账号:只给最基本的读写权限
GRANT SELECT, INSERT, UPDATE, DELETE ON blog_db.* TO 'blog'@'localhost';
-- 备份账号:允许锁表、查视图和导出
GRANT SELECT, LOCK TABLES, SHOW VIEW, PROCESS, RELOAD ON *.* TO 'backup'@'localhost';
-- 监控账号:只允许查看状态
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'monitor'@'localhost';注意几点:ON blog_db.* 表示 blog_db 库下的所有表,这是最常用的粒度;ON *.* 表示所有库,尽量少用;ON blog_db.users 可以精确到单张表。授予之后执行 FLUSH PRIVILEGES 刷新一下让权限立即生效。GRANT 也可以给不存在的用户直接授权,MySQL 会自动创建它,但为了清晰起见,我建议先 CREATE USER 再 GRANT,一步一步来。
五、最小权限原则实战
最小权限原则说起来就一句话:给每个账号只分配完成任务所必需的最小权限。落到实际场景就是:网站程序账号只需要 SELECT、INSERT、UPDATE、DELETE 四种权限,不要给 CREATE、ALTER、DROP,这样即使网站被注入,攻击者最多只能读写数据,不能删库改表,把损失控制在一个库的范围里。备份账号需要 SELECT、LOCK TABLES、SHOW VIEW 和 RELOAD,其中 RELOAD 是 mysqldump 做一致性备份时用到的,少了它备份可能报错或者备份出来的数据不一致。监控账号给 PROCESS 和 REPLICATION CLIENT 就够了,PROCESS 可以查看所有线程,REPLICATION CLIENT 可以查询主从状态。养成"每个账号只给够用的权限"的习惯,是数据库安全的第一步。
六、修改密码与权限回收
修改密码用 ALTER USER,这是 MySQL 5.7 之后推荐的方式:
ALTER USER 'blog'@'localhost' IDENTIFIED BY '新密码';权限回收用 REVOKE,语法和 GRANT 对称:
REVOKE DELETE ON blog_db.* FROM 'blog'@'localhost';删除账号用 DROP USER:
DROP USER 'blog'@'localhost';注意 DROP USER 必须同时指定用户名和主机,漏掉主机部分会报错。另外,定期轮换密码是低成本高收益的安全习惯,建议每半年换一次网站数据库密码,换完记得同步更新网站配置文件并重启 PHP-FPM 让新连接生效。
七、权限生效机制与连接数限制
这里补充两个容易踩坑的细节。第一是权限的生效机制:用 GRANT、REVOKE、ALTER USER 这些语句修改权限时,MySQL 会立即生效,不需要额外操作;但如果你直接往 mysql.user 表里 INSERT 或者 UPDATE 数据(比如用工具改密码),就必须执行 FLUSH PRIVILEGES 让 MySQL 重新加载权限表,否则改了等于没改。第二是认证插件的问题:MySQL 8.0 默认使用 caching_sha2_password 认证插件,而老版本的 PHP 客户端和部分旧程序只支持 mysql_native_password,连接时会报认证方式不支持的错误,解决办法是在创建用户时指定老插件:
CREATE USER 'blog'@'localhost' IDENTIFIED WITH mysql_native_password BY '强密码';第三是给账号设置最大连接数,防止某个应用出问题后把数据库连接池占满,拖垮整个数据库:
ALTER USER 'blog'@'localhost' WITH MAX_USER_CONNECTIONS 20;这三个细节平时不起眼,真遇到问题的时候能帮你少走不少弯路。
八、忘记 root 密码的应急处理
总有马虎的时候,root 密码忘了怎么办?MySQL 提供了一个应急通道:跳过权限表启动。先停掉 MySQL 服务,然后用下面的方式启动:
mysqld_safe --skip-grant-tables &或者修改配置文件 /etc/mysql/my.cnf,在 [mysqld] 段加一行 skip-grant-tables,重启服务。这时候任何客户端都能免密登录,赶紧执行 ALTER USER 把 root 密码改回来,然后务必删除配置文件里的那行并重启服务恢复正常模式。要特别提醒:这个模式等于把数据库大门敞开了,操作期间要保证服务器没有外网暴露或者立即完成修改,改完马上退出,不要让服务器长时间处于这种状态。
九、常见报错逐个排查
权限相关的报错就那几种,记住特征就能快速定位。ERROR 1045 Access denied for user:用户名密码错误,或者账号根本不存在,先用 mysql -u 用户名 -p 手动登录验证一遍密码是否正确,再检查账号的主机部分是否覆盖了当前连接来源。ERROR 1130 Host is not allowed to connect:账号存在但主机部分不允许当前来源连接,比如账号是 blog@localhost 却从远程机器连接,解决方法是把主机部分改成远程机器的 IP 或者建一个对应的远程账号。ERROR 1142 command denied:账号权限不够,执行了没被授予的操作,按需补充 GRANT 即可。ERROR 1396 Operation CREATE USER failed:要创建的账号已经存在,用 SELECT user, host FROM mysql.user WHERE user='用户名'; 查一下,确认后改用 ALTER USER 修改。
十、查看权限与权限审计
查看某个账号有什么权限,用 SHOW GRANTS:
SHOW GRANTS FOR 'blog'@'localhost';不带账号参数时显示当前账号的权限。想一次性看全所有账号,直接查系统表:
SELECT user, host, authentication_string FROM mysql.user;平时可以做一次权限审计:看看 mysql.user 表里有没有主机部分是 % 的高权限账号,有没有匿名账号(用户名为空),有没有长期不用的僵尸账号。Docker 部署 MySQL 时很多人习惯用 MYSQL_ROOT_PASSWORD 环境变量,初始化完建议立即创建一个专用业务账号,然后限制 root 只能本机登录。
十一、安全加固清单
最后给一份可以直接照着做的清单。第一,删除匿名用户:DELETE FROM mysql.user WHERE user='';。第二,禁止 root 远程登录:root 账号只保留 root@localhost,远程管理一律走 SSH 隧道或者用专用管理账号。第三,网站应用账号只给 SELECT、INSERT、UPDATE、DELETE。第四,数据库端口不要暴露公网,监听 127.0.0.1 即可,或者用防火墙限制来源 IP。第五,定期查看 mysql 的通用日志或者慢日志,留意异常的授权操作。第六,备份账号的密码单独保存,不要写进网站代码里。把这些做到位,即使网站代码出漏洞,数据库这层防线也还在。
十二、总结
MySQL 权限管理没有多高深,核心就是三件事:账号按"用户名加主机"来理解,权限按"够用就好"来授予,报错按"账号是否存在、主机是否匹配、权限是否足够"三步来排查。给网站、备份、监控分别建好专用账号,把 root 关进笼子里,你的数据库就从"裸奔"变成了"有防线"。下次再有人问为什么连接被拒绝,你也能一眼看出问题出在哪一层。