苍穹外卖Day1:项目数据库连接问题排查与原理分析报告

苍穹外卖项目数据库连接问题排查与原理分析报告

一、问题背景

在启动苍穹外卖后端初始工程时,项目在初始化数据库连接阶段失败,

Spring Boot 启动报错,Druid 数据源无法正常连接 MySQL。最开始直觉上以为是数据库用户名或密码错误,但经过逐步排查后发现,问题并不是单一因素造成的,而是经历了以下几个阶段:

  • 账号认证失败
  • 同一 MySQL 服务下,不同客户端命中了不同的 MySQL 账户
  • 项目实际命中的账户缺少目标数据库权限
  • 修改配置后又引入了 YAML 语法错误

最终通过逐步定位,问题得到解决。


二、初始报错现象

项目启动时报错如下:

复制代码
create connection SQLException
errorCode 1045, state 28000

java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

这类错误表面上看通常表示:

  • 用户名或密码错误
  • MySQL 不允许当前用户从当前主机登录

因此,最开始最自然的判断是:root 账号密码写错了

但是确认自己在 MySQL 客户端中输入同样的密码可以正常登录,因此说明问题不能简单归结为"密码错了",需要继续分析。


三、第一阶段分析:1045 报错的含义

1. 报错含义

1045 的核心含义是:认证失败

也就是说:

  • JDBC 已经成功访问到了 MySQL 服务
  • 但 MySQL 不认可当前这组登录身份

这说明,此时不是以下问题:

  • MySQL 服务未启动
  • 3306 端口不通
  • Druid 连接池本身故障

而是认证阶段出了问题。


2. 初步怀疑方向

结合现象,初步排查方向包括:

  1. 项目实际读取到的密码不是用户以为的那个密码
  2. Spring Boot 读取了其他配置文件,如 application-dev.yml
  3. localhost127.0.0.1 在 MySQL 中可能命中的是不同账户
  4. Workbench、命令行、JDBC 可能使用了不同的连接方式,最终导致不同的权限结果

四、第一轮调试:验证 root 在不同 host 下的登录情况

为了验证是否是 localhost127.0.0.1 的差异导致,先在命令行中测试 root 用户对不同 host 的登录情况。

1. 使用 localhost 登录

复制代码
mysql -hlocalhost -P3306 -uroot -p

结果:登录成功


2. 使用 127.0.0.1 登录

复制代码
mysql -h127.0.0.1 -P3306 -uroot -p

结果:

复制代码
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

3. 分析

这一步非常关键,说明:

  • 同样是 root
  • 同样是本机
  • 同样是 3306 端口
  • 但是 localhost 可以登录
  • 127.0.0.1 却不能登录

这表明:

MySQL 对不同 host 形式的本地连接,可能命中的是不同账户规则。

因此,不能简单地把"root 能登录"理解成"项目一定能登录"。


五、第二轮调试:查询 MySQL 中 root 账户的 host 情况

成功登录后,执行如下 SQL:

复制代码
select user, host, plugin, account_locked, password_expired
from mysql.user
where user = 'root';

查询结果:

复制代码
+------+-----------+-----------------------+----------------+------------------+
| user | host      | plugin                | account_locked | password_expired |
+------+-----------+-----------------------+----------------+------------------+
| root | %         | mysql_native_password | N              | N                |
| root | 127.0.0.1 | mysql_native_password | N              | N                |
| root | localhost | mysql_native_password | N              | N                |
+------+-----------+-----------------------+----------------+------------------+

分析

这说明系统中并不是只有一个 root,而是至少存在三条不同的 root 账户规则:

  • root@%
  • root@127.0.0.1
  • root@localhost

虽然用户名都叫 root,但在 MySQL 的权限系统中,它们是三个不同的账户实体,权限可以彼此独立。


六、建立正确理解:三者的连接关系到底是什么

在这次排查里,最容易让人困惑的点是:

为什么 Workbench、命令行、Spring Boot 项目都在连本地 MySQL,但结果却不一样?

关键在于:它们都连到了同一个 MySQL 服务实例,但 MySQL 最终给它们匹配到的账户身份不同。


1. 三者之间的关系图

