我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

MySQL用户管理实战:权限分配、密码重置与远程连接排查

MySQL用户管理实战:权限分配、密码重置与远程连接排查 MySQL的用户管理是每个DBA和后端开发都绕不开的日常操作。很多人觉得不就是建个账号、给个权限嘛真到自己上手时才发现坑不少明明授权了整库应用却连不上root密码忘了只能干瞪眼主从复制账号给错权限导致同步中断还有那个把无数人拦在外面的 ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。这篇内容就围绕MySQL用户管理这个核心把用户创建、权限分配、密码处理、连接排查、主从复制账号配置这些实际操作完整拆解一遍适合刚接手MySQL运维的开发、刚入门DBA以及所有想搞明白“MySQL账号到底是怎么一回事”的朋友。1. 用户管理到底在管什么先建立整体认知1.1 用户与权限的本质别把MySQL账号当成普通登录名MySQL的用户管理核心不是“让谁能登录”而是“登录之后能干什么”。MySQL把这两件事放在了一起处理身份认证你是谁和权限授权你能做什么。如果你只创建了一个用户却什么权限都不给那这个账号除了连上服务器感叹一声“我进来了”什么都做不了。MySQL的用户信息存储在系统库mysql的user表里权限信息分散在db、tables_priv、columns_priv、procs_priv等表中。这意味着你通过SQL改用户、授权限本质上是在改这几张系统表。不过现实中没人会直接改表都是通过CREATE USER、GRANT、REVOKE这些SQL命令来操作系统会自动维护底层表。这里有个非常容易忽略的点MySQL里的用户不是单纯一个名字而是“用户名 主机”的组合。testlocalhost和test%是两个不同的用户可以拥有完全不同的密码和权限。很多人在本地测试没问题换到服务器上就连不上大概率就是搞混了这两个概念。你可以把testlocalhost理解为“只在服务器本机允许使用的账号”而test%是“允许从任意IP远程登录的账号”。生产环境里这两个账号应分开管理甚至是两个密码。1.2 真实场景中的用户管理需求结合我实际接触过的项目用户管理最常见的使用场景大致是这几类应用连接数据库JavaWeb项目、Python脚本、Node服务等连库账号通常只需要某个库或某几张表的增删改查权限。DBA/运维管理root管理员或拥有全局权限的运维账号负责建库、调优、查看所有数据。主从复制/高可用主库需要给从库一个专门的复制账号权限通常是REPLICATION SLAVE。数据同步/ETL把远程库的某张表同步到本地需要一个有SELECT权限的只读账号最好还限定源IP。日常开发/测试给开发同事一套临时账号只开放测试库权限避免误碰生产数据。这几种场景对权限的要求完全不一样。如果给应用账号开了全局SUPER权限等于把数据库的钥匙交出去了一旦应用被注入或者代码出问题后果很难收拾。所以理解用户管理核心就是要建立“最小权限”意识。2. 用户的增删改查命令行下的完整操作2.1 创建用户的完整姿势MySQL 5.7 和 8.0 在用户创建上有明显差异。5.7 及更早的版本允许直接GRANT ALL ON db.* TO userhost;系统会自动把用户建出来。MySQL 8.0 开始强制要求先CREATE USER再GRANT这是出于安全性的收紧——不再允许通过授权隐式创建用户。如果你在8.0里执行不带CREATE USER的GRANT会直接报错。创建用户的标准姿势CREATE USER app_user192.168.1.% IDENTIFIED BY StrongPass#2024;这条命令的意思创建一个用户app_user只允许从192.168.1.0/24这个网段登录密码是StrongPass#2024。这里的主机限制非常有用你可以在数据库层面就拦住一大波来自未知IP的恶意连接。MySQL 8.0 默认的密码认证插件是caching_sha2_password安全性更高。但这会带来一个兼容性问题如果你的应用用的驱动版本比较老比如5.x的某些客户端库可能无法用这个插件完成认证连接时报错Authentication plugin caching_sha2_password cannot be loaded。解决办法有两个升级驱动或者创建用户时指定老插件CREATE USER old_app% IDENTIFIED WITH mysql_native_password BY LegacyPass#2024;我自己在实践中倾向于优先升级驱动实在没法升级才用mysql_native_password毕竟安全标准是在不断提高的老插件总有一天会被彻底淘汰。2.2 修改与删除用户修改密码是用户管理里出现频率最高的操作。MySQL 5.7 用SET PASSWORD FOR userhost PASSWORD(新密码);8.0 里PASSWORD()函数被移除了正确做法是ALTER USER app_user192.168.1.% IDENTIFIED BY NewPass#2025;这里提醒一点改完密码后已有的连接不会立刻失效新连接才会使用新密码。所以很多人在修改密码后觉得“没生效”其实是被旧的长连接蒙蔽了。删除用户相对简单DROP USER test_userlocalhost;如果不知道用户名对应的主机可以先查再删SELECT user, host FROM mysql.user;2.3 一个常用的用户信息查询清单下面这些查询是我日常排查用户问题时几乎必用的-- 查看所有用户及其认证插件 SELECT user, host, plugin FROM mysql.user; -- 查看某个用户的全局权限 SHOW GRANTS FOR app_user192.168.1.%; -- 查看当前登录用户是谁、有哪些权限 SELECT CURRENT_USER(); SHOW GRANTS; -- 查看库里有哪些对象授权8.0用 SELECT * FROM mysql.global_grants WHERE user app_user;SHOW GRANTS输出里第一行经常会有一条GRANT USAGE ON *.* TO ...这不是bugUSAGE权限代表“只有登录能力没有任何实际权限”是所有用户的默认状态。别看到USAGE就以为账号有全局权限。3. 授权与回收把权限粒度拿捏准3.1 GRANT与REVOKE的完整解析GRANT的基本语法是GRANT 权限列表 ON 对象 TO userhost [WITH GRANT OPTION];权限列表可以是具体的SELECT、INSERT、UPDATE、DELETE也可以是粗粒度的ALL PRIVILEGES。对象可以是*.*全局所有库表、db_name.*某个库下的所有对象、db_name.table_name单张表或者存储过程等。常见的几类授权实操-- 只读账号最常用比如给报表系统、数据同步用 GRANT SELECT ON report_db.* TO readonly%; -- 读写账号应用账号常用 GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO app_rw10.10.%; -- DDL权限开发需要改表结构时临时给 GRANT CREATE, ALTER, DROP, INDEX ON app_db.* TO dev10.10.%; -- 全局管理权限谨慎使用 GRANT ALL PRIVILEGES ON *.* TO dbalocalhost WITH GRANT OPTION;注意WITH GRANT OPTION这表示该用户可以把它的权限再转授给其他用户。给应用账号开这个权限等于开了个口子万一代码有SQL注入漏洞攻击者就能直接创建后门账号风险极高。生产环境给业务账号一律不要加这个选项。回收权限用REVOKEREVOKE DELETE ON app_db.* FROM app_rw10.10.%; REVOKE ALL PRIVILEGES ON *.* FROM temp_user%;改完权限后如果该用户已有连接需要重新连接才能拿到新权限。3.2 MySQL的权限层级从全局到单行MySQL的权限分四个层级理解这个层级对排查“为什么我还是没权限”至关重要层级授权方式作用范围全局层ON *.*所有库的所有对象库层ON db.*某个库的全部对象表层ON db.tbl单张表列层/过程层ON db.tbl (col1, col2)/ON PROCEDURE db.proc单列或单个存储过程权限的合并逻辑是“并集”——一个用户最终的权限是所有层级授权的叠加。判断问题时注意不是说你只给他授权了表层权限他就真的只能操作这张表如果之前不小心给过他库层或全局权限他依然能访问整个库。举一个真实踩坑案例某项目给开发账号开了ALL ON dev_db.*同时又想限制他不能删某张关键配置表于是执行了REVOKE DELETE ON dev_db.config_tbl FROM dev%。结果发现开发依然能删除这张表的数据。原因就是库层权限包含DELETE表级REVOKE无法盖住库级GRANT。遇到这种情况正确的做法是把库层的具体权限重新精确定义而不是笼统地给ALL。3.3 权限验证逻辑MySQL是怎么判断你有没有权限的每次执行SQL时MySQL按“全局 → 库 → 表 → 列”的顺序检查权限。只要在当前层级找到了明确授权就不再往下查。这是一个高效的短路设计但也带来了前面说的权限“只增不减”问题高层的授权永远覆盖低层的拒绝。想验证一个用户真正拥有哪些有效权限不要凭记忆拼凑直接连上用该账号执行SHOW GRANTS或者在其登录状态下逐步执行操作来实测。我常用的办法是-- 切到目标账号后查看自己 SHOW GRANTS; -- 模拟执行某操作看是否报权限错误 SELECT * FROM db_name.table_name LIMIT 1; DELETE FROM db_name.table_name WHERE 10;用DELETE ... WHERE 10这种无害方式实测删除权限是非常稳妥的验证手段。3.4 角色MySQL 8.0的多对多权限管理MySQL 8.0 引入了角色Role相当于把一组权限打包。比如你有五个开发都需要测试库的读写权限不用给每个人逐个GRANT建一个角色然后一次性授予五个人CREATE ROLE dev_rw; GRANT SELECT, INSERT, UPDATE, DELETE ON test_db.* TO dev_rw; GRANT dev_rw TO dev1%, dev2%, dev3%;默认情况下已激活的角色在登录后需要SET ROLE dev_rw;才能生效也可以设置SET DEFAULT ROLE让它在登录时自动生效ALTER USER dev1% DEFAULT ROLE dev_rw;角色的好处是集中管理人员变动时只需要改角色的授权所有成员的权限同步更新。如果你还在逐个用户改权限建议趁早切换到角色方案尤其当团队规模超过几人的时候效率差异非常明显。4. 密码管理与连接问题排查实操中绕不开的坎4.1 忘记root密码怎么办跳过授权表重置密码这是运维事故里最常见的一幕。流程不复杂但操作时机要拿捏准。5.7和8.0做法略有差异以8.0为例# 1. 停止MySQL服务 systemctl stop mysqld # 2. 以跳过授权表的方式启动8.0写法 mysqld_safe --skip-grant-tables --skip-networking # 3. 直接免密登录 mysql -uroot # 4. 刷新权限表让密码相关操作可用 FLUSH PRIVILEGES; # 5. 重置root密码 ALTER USER rootlocalhost IDENTIFIED BY NewRootPass#2024; # 6. 退出并重启MySQL exit systemctl restart mysqld注意事项--skip-networking非常重要这样启动的MySQL只允许本机socket连接不允许TCP/IP连接避免在免密状态下被外部攻破。整个过程要尽量快操作完立刻恢复正常启动方式。5.7及以前版本使用SET PASSWORD FOR rootlocalhost PASSWORD(新密码);。这里多说一句MySQL 5.7安装时会生成一个临时密码放在/var/log/mysqld.log里很多人第一次登录时被这个“临时密码”卡住。用grep temporary password /var/log/mysqld.log就能找到登录后必须立即改密才能继续操作。4.2 ERROR 2002 (HY000)Cant connect through socket 的排查思路这个报错几乎人人都会遇到原因通常是客户端默认用socket文件连接但socket文件不存在或者MySQL没在运行。排查步骤如下# 1. 确认MySQL进程是否在运行 systemctl status mysqld ps -ef | grep mysqld # 2. 查看socket文件是否存在默认在 /tmp/mysql.sock 或 /var/lib/mysql/mysql.sock ls -l /tmp/mysql.sock /var/lib/mysql/mysql.sock # 3. 如果文件不在查看MySQL配置确认socket路径 grep -r socket /etc/my.cnf /etc/mysql/ 2/dev/null # 4. 用显式的socket路径连接 mysql -uroot -p -S /var/lib/mysql/mysql.sock # 5. 如果只想用TCP连接指定-h mysql -uroot -p -h 127.0.0.1 -P 3306实际中最常见的情况MySQL服务没起来或者被SELinux/权限问题挡住了导致socket文件没生成。还有一个坑是/tmp目录被清理了socket文件丢失但服务还在运行这种情况下重启MySQL服务即可重新生成socket文件。4.3 远程连接失败的经典排查路径本地能连、远程连不上是MySQL用户管理里另一个高频问题。按照这个顺序排查基本能定位90%的问题第一步确认账号允许的登录主机SELECT user, host FROM mysql.user WHERE user 目标用户;如果host是localhost自然远程连不上需要改成%或指定网段。这里注意改了host要重新确认授权是否也跟着匹配。第二步用明确的TCP方式测试连接mysql -uapp_user -p -h 服务器IP -P 3306如果报Access denied说明用户名密码或者host匹配有问题如果报Cant connect to MySQL server (111)说明端口不通需要检查防火墙和MySQL的bind-address。第三步检查绑定地址。MySQL默认只监听本机如果要远程连接/etc/my.cnf里要设置bind-address 0.0.0.0改完重启MySQL。注意0.0.0.0表示监听所有网卡如果只允许内网访问更稳妥的做法是绑定内网IP避免把数据库暴露到公网。第四步防火墙放行3306端口firewall-cmd --permanent --add-port3306/tcp firewall-cmd --reload4.4 认证插件不匹配报错与解决新手在配置完MySQL 8.0后用老版本Navicat连接时经常报Client does not support authentication protocol requested by server。这就是前面说的认证插件差异。MySQL 8.0默认caching_sha2_password老客户端不支持。解决方案-- 修改已有用户为老认证插件 ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY 密码;但如果你的应用能够升级驱动我强烈建议升级驱动而不是降级插件。比如Java的mysql-connector-java8.0.11以上、Python的pymysql1.0以上都已经支持新插件升级成本远低于长期兼容老插件的风险。4.5 锁表与用户权限的微妙关系排错时经常发现一个现象一个用户明明有UPDATE权限执行更新时却卡住不动这不是权限问题而是行锁或表锁等待。比如另一会话正在修改同一行且未提交你的UPDATE就会一直等。排查方法-- 查看锁等待情况 SHOW PROCESSLIST; -- 查看事务和锁信息8.0 SELECT * FROM performance_schema.data_lock_waits;这种情况和用户管理没有直接关系但很多人在排查用户问题时会被误导。记住权限报错通常快速失败卡住不动则多半是锁或性能问题两者可以快速区分。5. 典型场景主从复制账号与多环境用户配置5.1 主从复制账号的精确配置配置MySQL主从复制时需要专门创建复制账号。给这个账号的权限应当尽可能精简只保留复制所需的最小权限CREATE USER repl10.10.%.% IDENTIFIED BY ReplPass#2024; GRANT REPLICATION SLAVE ON *.* TO repl10.10.%.%;REPLICATION SLAVE权限只用于从库拉取主库的binlog不需要SELECT、不需要ALL。给复制账号开过多权限是一种常见的安全疏漏因为复制账号往往会在多台机器之间配置一旦泄露影响面会放大。配置从库时CHANGE MASTER TO语句中填写的账号必须是这个精确的repl10.10.%.%注意从库连接主库时MySQL会按从库的源IP去匹配host字段所以如果从库IP不在授权网段内复制会报Access denied。定位这个问题时可以先用手动方式在从库上登录一次主库的账号测试mysql -urepl -h主库IP -P3306 -p如果能登录说明账号和网络没问题如果报错重点检查host匹配和防火墙。实际工作中见过太多次复制中断后DBA忙着查binlog位置结果发现只是复制账号被谁顺手改掉了或者主机IP变了。5.2 数据同步场景只读账号的配置“把远程库的这张表同步到本地”是个非常典型的需求。远程库给一个只读账号本地通过某种同步工具比如mysqldump或SELECT ... INTO OUTFILE来拉取。配置非常简单CREATE USER sync_ro本地IP IDENTIFIED BY SyncRead#2024; GRANT SELECT ON source_db.target_table TO sync_ro本地IP; -- 如果表结构也要同步需要SHOW VIEW权限 GRANT SHOW VIEW ON source_db.target_table TO sync_ro本地IP;这里有一个重要的小细节如果同步工具需要读取表结构信息除了SELECT通常还需要SHOW VIEW权限如果表上有触发器或者事件可能还需要额外授权。排查“没有权限查看该表”时可以用SHOW GRANTS核对一遍。5.3 Docker部署MySQL时的用户管理坑用Docker跑MySQL的人越来越多有个细节很容易被忽略容器里的默认root账号与宿主机网络的关系。Docker部署MySQL时如果想把数据持久化通常要挂载卷创建用户的方式一般是在容器初始化脚本里用环境变量MYSQL_USER、MYSQL_PASSWORD、MYSQL_DATABASE或者进入容器后手动执行SQLdocker exec -it mysql8 mysql -uroot -p CREATE USER app% IDENTIFIED BY AppPass#2024; GRANT ALL ON app_db.* TO app%;但在容器场景里%和localhost的边界要重新理解从宿主机连接容器MySQL时连接来源IP是Docker网桥IP比如172.17.0.1不是localhost也不是你的内网IP。所以只给applocalhost授权就会导致宿主机连不上容器。很多人第一次用Docker MySQL时都栽在这个host匹配上。容器里还有一个问题MySQL的配置文件路径、日志路径和普通安装不同改bind-address时要确认my.cnf的实际加载顺序。我自己习惯在启动容器时通过--default-authentication-pluginmysql_native_password或挂载自定义配置文件来统一控制避免进容器后到处找文件。6. 安全加固与日常运维建议6.1 用户权限的安全基线根据我个人运维经验MySQL用户管理至少要做到这几条底线第一禁止root远程登录。root只允许本机socket连接远程管理使用单独的dba%账号并严格授权。即使DBA账号泄露攻击者也拿不到root的全局操作权限。第二所有账号都要有明确的主机白名单。能写具体IP就不写网段能写网段就不写%。当然内部网络用网段比较务实但要坚决杜绝业务账号用%这种全开放写法。第三应用账号的权限遵循“最小必要”。只给需要的库、需要的表、需要的操作类型。不带WITH GRANT OPTION不用ALL PRIVILEGES。第四定期做账号审计。每季度过一遍mysql.user确认没有多余的僵尸账号、没有密码过期却未处理的账号。我见过不少公司的测试账号在线上跑了两年密码从来没变过这是很大的隐患。6.2 用户管理命令速查表操作MySQL 5.7写法MySQL 8.0写法创建用户CREATE USER uh IDENTIFIED BY p;同5.7授权GRANT SELECT ON db.* TO uh;需先CREATE USER再GRANT查看权限SHOW GRANTS FOR uh;同5.7改密码SET PASSWORD FOR uh PASSWORD(新p);ALTER USER uh IDENTIFIED BY 新p;删除用户DROP USER uh;同5.7锁定/解锁用户ALTER USER uh ACCOUNT LOCK;同5.7这个表可以直接抄下来贴在手边。实际遇到问题时5.7和8.0的语法差异常常是低级错误的重灾区特别是已经习惯老写法的人升到8.0后容易在旧语法上吃瘪。6.3 基于个人经验的最后分享做MySQL用户管理这些年我最大的体会是大部分连接问题、权限问题、安全问题根源都在“定义不够精确”上。u%太宽泛就带来安全风险ulocalhost太窄就让应用连不上授权给ALL就失控逐一REVOKE又可能互相覆盖。精确到主机、精确到库表、精确到具体操作类型前期多花两分钟后期能省下两天排查时间。另外一个小技巧任何一次用户变更无论是授权、改密还是删除都要记录在案。我习惯把每次变更的命令、原因、变更时间登记到运维文档里。生产环境出问题时这份记录能帮你快速定位“是谁在什么时候动了哪个账号”而不是对着SHOW GRANTS的输出硬猜。密码策略方面建议开启validate_password组件强制密码长度和复杂度。默认情况下MySQL只要求长度实际生产环境建议密码至少12位并包含大小写字母、数字和特殊符号这能过滤掉一大半弱口令风险。最后再分享一个细节做任何用户权限变更后先不要急着通知业务方自己先用一个最小权限的客户端连接验证一下。用刚创建的用户执行它应该能执行的操作再执行它不应该能执行的操作确认边界真实存在。这一步不需要花多少时间但能把“差不多行了”变成“确定没问题”。
返回列表