强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 位的能力标志是整个协议的核心。它定义了客户端和服务器各自支持的特性。只有双方都声明支持的特性才会启用

关键能力标志列表:

名称说明
0CLIENT_LONG_PASSWORD使用新式(> 4.1)密码哈希
2CLIENT_LONG_FLAG使用更长的字段标志
3CLIENT_CONNECT_WITH_DB可以在连接时指定数据库
5CLIENT_COMPRESS支持 zlib 压缩
6CLIENT_ODBCODBC 客户端
7CLIENT_LOCAL_FILES可以使用 LOAD DATA LOCAL
8CLIENT_IGNORE_SPACE忽略函数名后的空格
9CLIENT_PROTOCOL_41使用 4.1 协议格式(必须)
10CLIENT_INTERACTIVE交互式客户端
11CLIENT_SSL支持 SSL/TLS
13CLIENT_TRANSACTIONS支持事务状态追踪
15CLIENT_SECURE_CONNECTION使用新的认证方式
16CLIENT_MULTI_STATEMENTS支持多语句执行
17CLIENT_MULTI_RESULTS支持多结果集
19CLIENT_PLUGIN_AUTH支持认证插件
20CLIENT_CONNECT_ATTRS支持连接属性
21CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA认证数据使用长度编码
22CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS能处理过期密码
23CLIENT_SESSION_TRACK支持会话状态追踪
25CLIENT_DEPRECATE_EOF用 OK 包替代 EOF 包
27CLIENT_OPTIONAL_RESULTSET_METADATA结果集元数据可选
29CLIENT_ZSTD_COMPRESSION_ALGORITHM支持 zstd 压缩

character_set (1 字节)

字符集 ID,常用值:

ID字符集
8latin1
33utf8 (utf8mb3)
45utf8mb4
255utf8mb4 (MySQL 8.0 的 45 和 255 都指向 utf8mb4)

status_flags (2 字节)

服务器状态标志,常见值:

名称说明
SERVER_STATUS_IN_TRANS0x0001在事务中
SERVER_STATUS_AUTOCOMMIT0x0002自动提交已开启
SERVER_STATUS_CURSOR_EXISTS0x0040游标已打开
SERVER_STATUS_NO_GOOD_INDEX_USED0x0010未使用好索引
SERVER_STATUS_NO_INDEX_USED0x0020未使用索引

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):

字节数值范围编码方式
10-250直接存储
3251-655350xFC + 2 字节小端序
465536-167772150xFD + 3 字节小端序
9> 167772150xFE + 8 字节小端序

2.4 SSL/TLS 升级握手

如果客户端需要 SSL 加密,必须在发送 HandshakeResponse41 之前先发送一个简短的 SSLRequest 包,然后切换到 TLS 通信。

SSL 升级流程

Client                                    Server
  │                                          │
  │ ←── HandshakeV10 (明文) ────────────── │
  │                                          │
  │ ──── SSLRequest (明文, seq=1) ────────→ │
  │                                          │
  │ ══════ TLS 握手 (加密通道建立) ════════  │
  │                                          │
  │ ──── HandshakeResponse41 (加密) ─────→ │
  │ ←── OK/ERR (加密) ────────────────── │
  │                                          │
  │     == 后续所有通信加密 ==               │

SSLRequest 数据包

SSLRequestHandshakeResponse41精简版,只包含前 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 注意事项

重要提醒

  1. scramble 长度:MySQL 8.0 服务器发送的 scramble 通常为 32 字节,但 auth_plugin_data_length 字段的值是 21(= 8 + 13)。实际第二部分长度为 max(13, auth_plugin_data_length - 8)

  2. 能力标志交集:客户端的 capability_flags 必须是服务器声明的子集(按位与)。设置服务器不支持的标志可能导致不可预期的行为。

  3. 序列号连续性:握手阶段的序列号是连续的。服务器发送 seq=0,客户端回复 seq=1,如果有多次认证交换则继续递增。

  4. 保留字节HandshakeV10HandshakeResponse41 中的保留字节必须为全零。某些 MySQL 分支(如 MariaDB)可能使用保留字节存储额外信息。

  5. 编码注意server_versionauth_plugin_name 是 ASCII 字符串,但 usernamedatabase 应该使用连接协商的字符集编码。

  6. 超时处理:服务器的 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 扩展阅读


上一章01 - MySQL 协议概述 下一章03 - 认证机制 —— 深入分析各种认证插件的工作原理。