李涛自留地
回到顶部
首页 > 建站 > mysql报错1045,我在两台服务器上追到同一个原因

mysql报错1045,我在两台服务器上追到同一个原因

文章目录

登录被拒这件事在 MySQL 里只有一个编号,就是mysql报错1045。它跟权限不足是两件事:库表授权还没轮到判断,服务端连身份都没认下来,连接就被断掉了。判定只看三样,账号名、来源主机、密码,任何一样和 mysql.user 里的记录对不上,返回的都是同一个编号。

编号一样,原因可以完全不同。所以看到 1045 先别急着改密码,把报错行读全,再看账号是从哪条 host 记录上被挡下来的。

ERROR 1045 报错原文与常见成因

遇到mysql报错1045,先把这行读全,别只盯着那个数字。完整的一行长这样:

三个方框横向串联,依次标着客户端、host 匹配与密码

ERROR 1045 (28000): Access denied for user 'app'@'10.0.3.24' (using password: YES)

括号里的 28000 是 SQLSTATE,1045 是服务端错误号。真正有用的是后半段两处。

'app'@'10.0.3.24' 是服务端看到的来源地址,不是授权表里写的那一行。拿这个 IP 去 mysql.user 的 host 列搜,搜不到很正常。(using password: YES) 表示密码确实发过去了,可以排除「客户端根本没带密码」这一类。

剩下的成因按我遇到的次数排:

  • 账号不存在,或者建在了另一台实例上;
  • 账号在,host 写的是 localhost,这次却是从内网 IP 连进来的;
  • 密码对不上,或者账号的认证插件和客户端不匹配;
  • 直接 UPDATE 改过 mysql.user,没执行 flush privileges(8.0 起 mysql.user 是只读视图,这条路已经走不通,只能 CREATE USER / ALTER USER / GRANT)

最后一条要分清:用 CREATE USER、GRANT 改的权限会自动生效,不用手动刷;只有直接改系统表才需要刷。

host 字段决定从哪台机器登录

mysql.user 里的 host 管的不是「你从哪来」,是「允不允许你从这来」。

匹配按精确程度走:localhost 最具体,然后是写死的 IP,再往后是 10.0.3.% 这种网段,最后才是 %。同名账号可以挂好几行,服务端挑最具体的那行去比密码。

这台实例上,app 挂着两行:一行 localhost,一行 10.0.3.%。本地 socket 登录走 localhost 那行,口令是旧的;从 10.0.3.24 连过来,命中的是 10.0.3.% 那行,两行口令不一致,报错就出来了。

四个纵向方框串联,中间两节由虚线箭头互指

两行口令不一致的时候,从 10.0.3.% 那行进来的连接几乎必然报 1045,只改其中一行没用。

第一次撞上是在一台 5.7 上:本机用 root 进去看,账号、库、密码都对,应用那边一直报错。第二次是 8.0,反过来了,应用能连,本地命令行进不去。查到最后都是 host 那一列,只是被挡的是不同的客户端。

三处要对上的东西,按顺序查

顺序不能乱。先确认账号在不在,再看 host,最后才怀疑密码。

第一步,把同名账号的所有行列出来:

SELECT user, host, plugin FROM mysql.user WHERE user = 'app';

输出里一个 host 一行,重点看 host 和 plugin 两列。一行都没有的话,账号就不在这台实例上,后面几步都不用做了。我这边跑出来是两行:

  • app | localhost | mysql_native_password
  • app | 10.0.3.% | caching_sha2_password

第二行才是远程连接会用到的那条,它的密码和第一行不是一个。

第二步,把报错行里的来源地址对到具体那一行:

报错行写的是 'app'@'10.0.3.24',就拿 10.0.3.24 去对 host 列。localhost 匹配不上,10.0.3.% 匹配得上,% 也匹配得上,按最具体优先,命中 10.0.3.%。这一步只用看表,连接是断的也能做。

SELECT user, host, plugin FROM mysql.user
WHERE user = 'app' AND '10.0.3.24' LIKE host;

把来源 IP 当成待匹配串、host 当模式,能把候选行筛出来:localhost 这行不成立,10.0.3.% 和 % 两行都成立,最后按最具体优先取 10.0.3.%。

第三步,把口令改成一致:

ALTER USER 'app'@'10.0.3.%' IDENTIFIED BY '和 localhost 那行相同的口令';
GRANT SELECT, INSERT, UPDATE ON shop.* TO 'app'@'10.0.3.%';

这里不能用 CREATE USER —— 上一节列出来的两行里 10.0.3.% 已经存在,重复建会直接抛 ERROR 1396 (HY000): Operation CREATE USER failed for 'app'@'10.0.3.%'。账号不存在时才建,存在就用 ALTER USER 改口令。

把 host 写成 10.0.3.% 比给每台机器单独建账号省事,代价是这一段内网里的机器共用同一份授权,谁拿到口令都能进。

所以在动手改授权之前,先把mysql报错1045里那个来源地址抄下来,后面每一步都拿它对照。

左盘一格 socket,右盘 TCP 与 socket 并排两格

建完直接重连,不用重启 mysqld。换 host 再连一次,写成 mysql -h 10.0.3.24 -P 3306 -u app -p shop。

回车之后的变化很明确:提示从 ERROR 1045 变成 Welcome to the MySQL monitor,接着 SHOW DATABASES 能看到 shop。

进去之后还有一步值得做,执行 SELECT CURRENT_USER();,返回 app@10.0.3.%,这就是实际生效的那条记录。它和 USER() 不是一个东西:后者是你写在命令里的账号名和来源,前者是服务端匹配到的授权行,两个值对不上,说明还在别的 host 上。

localhost 和 % 差在哪

这两个写法不是「本机」和「别的机器」这么简单,它们对应两条不同的连接路径。

localhost 走 Unix socket,不经过 TCP。所以在 Linux 上 mysql -h localhost 和 mysql -h 127.0.0.1 是两条路:前者匹配 localhost 那行,后者走 TCP,匹配 127.0.0.1 或 %,匹配不到 localhost。

用 -h 127.0.0.1 去测「本地能不能连」,测出来的结果和 socket 登录不是一回事。

想确认走的是哪条路,看 socket 文件在不在最直接。常见的路径是 /var/run/mysqld/mysqld.sock 或者 /tmp/mysql.sock,具体位置由 my.cnf 里的 socket 项决定,以自己机器上的为准。

% 匹配任意来源,但它最不具体。两行都在的时候,本机 socket 永远先命中 localhost;只有 % 一行的时候,socket 登录也能进,因为 % 把 localhost 也覆盖了。

分清这两条路之后,多数mysql报错1045在第 3 步就结束了。

还没定下来的那两处

账号放在代理或中间件后面,服务端看到的来源是代理 IP。host 该写代理段还是写真实网段,取决于代理有没有透传源地址。我手上这两台是直连的,没这套环境,测不出来。

另一处是 skip_name_resolve。开了它,服务端不对客户端 IP 做反解,host 只能按 IP 或通配符匹配,写主机名的那几条会全部失效。它在 my.cnf 里,改完要重启 mysqld,值不值得改要看你这边 DNS 稳不稳。

若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。