复制代码
+--------------------------------------+
           |          MySQL Server                |
           |      主机: 本机   端口: 3306          |
           |                                      |
           |   内部存在多条账户记录:              |
           |   - root@localhost                   |
           |   - root@127.0.0.1                   |
           |   - root@%                           |
           +--------------------------------------+
                    ▲                    ▲
                    |                    |
                    |                    |
         +------------------+   +----------------------+
         | MySQL Workbench  |   | Spring Boot 项目     |
         |                  |   | Druid + JDBC         |
         | 一个客户端        |   | 另一个客户端         |
         +------------------+   +----------------------+

2. 为什么"同样是 root",结果却不一样

因为 MySQL 账户不是只看用户名,而是看:

复制代码
用户名 + host

也就是说:

  • root@localhost
  • root@127.0.0.1
  • root@%

虽然名字都叫 root,但它们是不同账户,权限也可以完全不同。


七、user() 与 current_user() 的区别

这是整个问题中最关键、最容易被忽视的一点。

在IDEA中执行为了确认 MySQL 最终到底按哪个账户做权限判断,执行:

复制代码
select user(), current_user();

结果:

复制代码
+----------------+----------------+
| user()         | current_user() |
+----------------+----------------+
| root@localhost | root@127.0.0.1 |
+----------------+----------------+

1. user() 的含义

user() 表示:

客户端在登录时,自报家门说自己是谁。

也就是"我输入的用户名是什么,我从哪来"。


2. current_user() 的含义

current_user() 表示:

MySQL 服务器最终决定,把这次连接视为哪条账户记录。

真正用于权限判断的,不是 user(),而是 current_user()


3. 本次问题中的意义

虽然登录时看起来像是:

复制代码
root@localhost

但 MySQL 最终做权限判断时,使用的是:

复制代码
root@127.0.0.1

因此,项目真正生效的权限不是 root@localhost 的权限,而是 root@127.0.0.1****的权限


4. 用图说明 user() 和 current_user()

复制代码
Spring Boot 项目
    │
    │  登录时自报身份
    ▼
 user() = root@localhost
    │
    │  MySQL 根据 host 规则匹配账户
    ▼
 current_user() = root@127.0.0.1
    │
    │  最终按这条账户记录判断权限
    ▼
 是否允许访问 sky_take_out

所以,真正决定权限的关键不是"我以为我是谁",而是:

MySQL 最终把我算成谁。


八、为什么 Workbench 和项目会命中不同账户

后来在 MySQL Workbench 中执行:

复制代码
select @@hostname, @@port;
select user(), current_user();
show databases like 'sky_take_out';
show grants for current_user();

Workbench 中的结果显示:

  • 主机是同一台机器
  • 端口是同一个 3306
  • user() = root@localhost
  • current_user() = root@localhost
  • 能看到 sky_take_out
  • 权限明显大于 root@127.0.0.1

分析

这说明:

  • Workbench 和项目连接的是同一个 MySQL 服务实例
  • 但命中的不是同一个 MySQL 账户
  • Workbench 命中的是 root@localhost
  • 项目/JDBC 命中的是 root@127.0.0.1

因此就出现了现象差异:

  • Workbench 能看到 sky_take_out
  • 项目不能访问 sky_take_out

更形象的说明图

复制代码
同一台 MySQL Server:3306
        +--------------------------------------------------+
        |  账户A: root@localhost                           |
        |      └─ 权限较大,可访问 sky_take_out            |
        |                                                  |
        |  账户B: root@127.0.0.1                           |
        |      └─ 最初只有 malware.* 权限                 |
        |                                                  |
        |  账户C: root@%                                   |
        +--------------------------------------------------+
                  ▲                             ▲
                  |                             |
                  |                             |
       +---------------------+     +------------------------+
       | Workbench           |     | Spring Boot + JDBC     |
       | current_user()      |     | current_user()         |
       | = root@localhost    |     | = root@127.0.0.1       |
       +---------------------+     +------------------------+
                  |                             |
                  |                             |
          能看到 sky_take_out           最初不能访问 sky_take_out

九、第三阶段:报错从 1045 变成 1044

后来项目报错发生变化:

复制代码
errorCode 1044, state 42000

java.sql.SQLSyntaxErrorException: Access denied for user 'root'@'127.0.0.1' to database 'sky_take_out'

