苍穹外卖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 最终把它们识别成了两个不同的账户身份。

相关推荐
这个Bug有点难搞2 小时前
Android开发 JNI-调用第三方so库
android
2501_915106322 小时前
如何在 Mac 上面代理抓包和数据流分析
android·macos·ios·小程序·uni-app·iphone·webview
诸神黄昏EX2 小时前
Android Safety 系列专题【篇六:SecureElement安全硬件】
android
shuangrenlong3 小时前
adb连接无线wifi
adb
一只特立独行的Yang3 小时前
Android Graphics - openGL and Vulkan小结
android
2501_915921433 小时前
在 Linux 上通过命令行上架 iOS APP,Fastlane + AppUploader(开心上架)
android·linux·运维·ios·小程序·uni-app·iphone
2501_915921433 小时前
从构建到 IPA 保护,Flutter iOS 包如何做混淆与安全处理
android·安全·flutter·ios·小程序·uni-app·iphone
2501_916008894 小时前
iPhone 手机硬件组件使用耗能历史记录查看,能耗查看
android·ios·智能手机·小程序·uni-app·iphone·webview
峥嵘life4 小时前
Android16 EDLA更新25-12补丁导致【CTS】CtsWindowManagerDeviceAnimations存在fail项
android·linux·学习