MySQL 传输协议精讲 / 02 - 握手过程
第 02 章:握手过程
2.1 握手过程概述
MySQL 连接的建立分为两个主要阶段:TCP 连接建立和 MySQL 协议握手。本章聚焦于后者。
Client Server
│ │
│ ──── TCP SYN ────────────────────────────────→ │
│ ←─── TCP SYN-ACK ─────────────────────────── │
│ ──── TCP ACK ───────────────────────────────→ │
│ │
│ == MySQL 握手开始 (Protocol V10) == │
│ │
│ ←─── HandshakeV10 (seq=0) ────────────────── │ 服务器问候
│ ──── HandshakeResponse41 (seq=1) ───────────→ │ 客户端响应
│ │
│ [如果需要 SSL] │
│ ──── SSLRequest (seq=2) ───────────────────→ │ SSL 升级请求
│ === TLS 握手 (加密通道建立) === │
│ ──── HandshakeResponse41 (seq=1) ───────────→ │ 加密后的响应
│ │
│ [如果需要额外认证] │
│ ←─── AuthMoreData (seq=2) ───────────────── │ 认证数据
│ ──── AuthResponse (seq=3) ─────────────────→ │ 认证应答
│ │
│ ←─── OK_Packet (seq=2) ──────────────────── │ 认证成功
│ 或 ERR_Packet (seq=2) ────────────────→ │ 认证失败
│ │
│ == 握手结束,进入命令阶段 == │
2.2 HandshakeV10 数据包详解
服务器在 TCP 连接建立后,立即发送 HandshakeV10 数据包。这是客户端收到的第一个 MySQL 数据包。
数据包结构
偏移量 大小 字段名称 说明
─────────────────────────────────────────────────────────────
0 1 字节 protocol_version 固定为 10
1 变长 server_version 以 0x00 结尾的字符串
N+1 4 字节 connection_id 连接 ID (小端序)
N+5 8 字节 auth_plugin_data_part_1 认证数据第一部分
N+13 1 字节 filler 固定为 0x00
N+14 2 字节 capability_flags_lower 能力标志低位 2 字节
N+16 1 字节 character_set 字符集 ID
N+17 2 字节 status_flags 服务器状态标志
N+19 2 字节 capability_flags_upper 能力标志高位 2 字节
N+21 1 字节 auth_plugin_data_length 认证数据总长度
N+22 10 字节 reserved 保留字节 (全 0x00)
N+32 变长 auth_plugin_data_part_2 认证数据第二部分 (min 13 bytes)
1 字节 (null terminator) 0x00
变长 auth_plugin_name 认证插件名 (以 0x00 结尾)
注意:N 是
server_version字符串的长度(包括末尾的 0x00)。
字段详解
protocol_version (1 字节)
值: 0x0A (十进制 10)
MySQL 4.1+ 统一使用协议版本 10。如果客户端收到的不是 10,说明连接到了一个非常老的 MySQL 服务器。
server_version (变长字符串)
以 null 结尾的 ASCII 字符串,格式通常为 主版本.次版本.修订版本:
示例值:
"8.0.35" → MySQL 8.0.35
"8.0.35-0ubuntu" → 带发行版后缀
"5.7.44-log" → MySQL 5.7,带日志标志
安全提示:生产环境建议配置
version-suffix隐藏精确版本号,防止攻击者利用版本特定漏洞。
connection_id (4 字节,小端序)
服务器为每个连接分配的唯一 ID,用于 KILL 命令:
-- 查看当前连接列表
SHOW PROCESSLIST;
-- Id 列就是 connection_id
-- 终止某个连接
KILL 123;
auth_plugin_data_part_1 (8 字节) + part_2 (min 13 字节)
这是服务器为认证生成的随机数(scramble),用于挑战-应答认证。总共至少 20 + 13 = 32 字节。
- Part 1(8 字节):位于固定偏移量
- Part 2(至少 13 字节):位于数据包尾部,长度由
auth_plugin_data_length决定
完整 scramble = auth_plugin_data_part_1 + auth_plugin_data_part_2
总长度 = max(13, auth_plugin_data_length - 8)
客户端使用这个 scramble 对密码进行加密,服务器验证加密结果。
capability_flags (4 字节 = 2 字节低位 + 2 字节高位)
32 位的能力标志是整个协议的核心。它定义了客户端和服务器各自支持的特性。只有双方都声明支持的特性才会启用。
关键能力标志列表:
| 位 | 名称 | 说明 |
|---|---|---|
| 0 | CLIENT_LONG_PASSWORD | 使用新式(> 4.1)密码哈希 |
| 2 | CLIENT_LONG_FLAG | 使用更长的字段标志 |
| 3 | CLIENT_CONNECT_WITH_DB | 可以在连接时指定数据库 |
| 5 | CLIENT_COMPRESS | 支持 zlib 压缩 |
| 6 | CLIENT_ODBC | ODBC 客户端 |
| 7 | CLIENT_LOCAL_FILES | 可以使用 LOAD DATA LOCAL |
| 8 | CLIENT_IGNORE_SPACE | 忽略函数名后的空格 |
| 9 | CLIENT_PROTOCOL_41 | 使用 4.1 协议格式(必须) |
| 10 | CLIENT_INTERACTIVE | 交互式客户端 |
| 11 | CLIENT_SSL | 支持 SSL/TLS |
| 13 | CLIENT_TRANSACTIONS | 支持事务状态追踪 |
| 15 | CLIENT_SECURE_CONNECTION | 使用新的认证方式 |
| 16 | CLIENT_MULTI_STATEMENTS | 支持多语句执行 |
| 17 | CLIENT_MULTI_RESULTS | 支持多结果集 |
| 19 | CLIENT_PLUGIN_AUTH | 支持认证插件 |
| 20 | CLIENT_CONNECT_ATTRS | 支持连接属性 |
| 21 | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | 认证数据使用长度编码 |
| 22 | CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | 能处理过期密码 |
| 23 | CLIENT_SESSION_TRACK | 支持会话状态追踪 |
| 25 | CLIENT_DEPRECATE_EOF | 用 OK 包替代 EOF 包 |
| 27 | CLIENT_OPTIONAL_RESULTSET_METADATA | 结果集元数据可选 |
| 29 | CLIENT_ZSTD_COMPRESSION_ALGORITHM | 支持 zstd 压缩 |
character_set (1 字节)
字符集 ID,常用值:
| ID | 字符集 |
|---|---|
| 8 | latin1 |
| 33 | utf8 (utf8mb3) |
| 45 | utf8mb4 |
| 255 | utf8mb4 (MySQL 8.0 的 45 和 255 都指向 utf8mb4) |
status_flags (2 字节)
服务器状态标志,常见值:
| 名称 | 值 | 说明 |
|---|---|---|
SERVER_STATUS_IN_TRANS | 0x0001 | 在事务中 |
SERVER_STATUS_AUTOCOMMIT | 0x0002 | 自动提交已开启 |
SERVER_STATUS_CURSOR_EXISTS | 0x0040 | 游标已打开 |
SERVER_STATUS_NO_GOOD_INDEX_USED | 0x0010 | 未使用好索引 |
SERVER_STATUS_NO_INDEX_USED | 0x0020 | 未使用索引 |
2.3 HandshakeResponse41 数据包
客户端收到服务器的 HandshakeV10 后,回复 HandshakeResponse41。
数据包结构
偏移量 大小 字段名称 说明
─────────────────────────────────────────────────────────────
0 4 字节 capability_flags 客户端能力标志
4 4 字节 max_packet_size 最大包大小
8 1 字节 character_set 字符集 ID
9 23 字节 reserved 保留 (全 0x00)
32 变长 username 用户名 (null 结尾)
变长 auth_response 认证响应数据
变长 database 数据库名 (可选, null 结尾)
变长 auth_plugin_name 认证插件名 (null 结尾)
变长 connect_attributes 连接属性 (可选)
字段详解
capability_flags (4 字节)
客户端声明自己支持的特性。必须是服务器能力标志的子集,否则可能导致未定义行为。
客户端通常会启用以下标志组合:
# 典型的客户端能力标志
CLIENT_FLAGS = (
CLIENT_PROTOCOL_41 | # 4.1 协议格式
CLIENT_SECURE_CONNECTION | # 安全连接
CLIENT_PLUGIN_AUTH | # 认证插件
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | # 长度编码认证数据
CLIENT_CONNECT_WITH_DB | # 可选:指定数据库
CLIENT_CONNECT_ATTRS | # 可选:连接属性
CLIENT_DEPRECATE_EOF | # 可选:使用 OK 替代 EOF
CLIENT_MULTI_STATEMENTS | # 可选:多语句
CLIENT_MULTI_RESULTS # 可选:多结果集
)
max_packet_size (4 字节)
客户端能接收的最大数据包大小。通常设为 16 MB (0x01000000) 或更大。
典型值:
0x00000000 → 使用服务器默认
0x01000000 → 16 MB
0x40000000 → 1 GB
auth_response (变长)
认证响应的编码方式取决于是否设置了 CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA:
如果设置了该标志:
[长度编码的字节数] + [认证数据字节]
如果没有设置:
[认证数据字节] + 0x00 (null 结尾)
长度编码方式(Length-Encoded Integer):
| 字节数 | 值范围 | 编码方式 |
|---|---|---|
| 1 | 0-250 | 直接存储 |
| 3 | 251-65535 | 0xFC + 2 字节小端序 |
| 4 | 65536-16777215 | 0xFD + 3 字节小端序 |
| 9 | > 16777215 | 0xFE + 8 字节小端序 |
2.4 SSL/TLS 升级握手
如果客户端需要 SSL 加密,必须在发送 HandshakeResponse41 之前先发送一个简短的 SSLRequest 包,然后切换到 TLS 通信。
SSL 升级流程
Client Server
│ │
│ ←── HandshakeV10 (明文) ────────────── │
│ │
│ ──── SSLRequest (明文, seq=1) ────────→ │
│ │
│ ══════ TLS 握手 (加密通道建立) ════════ │
│ │
│ ──── HandshakeResponse41 (加密) ─────→ │
│ ←── OK/ERR (加密) ────────────────── │
│ │
│ == 后续所有通信加密 == │
SSLRequest 数据包
SSLRequest 是 HandshakeResponse41 的精简版,只包含前 32 字节:
偏移量 大小 字段名称
─────────────────────────────────
0 4 字节 capability_flags (必须设置 CLIENT_SSL)
4 4 字节 max_packet_size
8 1 字节 character_set
9 23 字节 reserved
Python 示例:建立 SSL 连接
"""
mysql_ssl_connection.py
演示 MySQL 协议的 SSL/TLS 升级过程
"""
import socket
import ssl
import struct
def mysql_ssl_connect(host='127.0.0.1', port=3306,
username='root', password='password',
ca_cert='/path/to/ca.pem'):
"""建立带 SSL 的 MySQL 连接"""
# 第一步:建立普通 TCP 连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
print(f"[+] TCP 连接建立")
# 第二步:接收服务器握手包
header = sock.recv(4)
payload_length = struct.unpack('<I', header[0:3] + b'\x00')[0]
sequence = header[3]
server_handshake = sock.recv(payload_length)
print(f"[+] 收到 HandshakeV10 (seq={sequence}, len={payload_length})")
# 提取 scramble(简化版)
# 跳过 protocol_version(1) + server_version + null
null_pos = server_handshake.index(b'\x00', 1)
offset = null_pos + 1
# connection_id(4) + auth_data_part1(8) + filler(1)
offset += 13
cap_low = struct.unpack('<H', server_handshake[offset:offset+2])[0]
# 第三步:发送 SSLRequest(仅前 32 字节)
CLIENT_SSL = 1 << 11
CLIENT_PROTOCOL_41 = 1 << 9
CLIENT_SECURE_CONNECTION = 1 << 15
CLIENT_PLUGIN_AUTH = 1 << 19
capability_flags = (
CLIENT_PROTOCOL_41 |
CLIENT_SECURE_CONNECTION |
CLIENT_SSL |
CLIENT_PLUGIN_AUTH
)
ssl_request = struct.pack('<IIB23s',
capability_flags, # capability_flags
1024 * 1024 * 16, # max_packet_size
33, # character_set (utf8)
b'\x00' * 23 # reserved
)
# 添加包头 (3 字节长度 + 1 字节序列号)
packet = struct.pack('<I', len(ssl_request))[:3] + struct.pack('B', 1) + ssl_request
sock.send(packet)
print(f"[+] 发送 SSLRequest")
# 第四步:升级到 TLS
context = ssl.create_default_context()
context.load_verify_locations(ca_cert)
# 如需跳过证书验证(仅测试环境):
# context.check_hostname = False
# context.verify_mode = ssl.CERT_NONE
ssl_sock = context.wrap_socket(sock, server_hostname=host)
print(f"[+] TLS 升级成功: {ssl_sock.version()}")
# 第五步:通过加密通道发送 HandshakeResponse41
# (此处省略完整的认证响应构造,实际实现请参考 PyMySQL 源码)
print("[*] 后续所有通信已加密")
ssl_sock.close()
if __name__ == '__main__':
mysql_ssl_connect()
服务器端 SSL 配置
-- 查看 SSL 配置
SHOW VARIABLES LIKE '%ssl%';
-- 强制所有连接使用 SSL
ALTER USER 'app_user'@'%' REQUIRE SSL;
-- 要求特定的 SSL 证书
ALTER USER 'app_user'@'%' REQUIRE ISSUER '/CN=MyCA';
2.5 握手过程中的认证交换
认证交换的详细流程
以 MySQL 8.0 默认的 caching_sha2_password 插件为例:
Server → Client: HandshakeV10
scramble = "NjYxZjQ2NjM5NjUx..." (32 字节随机数)
auth_plugin = "caching_sha2_password"
Client → Server: HandshakeResponse41
username = "root"
auth_data = SHA256(SHA256(password)) XOR SHA256(scramble + SHA256(SHA256(password)))
auth_plugin = "caching_sha2_password"
Server → Client: 认证结果
情况 A: OK_Packet (缓存命中,认证成功)
情况 B: AuthMoreData + 公钥 (缓存未命中)
情况 C: ERR_Packet (认证失败)
详细认证过程将在第 03 章中展开。
2.6 握手失败的常见场景
场景一:协议版本不匹配
ERR_Packet:
error_code: 2027
message: "Packet malformed"
原因:客户端发送了错误格式的 HandshakeResponse。
场景二:能力标志不兼容
ERR_Packet:
error_code: 1251
message: "Client does not support authentication protocol requested by server;
consider upgrading MySQL client"
原因:客户端未设置 CLIENT_PLUGIN_AUTH 标志,但服务器要求使用认证插件。
场景三:认证失败
ERR_Packet:
error_code: 1045
message: "Access denied for user 'root'@'localhost' (using password: YES)"
原因:用户名或密码错误。
场景四:连接数超限
ERR_Packet:
error_code: 1040
message: "Too many connections"
原因:服务器连接数已达 max_connections 限制。
场景五:主机被阻断
ERR_Packet:
error_code: 1130
message: "Host '192.168.1.100' is not allowed to connect to this MySQL server"
原因:该主机未在 mysql.user 表中授权。
2.7 Python 完整握手实现
下面是一个最小化的 MySQL 握手客户端实现:
"""
mysql_minimal_handshake.py
实现最简单的 MySQL 协议握手(使用 mysql_native_password 认证)
"""
import socket
import struct
import hashlib
def sha1(data):
return hashlib.sha1(data).digest()
def mysql_native_password_hash(password, scramble):
"""
mysql_native_password 认证算法:
hash = SHA1(password) XOR SHA1(scramble + SHA1(SHA1(password)))
"""
if not password:
return b''
password_sha1 = sha1(password.encode('utf-8'))
password_sha1_sha1 = sha1(password_sha1)
scramble_and_hash = sha1(scramble + password_sha1_sha1)
# XOR 操作
result = bytes(a ^ b for a, b in zip(password_sha1, scramble_and_hash))
return result
def read_null_terminated_string(data, offset):
"""读取以 null 结尾的字符串"""
end = data.index(b'\x00', offset)
return data[offset:end].decode('ascii'), end + 1
def read_length_encoded_integer(data, offset):
"""读取长度编码的整数"""
first = data[offset]
if first < 0xFB:
return first, offset + 1
elif first == 0xFC:
return struct.unpack('<H', data[offset+1:offset+3])[0], offset + 3
elif first == 0xFD:
return struct.unpack('<I', data[offset+1:offset+4] + b'\x00')[0], offset + 4
elif first == 0xFE:
return struct.unpack('<Q', data[offset+1:offset+9])[0], offset + 9
def minimal_handshake(host='127.0.0.1', port=3306,
username='root', password='', database=None):
"""实现最小化的 MySQL 协议握手"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
try:
# 1. TCP 连接
sock.connect((host, port))
print(f"[+] TCP 连接到 {host}:{port}")
# 2. 接收 HandshakeV10
header = b''
while len(header) < 4:
header += sock.recv(4 - len(header))
pkt_len = struct.unpack('<I', header[0:3] + b'\x00')[0]
seq = header[3]
payload = b''
while len(payload) < pkt_len:
payload += sock.recv(pkt_len - len(payload))
print(f"[+] 收到 HandshakeV10 (seq={seq}, len={pkt_len})")
# 解析 HandshakeV10
offset = 0
protocol_version = payload[offset]
offset += 1
server_version, offset = read_null_terminated_string(payload, offset)
connection_id = struct.unpack('<I', payload[offset:offset+4])[0]
offset += 4
auth_data_part1 = payload[offset:offset+8]
offset += 8
filler = payload[offset]
offset += 1
cap_low = struct.unpack('<H', payload[offset:offset+2])[0]
offset += 2
charset = payload[offset]
offset += 1
status = struct.unpack('<H', payload[offset:offset+2])[0]
offset += 2
cap_high = struct.unpack('<H', payload[offset:offset+2])[0]
offset += 2
server_caps = (cap_high << 16) | cap_low
auth_data_len = payload[offset]
offset += 1
offset += 10 # reserved
# auth_data_part2
part2_len = max(13, auth_data_len - 8)
auth_data_part2 = payload[offset:offset+part2_len]
offset += part2_len + 1 # +1 for null terminator
scramble = auth_data_part1 + auth_data_part2
auth_plugin, offset = read_null_terminated_string(payload, offset)
print(f" 服务器版本: {server_version}")
print(f" 连接 ID: {connection_id}")
print(f" 字符集: {charset}")
print(f" 认证插件: {auth_plugin}")
print(f" Scramble: {scramble.hex()}")
# 3. 构造 HandshakeResponse41
# 选择 mysql_native_password 以简化演示
auth_method = 'mysql_native_password'
auth_data = mysql_native_password_hash(password, scramble)
CLIENT_PROTOCOL_41 = 1 << 9
CLIENT_SECURE_CONNECTION = 1 << 15
CLIENT_PLUGIN_AUTH = 1 << 19
CLIENT_CONNECT_WITH_DB = 1 << 3
client_caps = (
CLIENT_PROTOCOL_41 |
CLIENT_SECURE_CONNECTION |
CLIENT_PLUGIN_AUTH
)
if database:
client_caps |= CLIENT_CONNECT_WITH_DB
# 能力标志取交集
client_caps &= server_caps
# 构建响应体
body = b''
body += struct.pack('<I', client_caps) # capability_flags
body += struct.pack('<I', 16 * 1024 * 1024) # max_packet_size
body += struct.pack('<B', 33) # character_set (utf8mb4=33)
body += b'\x00' * 23 # reserved
body += username.encode('utf-8') + b'\x00' # username
body += bytes([len(auth_data)]) + auth_data # auth_response (length-encoded)
if database:
body += database.encode('utf-8') + b'\x00' # database
body += auth_method.encode('utf-8') + b'\x00' # auth_plugin_name
# 添加包头
packet = struct.pack('<I', len(body))[:3] + struct.pack('B', seq + 1) + body
sock.send(packet)
print(f"[+] 发送 HandshakeResponse41 (seq={seq+1})")
# 4. 接收认证结果
header = sock.recv(4)
resp_len = struct.unpack('<I', header[0:3] + b'\x00')[0]
resp_seq = header[3]
response = b''
while len(response) < resp_len:
response += sock.recv(resp_len - len(response))
print(f"[+] 收到响应 (seq={resp_seq}, len={resp_len})")
if response[0] == 0x00:
print("[✓] 认证成功! (OK_Packet)")
# 解析 OK 包
affected_rows, _ = read_length_encoded_integer(response, 1)
print(f" affected_rows: {affected_rows}")
elif response[0] == 0xFF:
# ERR_Packet
error_code = struct.unpack('<H', response[1:3])[0]
error_msg = response[9:].decode('utf-8', errors='replace')
print(f"[✗] 认证失败! (ERR_Packet)")
print(f" 错误代码: {error_code}")
print(f" 错误消息: {error_msg}")
elif response[0] == 0xFE:
print("[*] 需要 AuthMoreData (额外认证步骤)")
else:
print(f"[?] 未知响应: {response.hex()}")
except Exception as e:
print(f"[-] 错误: {e}")
finally:
sock.close()
if __name__ == '__main__':
minimal_handshake(
host='127.0.0.1',
port=3306,
username='root',
password='your_password_here',
database='test'
)
运行测试
# 确保 MySQL 服务器允许 mysql_native_password 认证
mysql -u root -p -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password_here';"
# 运行脚本
python3 mysql_minimal_handshake.py
2.8 用 Go 实现握手
// mysql_handshake.go
package main
import (
"crypto/sha1"
"encoding/binary"
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:3306")
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Println("[+] TCP 连接建立")
// 读取 HandshakeV10
header := make([]byte, 4)
conn.Read(header)
pktLen := uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16
seq := header[3]
payload := make([]byte, pktLen)
conn.Read(payload)
fmt.Printf("[+] HandshakeV10: seq=%d, len=%d\n", seq, pktLen)
// 解析协议版本
protoVer := payload[0]
fmt.Printf(" 协议版本: %d\n", protoVer)
// 解析服务器版本
end := 1
for payload[end] != 0 {
end++
}
version := string(payload[1:end])
fmt.Printf(" 服务器版本: %s\n", version)
// 解析连接 ID
connID := binary.LittleEndian.Uint32(payload[end+1 : end+5])
fmt.Printf(" 连接 ID: %d\n", connID)
fmt.Println("[✓] 握手包解析完成")
}
func mysqlNativePassword(password string, scramble []byte) []byte {
if len(password) == 0 {
return nil
}
h1 := sha1.Sum([]byte(password))
h2 := sha1.Sum(h1[:])
h3 := sha1.Sum(append(scramble, h2[:]...))
result := make([]byte, 20)
for i := 0; i < 20; i++ {
result[i] = h1[i] ^ h3[i]
}
return result
}
2.9 注意事项
重要提醒
scramble 长度:MySQL 8.0 服务器发送的 scramble 通常为 32 字节,但
auth_plugin_data_length字段的值是 21(= 8 + 13)。实际第二部分长度为max(13, auth_plugin_data_length - 8)。能力标志交集:客户端的
capability_flags必须是服务器声明的子集(按位与)。设置服务器不支持的标志可能导致不可预期的行为。序列号连续性:握手阶段的序列号是连续的。服务器发送 seq=0,客户端回复 seq=1,如果有多次认证交换则继续递增。
保留字节:
HandshakeV10和HandshakeResponse41中的保留字节必须为全零。某些 MySQL 分支(如 MariaDB)可能使用保留字节存储额外信息。编码注意:
server_version和auth_plugin_name是 ASCII 字符串,但username和database应该使用连接协商的字符集编码。超时处理:服务器的
connect_timeout(默认 10 秒)限制了握手完成的时间。如果客户端在超时前未完成握手,服务器会主动断开连接。
2.10 业务场景
场景一:连接池的握手优化
连接池(如 HikariCP、Druid)在初始化时会预建立一定数量的 MySQL 连接。理解握手过程有助于:
- 计算连接建立的延迟(2-3 个 RTT)
- 评估认证加密的 CPU 开销
- 合理设置连接池的最小空闲连接数
场景二:故障转移时的重连
当 MySQL 主节点故障、VIP 漂移到备节点时,客户端需要重新握手。如果使用了 caching_sha2_password 且备节点没有缓存,可能需要完整的 RSA 公钥交换,增加重连延迟。
场景三:跨版本迁移
从 MySQL 5.7 升级到 8.0 时,认证插件从 mysql_native_password 变为 caching_sha2_password。旧客户端可能无法完成握手,需要提前修改用户认证方式或升级客户端驱动。
2.11 扩展阅读
- MySQL Internals: HandshakeV10 Packet
- MySQL Source: sql/auth/sha256_password.cc
- MySQL 8.0: caching_sha2_password 插件
- MariaDB Protocol: Handshake
上一章:01 - MySQL 协议概述 下一章:03 - 认证机制 —— 深入分析各种认证插件的工作原理。