分析

这个变化非常重要,因为它意味着问题已经进入下一阶段。

  • 1045:认证失败,连不上
  • 1044:已经登录成功,但没有访问某个数据库的权限

也就是说,此时问题已经从"账号能不能登录"转变成了:

账号虽然已经登录成功,但它没有 sky_take_out****这个数据库的访问权限。


十、第五轮调试:查看当前账户的授权情况

执行:

复制代码
show grants for current_user();
show grants for 'root'@'127.0.0.1';

结果:

复制代码
GRANT USAGE ON *.* TO `root`@`127.0.0.1`
GRANT ALL PRIVILEGES ON `malware`.* TO `root`@`127.0.0.1`

分析

这说明 root@127.0.0.1 只有两类权限:

  1. USAGE ON *.*
    • 基本等于"允许登录,但没有实质性全局权限"
  1. ALL PRIVILEGES ON malware.*
    • 只对 malware 数据库有全部权限

而对于 sky_take_out

  • 没有授权
  • 因此访问时会报 1044

这正好解释了为什么项目报:

复制代码
Access denied for user 'root'@'127.0.0.1' to database 'sky_take_out'

十一、关于 localhost 与 127.0.0.1 的进一步理解

通过这次排查,还需要建立一个更准确的理解:

1. localhost127.0.0.1 不是简单完全等价

虽然从直觉上看,它们都指向本机,但在数据库连接场景中,它们不一定被完全一样地处理。

  • 127.0.0.1 更明确表示通过 TCP/IP 回环地址连接本机
  • localhost 在不同客户端、不同实现中,可能触发更偏"本地"的连接处理方式

因此:

  • Workbench 更可能命中 root@localhost
  • JDBC 作为数据库驱动,更稳定地按 TCP/IP 路径工作,更容易最终命中 root@127.0.0.1

2. 这次问题中应该如何准确表述

更准确的结论不是:

JDBC 自动把 localhost 改成了 127.0.0.1

而是:

JDBC 这条连接最终通过 TCP 路径接入 MySQL,MySQL 最终将这次连接匹配为了 root@127.0.0.1 账户。

所以,最终权限判断依据的是:

复制代码
current_user() = root@127.0.0.1

十二、问题根因

最终可以明确,根因不是单一问题,而是多个因素叠加:

1. MySQL 中存在多个 root 账户

分别是:

  • root@localhost
  • root@127.0.0.1
  • root@%

这些账户虽然名字都叫 root,但权限彼此独立。

2. Workbench 和 JDBC 命中的账户不同

  • Workbench:root@localhost
  • 项目/JDBC:root@127.0.0.1

3. root@127.0.0.1 最初没有 sky_take_out 库权限

它只有 malware.* 的权限,因此项目启动时无法访问 sky_take_out

4. 后续修改配置时,又引入了 YAML 语法错误

例如把 URL 写成了:

