“Got a packet bigger than max_allowed_packet bytes”(ERROR 1153)是写入大字段、批量插入、导入 dump 时的常见报错,但很多人在 Python 里根本看不到 1153,只看到 2006 server has gone away。这篇用 1 MiB 的服务器上限在本机复现,看清楚到底差多少字节、调大要怎么调、批量插入为什么有时能躲过去。
实例二默认 @@max_allowed_packet = 67108864(64 MiB);实例一 = 1048576 PyMySQL insert 100 KB 的字符串 -> ok PyMySQL insert 2 MB 的字符串 -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))") 同一个连接接着执行 select 1 -> InterfaceError: (0, '') 命令行: mysql d < big.sql(一条 2097185 字节的 INSERT)-> ERROR 1153 (08S01) at line 1: Got a packet bigger than 'max_allowed_packet' bytes 命令行: select 1 对比 -> 1 表里的行数: 1(只有那次成功的 100 KB) 服务器错误日志里含 packet 的行: (没有)
服务器遇到超限的包会直接断开连接,命令行客户端能读到 1153,PyMySQL 往往只看到 2006 加 Broken pipe,之后同一个连接不可用。
二分查找:整条 SQL 文本最长 1048574 字节还能执行(上限 1048576,差 2) insert 一个 str 参数:最大 1048543 个字符(整条语句 = 数据 + 31 字节) insert 一个 bytes 参数:最大 524271 字节,约是上面的一半;原因:PyMySQL 拼出的 SQL 是 "insert into b(data) values (X'616263')" str 里全是单引号:最大 524271 个字符(每个单引号在 SQL 里要写成两个字符)
整条 SQL 文本最长比 max_allowed_packet 少 2 字节;bytes 参数被拼成十六进制,有效数据只有一半;单引号这类要转义的字符也会翻倍。
SET GLOBAL max_allowed_packet = 4194304 -> [] 已经连着的老连接: @@session.max_allowed_packet = 1048576,@@global = 4194304 老连接放 2 MB 的字符串 -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))") 新连接: @@session.max_allowed_packet = 4194304 新连接 SET SESSION max_allowed_packet = 8388608 -> OperationalError: (1621, "SESSION variable 'max_allowed_packet' is read-only. Use SET GLOBAL to assign the value") 新连接放 2 MB 的字符串 -> ok 另一个新连接放 5 MB 的字符串 -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
SET GLOBAL 只对之后的新连接生效,老连接要重连;会话级变量只读,SET SESSION 报 1621;SET GLOBAL 重启后失效。
PyMySQL cursor.max_stmt_length = 1024000 服务器上限 1 MiB:executemany 50000 行(每行 100 字节,约 5 MB)-> ok,0.25 s,表里 50000 行 把服务器上限改成 524288(比 max_stmt_length 小一半)后同样的 executemany -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))") 手写一条 9000 行的多行 INSERT -> ok(945024 字节) 手写一条 11000 行的多行 INSERT -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")(语句 1155024 字节) 对照:同样 11000 行,用命令行客户端读文件执行 -> ERROR 1153 (08S01) at line 1: Got a packet bigger than 'max_allowed_packet' bytes
PyMySQL 的 executemany 在 max_stmt_length(1024000)处切分;服务器上限不小于它时安全,小于它时仍然 2006。手写超长的多行 INSERT 没有这层保护。
默认服务器上有一行 20 MiB 的数据: ((1, 20971520),) 命令行客户端 --help 里 max-allowed-packet 的默认值: 16777216(16 MiB) 命令行客户端用默认值读这一行 data -> ERROR 2020 (HY000) at line 1: Got packet bigger than 'max_allowed_packet' bytes 命令行客户端 --max-allowed-packet=33554432(32 MiB)读 -> 读到 20971520 字节 命令行客户端 --max-allowed-packet=1048576 只读 length(data) 这个数字 -> 20971520 PyMySQL 读同一行(它的 Connection.max_allowed_packet = 16777216)-> 读到 20971520 字节
ERROR 2020 是客户端侧的 max_allowed_packet,命令行客户端默认 16 MiB,读超过它的单行会失败,改服务器的值没用。