mysql连接建立需经tcp三次握手、协议协商与认证交换三阶段,权限验证失败会立即中断连接;连接池复用需重置会话变量,否则污染后续请求;定位权限问题应分网络层、账号层、对象层逐层排查。

MySQL 连接建立时发生了什么
连接不是“点一下就通”,而是客户端和服务器之间完成三次握手、协议协商、认证交换的完整过程。你调用 mysql_real_connect()(C API)或 new mysqli()(PHP)、mysql.connect()(Python 的 mysql-connector-python)时,底层都在走这套流程。
关键点在于:连接建立 ≠ 会话就绪。权限验证失败会导致连接被立即中断,但错误可能被上层库吞掉或误报为“timeout”。
- 常见错误现象:
Access denied for user 'xxx'@'yyy'或Lost connection to MySQL server at 'handshake' - 真实原因常是:用户名密码错、host 匹配失败(比如用
'user'@'localhost'却从 127.0.0.1 连)、账户被FLUSH PRIVILEGES后未重载(其实已废弃,但旧脚本仍这么写) - 注意:MySQL 8.0 默认用
caching_sha2_password插件,老客户端(如 MySQL 5.7 客户端、某些 JDBC 驱动旧版)不支持,会直接断连,报错可能是Authentication plugin 'caching_sha2_password' cannot be loaded
连接复用与连接池怎么选
短生命周期应用(如 CLI 脚本、单次 HTTP 请求)直接新建/关闭连接没问题;高并发服务必须用连接池,否则很快耗尽 max_connections 或触发 TCP TIME_WAIT 拥塞。
但别以为“加个 pool 就万事大吉”。连接池管理的是物理连接,而每个连接绑定一个会话上下文(@@session.sql_mode、@@autocommit、临时表、用户变量等)。没清理干净就复用,会污染后续请求。
- 典型坑:
SET @myvar := 1后把连接还给池,下一个请求读@myvar得到 1 —— 不是 bug,是设计如此 - 正确做法:连接归还前执行
RESET SESSION(MySQL 5.7.3+),或手动SET autocommit = 1; SET sql_mode = DEFAULT;等重置关键会话变量 - 语言差异:Go 的
database/sql默认启用连接池且自动RESET SESSION;Python 的pymysql不自动重置,需自己在ping()后加清理逻辑
权限验证失败时,怎么快速定位是哪一层拦的
权限校验发生在连接建立阶段,但拦截点有三层:网络层、账号层、对象层。不能一看到拒绝就连忙改 GRANT,先分清是哪一关没过。
最有效的方式是开 general log 或用 SHOW PROCESSLIST 观察连接状态变化:如果刚出现就变成 Connect 然后消失,基本是账号层拒绝;如果卡在 Connecting to master 或超时,优先查防火墙、bind_address、skip-networking。
- 检查账号是否存在:
SELECT User, Host FROM mysql.user WHERE User = 'xxx'; - 确认 host 匹配逻辑:MySQL 用最长匹配,
'user'@'192.168.%'优先于'user'@'%',但'user'@'localhost'和'user'@'127.0.0.1'是两个不同账号 - 绕过 DNS 解析干扰:启动 mysqld 时加
--skip-name-resolve,并确保所有GRANT语句里的 host 写 IP 而非域名
会话级设置(如 SQL_MODE、time_zone)为什么每次连接都要重设
因为它们不属于账号元数据,不随认证持久化。MySQL 只在连接建立时继承全局值(@@global.sql_mode),之后全靠你自己初始化。
很多 ORM 或连接器提供 init_command 参数,就是干这事的。别依赖“我上次 SET 过,这次应该还在”——连接池里拿来的连接,它的会话状态是上一个使用者留下的残影。
- 推荐 init_command:
SET NAMES utf8mb4, time_zone = '+00:00', sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' - 避免用
SET @@session.xxx = yyy在业务代码里零散设置,容易遗漏或顺序错乱 - 注意:MySQL 8.0.19+ 支持
PERSIST全局设置,但它影响的是@@global,不影响新连接的默认会话值
会话状态的边界比想象中模糊——连接池、长连接、多线程共享连接这些场景下,“谁设置了什么”“谁该负责清理”很容易变成隐式契约。真要稳定,就得把初始化和清理写进连接获取/释放的钩子里,而不是靠文档里那句“默认值是 XXX”。