复制代码
url: { jdbc:mysql://127.0.0.1:3306/sky_take_out?... }

导致 Spring Boot 在读取配置文件时直接报错:

复制代码
org.yaml.snakeyaml.parser.ParserException
while parsing a flow mapping
expected ',' or '}', but got '?'

这说明程序甚至还没开始连数据库,配置文件解析阶段就失败了。


十三、解决过程

1. 在有管理权限的 Workbench 会话中授权

由于 Workbench 命中的是 root@localhost,并且该账户拥有足够权限,因此可以用它给 root@127.0.0.1 授权:

复制代码
GRANT ALL PRIVILEGES ON sky_take_out.* TO 'root'@'127.0.0.1';

再次查看授权:

复制代码
SHOW GRANTS FOR 'root'@'127.0.0.1';

结果中出现:

复制代码
GRANT ALL PRIVILEGES ON `sky_take_out`.* TO `root`@`127.0.0.1`

说明授权成功。


2. 修正 application.yml 配置

将 JDBC URL 改为明确使用 127.0.0.1,避免 localhost 再引发账户命中混乱。

正确写法如下:

复制代码
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: "jdbc:mysql://127.0.0.1:3306/sky_take_out?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true"
      username: root
      password: "你的密码"

3. 修正 YAML 语法错误

不能写成:

复制代码
url: { jdbc:mysql://127.0.0.1:3306/sky_take_out?... }

因为 {} 在 YAML 中会被解析成对象映射,而不是普通字符串。

必须写成普通字符串,最好加引号。


十四、关键报错与含义总结

1. 1045

复制代码
Access denied for user 'root'@'localhost' (using password: YES)

含义:

  • 用户认证失败
  • 用户名/密码/host 匹配不通过

2. 1044

复制代码
Access denied for user 'root'@'127.0.0.1' to database 'sky_take_out'

含义:

  • 登录成功
  • 但没有数据库 sky_take_out 的访问权限

3. YAML ParserException

复制代码
while parsing a flow mapping
expected ',' or '}', but got '?'

含义:

  • application.yml 文件语法错误
  • Spring Boot 还没开始连数据库,配置解析阶段就失败了

十五、本次排查中使用的关键命令汇总

1. 测试 localhost 登录

复制代码
mysql -hlocalhost -P3306 -uroot -p

2. 测试 127.0.0.1 登录

复制代码
mysql -h127.0.0.1 -P3306 -uroot -p

3. 查看 root 账户列表

复制代码
select user, host, plugin, account_locked, password_expired
from mysql.user
where user = 'root';

4. 查看当前真实权限身份

复制代码
select user(), current_user();

5. 查看当前账户授权

复制代码
show grants for current_user();
show grants for 'root'@'127.0.0.1';

6. 查看库是否可见

复制代码
show databases like 'sky_take_out';

7. 给 root@127.0.0.1 授权

复制代码
GRANT ALL PRIVILEGES ON sky_take_out.* TO 'root'@'127.0.0.1';

8. 验证库可用性

复制代码
use sky_take_out;
show tables;

十六、经验总结

这次问题的最大收获,不只是"把项目跑起来",而是对 MySQL 权限体系、本地连接行为,以及项目配置排错逻辑有了更深入的理解。

1. MySQL 权限判断看的是"用户名 + host"

不能只看用户名。
root@localhostroot@127.0.0.1 虽然都叫 root,但权限可以完全不同。

2. user()current_user() 很关键

  • user() 表示登录时客户端自报的身份
  • current_user() 表示 MySQL 最终实际按哪个账户做权限判断

排查权限问题时,必须看 current_user()

3. "能登录"不等于"有库权限"

即使某账号能进入 MySQL,也不代表它有目标数据库的权限。

认证通过只是第一步,库权限还要单独检查。

4. Workbench、命令行、JDBC 可能命中不同账户

虽然都在访问本地 MySQL,但最终匹配到的账户不一定一样,不能简单互相替代。

5. 配置文件错误会掩盖真正问题

数据库权限刚修好后,又因为 YAML 写法错误导致程序启动失败。

所以调试时要区分:

  • 配置解析错误
  • 数据库连接错误
  • 权限错误

这三类问题不在同一个层面。

6. 连接地址最好统一

在本地开发中,如果已经确认项目实际按 TCP 路径访问数据库,那么 JDBC URL 直接写成 127.0.0.1 更清晰,也更利于排查。


十七、最终结论

本次苍穹外卖项目数据库连接问题,根本原因是:

项目通过 JDBC 连接 MySQL 时,最终命中了 root@127.0.0.1****账户,而该账户最初没有 sky_take_out****数据库的访问权限;同时在修改配置过程中,又引入了 YAML 语法错误,导致 Spring Boot 启动失败。

最终通过以下措施解决:

  1. 明确区分 root@localhostroot@127.0.0.1
  2. 使用具有管理员权限的 root@localhostroot@127.0.0.1 授予 sky_take_out.* 权限
  3. 将 JDBC 连接地址统一为 127.0.0.1
  4. 修正 application.yml 中 JDBC URL 的 YAML 语法错误

至此,项目数据库连接问题得到解决。


十八、一句话总结这次问题的本质

不是 Workbench 和项目连了不同的数据库,而是它们连了同一个 MySQL 服务,但 MySQL 最终把它们识别成了两个不同的账户身份。

相关推荐
千里马学框架8 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台8 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone8 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc9 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo10 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770011 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼12 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone13 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen13 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone13 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui