
装完MySQL之后第一件事应该做什么不是把root密码一改就万事大吉而是立刻把用户体系梳理清楚。我见过太多项目把root账号直接丢给业务代码用也见过不少刚入行的同学卡在CREATE USER这条命令上死活创建不出能连上的账号——要么报错语法不对要么授权完还是Access denied。这个基础里其实藏着不少坑今天就把MySQL创建用户的完整逻辑、实操命令和排错思路一次讲透适合刚装完MySQL不知道下一步干什么的运维也适合写代码时总被数据库连不上折腾的后端。1. 先搞懂用户怎么被认出来的userhost双层身份很多人对MySQL用户的理解停留在用户名密码所以创建用户时只想着CREATE USER u1 IDENTIFIED BY pwd却忽略了MySQL判断用户身份用的其实是两个维度user和host。MySQL官方文档里把用户写成账户名主机名这个格式在创建授权和排错时是绕不开的。1.1 host字段为什么这么重要host字段表示允许从哪个客户端IP或主机名登录。同样的用户名配不同的host完全就是两个独立账号。比如CREATE USER demolocalhost IDENTIFIED BY pwd123; CREATE USER demo192.168.1.% IDENTIFIED BY pwd456;这两条命令创建的是两个不同的账号虽然用户名都叫demo但密码、权限、登录来源彼此完全独立。localhost那个只能从MySQL服务器本机连进来192.168.1.%那个允许从192.168.1网段的机器远程连。如果你在本机登录时只匹配到localhost账号就算远程账号密码再怎么对用错端口或跳过匹配规则照样连不上。生活里可以这样理解用户名相当于你的名字host相当于你登记的住址。银行开户时身份证号姓名才是唯一标识MySQL里userhost才是唯一标识。1.2 权限表的前世今生user、db、tables_priv、columns_privMySQL的账号权限是分层次存储的创建了用户接下来要搞清楚它会被哪些表约束。系统库里最核心的几张权限表是权限表权限级别说明mysql.user全局权限只要在表里出现的权限对所有库生效mysql.db库级权限限定某用户对某个库的操作权限mysql.tables_priv表级权限限定某用户对某张表的权限mysql.columns_priv列级权限精确到字段级别的权限mysql.procs_priv存储过程权限对存储过程、函数的执行权限判断一个请求能不能执行时MySQL会先从mysql.user看有没有全局权限然后查mysql.db再到mysql.tables_priv最后才是mysql.columns_priv。权限越靠前优先级越高全局授权后其他表里的限制对它作用就非常小了。这个机制直接解释了开发过程中常见的诡异现象你在全局把UPDATE权限给了用户但某张表还是报权限不足或者反过来你只给了单表权限结果用户能看到的数据库列表依然空空如也。美团这类大厂里通常按库和表精确授权避免全局撒网。1.3 host匹配的隐藏排序规则MySQL在客户端发起连接后会按一定顺序去mysql.user表里匹配记录。localhost、127.0.0.1、::1、主机名、无通配符的IP地址这些精确记录会被优先匹配之后才轮到包涵通配符的记录比如192.168.1.%、%。这个匹配顺序会带来一个很经典的坑同一用户名下同时存在demolocalhost和demo%两个账号时在本机通过socket或localhost连接会命中前者通过IP连接会命中后者。如果你不小心给前者设置了错误密码后者密码正确反过来连127.0.0.1就成功连localhost就失败特别容易把人绕晕。理解了这一层后面排查Access denied时你就会知道先看匹配的是哪个host记录再核对密码而不是盯着CREATE USER语句反复发呆。2. 一条CREATE USER命令能做的事真正理解语法逻辑之后CREATE USER其实非常简单难点在于写对选项和夺回细节控制权。CREATE USER [IF NOT EXISTS] 用户名host IDENTIFIED BY 初始密码 [WITH mysql_native_password / caching_sha2_password] [REQUIRE SSL] [PASSWORD EXPIRE INTERVAL 90 DAY];IF NOT EXISTS会在用户已存在时给出告警而不是直接报错脚本里重跑很实用。IDENTIFIED BY指定认证密码密码会以哈希形式存到mysql.user表的authentication_string字段没人能反向看到明文。WITH子句可以指定认证插件。MySQL 8.0默认使用caching_sha2_password安全性更高但某些老客户端和旧版中间件不认识它。如果被老工具困扰需要在创建时单独指定mysql_native_password。这个细节我已经数不清帮多少人解决过Navicat突然连不上的问题。关于host的写法常见的模板是-- 本机维护账号 CREATE USER adminlocalhost IDENTIFIED BY Tp2024#Xy; -- 指定网段的应用账号 CREATE USER app192.168.1.% IDENTIFIED BY App2024#Secure; -- 所有来源可登录能不用就尽量别用 CREATE USER ops% IDENTIFIED BY Ops2024#Secure;生产环境里%我建议少用甚至禁用业务账号一律按服务器网段限制运维账号按办公室出口IP限制。host写得太宽等于是把数据库大门钥匙复制了好几把出问题时攻击面也大。允许范围越小登录出问题时反而越好排查——不然你连日志里那一串来源IP都不知道该信谁。如果你想创建的用户需要跨多个来源登录可以创建多个同用户名账号分别配不同host比如backup10.0.0.%和backuplocalhost。两张记录互不干扰一个用于远程备份服务一个用于本机脚本。MySQL 8.0.16起还支持CREATE USER ... PASSWORD EXPIRE强制用户在指定天数后改密CREATE USER templocalhost IDENTIFIED BY Temp2024#Xy PASSWORD EXPIRE INTERVAL 30 DAY;这个在做临时账号和外包协作场景时特别有用。30天一到不换密码就登录不了到期后帮你省掉一堆怎么还不删除临时账号的催收电话。3. GRANT授权的新手盲区用户创建出来只是一个空壳不给权限什么都干不了。授权最常见的命令是GRANT但也是踩坑最密集的地方。3.1 授权语法和权限级别GRANT 权限列表 ON 权限级别对象 TO 用户host;权限列表可以是一个权限也可以是逗号分隔的多个权限比如SELECT, INSERT, UPDATE, DELETE。最常用的几种写法-- 全局级别能管理所有库 GRANT ALL PRIVILEGES ON *.* TO adminlocalhost; -- 库级别管理某个库 GRANT ALL PRIVILEGES ON mydb.* TO app192.168.1.%; -- 表级别只读某张表 GRANT SELECT ON mydb.orders TO analyst%; -- 列级别只能看某些列 GRANT SELECT (name, email) ON mydb.customers TO analyst%;3.2 最小权限原则怎么落地讲道理谁都会实际授权时新手最常见的操作是给ALL PRIVILEGES一键到位。我自己的建议是业务账号永远不给ALL根据实际工作拆开给。账号类型推荐授权使用场景只读报表账号SELECTBI报表、数据分析、定时导出业务读写账号SELECT, INSERT, UPDATE, DELETE后端业务服务管理维护账号ALL PRIVILEGESDBA / 本机运维操作备份账号SELECT, LOCK TABLES, SHOW VIEW逻辑备份、物理备份前的锁表有人会问后端服务要跑事务和联表查询只给CRUD够吗够。如果你在服务里执行CREATE TABLE、DROP TABLE之类的DDL那说明设计架构有问题——建表应该走发布流程而不是业务代码顺手执行。有个我踩过的坑刚接手项目时图省事给读写账号加了GRANT ALL ON mydb.*结果有一次线上排查误删了一张日志表万幸有备份。从那以后所有业务账号我都严格收敛为CRUDDDL权限走专门的高权限账号。3.3 5.7到8.0的隐式创建行为变化这是很多旧教程挖的坑在MySQL 5.7及之前版本你可以直接用GRANT SELECT ON mydb.* TO xiaoming% IDENTIFIED BY somepassword;它允许GRANT连带创建用户还不限制必须有CREATE USER权限。但在MySQL 8.0之后这个用法直接被禁用——必须先CREATE USER再GRANT否则直接语法报错。所以如果你搜到的是老资料或者习惯了5.7的写法迁移到8.0后就会卡在这明明一模一样的两条命令在5.7跑得好好的在8.0却报错。解决办法很简单就是补一条CREATE USERCREATE USER xiaoming% IDENTIFIED BY somepassword; GRANT SELECT ON mydb.* TO xiaoming%;3.4 FLUSH PRIVILEGES到底什么时候要执行可能你在很多教程里见过授权后要加一句FLUSH PRIVILEGES;但实际上这句命令不是每次都要执行。它存在的意义是让MySQL重新读取授权表仅在直接操作mysql.user、mysql.db这些系统表时才有必要。通过标准的CREATE USER、GRANT、REVOKE语句修改权限MySQL会自动生效不需要FLUSH。老运维的习惯来自于早期一些版本直接改表后必须刷新的操作经验沿用至今。日常授权流程里加上它没有什么危害却容易让你误以为授权有问题时全靠FLUSH解决反而忽略了真正的授权语句错误。3.5 REVOKE回收权限的正确姿势权限给出去改完后一门心思收藏结果回收时又出错。REVOKE的纯粹和GRANT对应REVOKE INSERT, UPDATE ON mydb.* FROM app192.168.1.%; REVOKE ALL PRIVILEGES ON mydb.* FROM app192.168.1.%;但要讲清楚REVOKE删除的是授权权限不会删除用户本身。想连用户带权限一起清理要用DROP USER。权限回收后已建立的连接仍然持有之前的权限直到连接断开或重启对于在线回收连接权限的需求需要KILL相关连接进程这在实际生产操作时经常被忽略。我当时处理过一个报表账号需求先给了它临时ALL PRIVILEGES几天后改成了SELECT结果报表服务一直没重连还把UPDATE跑通了看起来像是权限没撤回。重启进程后一切恢复正常。4. 连接不上的时候九个排查方向创建了用户、授权也做了然后拿Navicat一连接——啊又报错了。这类问题占了数据库日常排错的半壁江山我把高频坑全部列出方便你直接按图索骥。4.1 Access denied for user这个错误最能说明host匹配问题。完整报错通常长这样ERROR 1045 (28000): Access denied for user demo192.168.1.50 (using password: YES)关键信息是报错里显示的来源host是你客户端的真实IP而不是你写错的那个host通配符。如果报错里出现的IP不在你创建用户的host范围内那就说明MySQL根本没有匹配到该账号。按三步排查# 1. 在MySQL服务器上查看账号和host SELECT user, host, authentication_string FROM mysql.user WHERE userdemo; # 2. 确认当前客户端实际来源IP -- 登录后执行 SELECT CURRENT_USER(); SELECT SUBSTRING_INDEX(HOST, :, 1) AS client_ip FROM information_schema.processlist WHERE IDCONNECTION_ID();如果CURRENT_USER()返回的host和你建立的账号host对不上就是匹配问题。干脆创建两条覆盖所需来源的账号或者收敛所有业务连接到一个统一的host段。4.2 caching_sha2_password带来的客户端兼容问题MySQL 8.0把默认认证插件改成了caching_sha2_password。如果你的Navicat版本较旧、驱动太老或连接池中间件不支持这个插件即使密码完全正确也会报认证失败。解决方案有三种升级客户端驱动到支持caching_sha2_password的版本长期最优。临时把账号认证方式改回mysql_native_password兼容旧客户端ALTER USER demo% IDENTIFIED WITH mysql_native_password BY Demo2024#Xy;创建用户时就指定老认证插件CREATE USER demo% IDENTIFIED WITH mysql_native_password BY Demo2024#Xy;只提醒一点不要把整个mysqld的默认认证插件都改掉不然新账号默认都走老插件安全性会拉低。4.3 密码里的特殊字符和大小写密码含!、、#、%这类字符时如果写在脚本或连接串里没做转义会出现一个让你怀疑人生的结果MySQL里存的密码对程序里连不上。用命令行连接时和程序连接时对特殊字符的解释规则不同。Shell命令行里单引号包住连接串能规避大半问题但程序代码里还会涉及URL编码。经验做法业务系统连接账号的密码尽量避免使用需要转义的高风险字符否则光在配置文件里折腾转义就能耗掉半天。比如Demo2024#Xy这种中等强度的密码就够用了不必非得上!#$%^*全家桶。4.4 建了账号但是授权还没生效很多新手在一个会话里建用户、授权然后在另一个会话里测试连接发现没有权限。这不是授权没生效而是你测试用的会话在用户创建之前就已经完成了身份验证。重新建立连接后再试即可。还有一种类似情况是改了账号host比如从demolocalhost改成demo%已建立的旧连接依然按老host权限运行需要重启连接或杀掉对应线程。4.5 bind-address限制导致远程连不上这个和用户创建关系不大但实在太多人栽在这MySQL默认监听地址可能是127.0.0.1只允许本机连接。你的账号host写了%也没用因为服务器从TCP层就把外部连接拒绝了。-- 查看监听地址 SHOW VARIABLES LIKE bind_address;如果值是127.0.0.1需要改配置文件my.cnf的bind-address 0.0.0.0或者指定内网IP然后重启MySQL。改了监听后记得同步防火墙放行3306端口iptables/安全组/ufw三层都要检查我见过只改了配置文件但被云安全组挡着连不上的。4.6 skip-name-resolve带来的host匹配异常skip_name_resolve开启时MySQL不反向解析客户端域名这个机制本身无害但会连带影响权限匹配——你创建用户时写的host如果用的是主机名而非IP匹配就失效。开了这个参数后host字段里的myhost这种写法就形同虚设必须写IP地址或通配符。如果你遇到过本机localhost能连局域网IP连不上但账号host明明写了%查一查skip_name_resolve和mysql.user里的host写法大概率是主机名和IP两种风格混着用了。4.7 连接串把host写成本机名而非IP连接时用的是socket还是TCP/IP很多初学者分不清。MySQL客户端连接localhost时默认走Unix socketLinux下为/tmp/mysql.sock连接127.0.0.1时才走TCP/IP 3306端口。用户host只授权给demolocalhost时你用mysql -h 127.0.0.1连接会命中另一条匹配规则。一个比较省心的做法应用和MySQL在同一台机器时就统一用127.0.0.1连接配合授权app127.0.0.1注意不要用localhost和127.0.0.1混着配分分钟怀疑是密码错了。4.8 SSL连接和useSSL参数MySQL 8.0默认也开启了REQUIRE SSL的选项但实际生产环境大量使用非SSL连接。如果创建用户时使用了REQUIRE SSL语句应用连接串里必须显式开启SSL否则连接直接被拒绝。Java的JDBC连接串里有一个高频坑useSSLfalse和sslmodeDISABLED各自代表不同版本的参数体系。MySQL Connector/J 8.x版本的sslmode换成DISABLED老写法是useSSLfalse如果两边参数不一致会出现服务端是8.0要求SSL客户端老代码没正确配置SSL参数这种连环炸。我维护过一个Java项目升级驱动版本后突然报SSL握手失败改一行sslmodeDISABLED就恢复了。这类问题表面上是数据库连接失败根子上其实是MySQL 8.0安全策略升级导致的兼容性问题。4.9 授权范围过窄导致数据库列表为空有时候用户能连上MySQL但Navicat左侧看不到任何数据库或只能看到一个系统库。这不是连接失败纯属MySQL对SHOW DATABASES返回内容的过滤用户对某库没有任何权限时该库根本不出现在列表中。如果是你刚授权的库没显示检查授权是否漏了该库或者二度连接一下有些GUI工具缓存了数据库列表。如果确实没权限就该补授权GRANT USAGE ON mydb.* TO demo%;USAGE这个权限本身不带任何操作能力但能让用户看得到这个库这对需要允许用户浏览库结构但不给改动权限的场景很有用。5. 后续管理改密、锁号、删号一个都不能少创建用户只是开始。MySQL用户的生命周期管理里改密码、锁定异常账号、精准删除这三件事是运维和开发都要会的。5.1 ALTER USER改密码与改认证方式改密码最常用的语句是ALTER USER demo% IDENTIFIED BY NewPwd2024#Secure;5.7.6之前的旧版本写法是SET PASSWORD FOR现在ALTER USER是标准姿势。批量改密时可以配合脚本循环处理多个账号但注意密码策略——MySQL默认会校验密码强度如果密码太弱会被validate_password插件弹回。如果一个账号的认证方式不对也可以直接改ALTER USER demo% IDENTIFIED WITH caching_sha2_password BY NewPwd2024#Secure;5.2 锁定与解锁账号员工离职、外包项目结束、疑似账号被盗临时锁号是好选择而不是立刻删除。-- 锁定 ALTER USER demo% ACCOUNT LOCK; -- 解锁 ALTER USER demo% ACCOUNT UNLOCK;锁定的账号尝试登录时返回错误ERROR 3118 (HY000): Access denied for user demo%. Account is locked.已经建立的连接不受锁定影响需要额外KILL断开敏感会话。5.3 RENAME USER的适用场景用户名变更保持权限不变时RENAME USER oldname% TO newname%;注意host也可以一起改。这个命令在账号迁移时非常顺滑——先建新账号迁移数据等业务切换后删除旧账号比直接改账号风险低很多。5.4 DROP USER删除账号DROP USER demo%; DROP USER IF EXISTS demo%;MySQL 8.0后DROP USER不会自动回收该账号对其他库对象的授权而是把它归到匿名用户。所以删除前最好先查一遍SHOW GRANTS确定没有残留对象依赖。这里我踩过一次坑删了一个报表账号结果一张存储过程里还绑定它的DEFINER后面调用全报错只能临时重建账号才恢复。5.5 SHOW GRANTS查权限一击必中SHOW GRANTS FOR demo%;返回结果里每个授权逐条列出权限排查第一步就靠它。想查看当前登录用户自己的权限就用SHOW GRANTS FOR CURRENT_USER();避免猜错host写错查看对象。5.6 安全基线给用户体系定规矩最后给一份我个人在团队里强制执行的用户安全清单供你参考所有业务账号不给全局ALL只给所属库的CRUD。远程管理账号一律REQUIRE SSL从明文网络上掐死密码泄露面。每90天强制轮换密码业务账号用发布流程统一改密并重启服务。任何账号权限变更后复核一遍SHOW GRANTS并在变更记录里更新。临时账号明确到期时间用PASSWORD EXPIRE做硬约束。离职/转岗人员涉及的账号锁定后保留15天再删除方便追溯历史操作。账号名和用途挂勾app_前缀、ro_只读、etl_数据抽取一看名字就知道该账号的权限边界。我的经验是把创建用户的流程录成自动化脚本所有账号生成的SQL都走Git版本管理——谁申请、为什么申请、授权范围是什么全部留痕。数据库出问题被追责时这套流程能帮你省掉很多口水。从CREATE USER一条语法聊到权限体系、排错思路说白了MySQL用户管理就是两件事让该进来的人顺畅干活让不该进来的人彻底挡在门外。基础的东西扎扎实实搞明白后面做高可用、分库分表、审计这些进阶事务时才不会在权限地基上翻车。