在 MySQL 5.7 上踩的几个坑


最近把一批含大量生僻字的中文数据导进一台云上的 MySQL 5.7,前面挂了一个表格式的数据浏览工具和一个自己写的小网页。数据量不大,坑却不少。按“最难发现”排序记下来。

1. 所有四字节汉字都“相等” §

这是最危险的一个,因为它不报错。

utf8mb4 能存四字节字符,Unicode 扩展区的汉字(𬶍、𡌶、𣿀 这类)都在这里。但 5.7 默认的排序规则 utf8mb4_unicode_ci 基于很老的 UCA 4.0.0,它对所有补充平面字符给的是同一个权重:

SELECT '𬶍' = '𡌶';   -- 返回 1

后果是:按名字去重会把两个不同的地名合成一个;WHERE name = '𬶍兴巷' 会查出一堆毫不相干的行;唯一索引会莫名其妙地报重复。地名、人名这类以汉字为主、又可能含生僻字的列,一律用 utf8mb4_bin。库的默认规则可以不动,只改这些列。MySQL 8.0 的 utf8mb4_0900_* 规则没有这个问题。

顺带:托管实例的服务器默认字符集可能还是 latin1,建库、建表、连接时都要显式写 utf8mb4。

2. 没有 CTE,现代工具会零星出错 §

5.7 不支持 WITH 公用表表达式,也没有窗口函数。很多新版本的工具默认按 8.0 写 SQL,在 5.7 上不会整体崩溃,而是某个功能单独报错:我遇到的是数据浏览工具的“分组”功能,生成的查询带 WITH,直接语法错误。

同类问题还有日期格式:工具写入 '2026-09-16T14:38:10.364Z' 这种 ISO 字符串,5.7 在严格模式下拒收(Incorrect datetime value);NO_ZERO_DATE、explicit_defaults_for_timestamp 这几个参数也会让一些工具的建表迁移失败。数据本身不会坏,但会让人以为坏了。如果能选,直接上 8.0。

3. 半死的长连接 §

自己写的网页用一条长连接访问数据库。晚上闲置了五个小时,第二天第一个请求开始,整个页面卡死,nginx 报 504。

原因是连接在中间被静默掐断了:服务器和数据库之间走的是云上的内网互通,中间网关的连接表会清掉长时间空闲的流,而且不通知任何一端。程序拿着这条“看起来还活着”的 socket 发查询,包被丢弃,没有回包也没有拒绝,TCP 层要重传十几分钟才放弃。偏偏程序只开了一条连接加一把锁,第一个请求占着锁等,后面所有请求跟着排队。

ping(reconnect=True) 救不了这种情况,它能发现服务端主动关闭的连接,但对静默丢包一样只会干等。真正的修法是三件事:给连接设读写超时(我用 20 秒),出错后丢弃旧连接重连,再重试一次。

一个额外的教训:未登录的请求不碰数据库,所以外部探测一直显示“服务正常”。健康检查最好走一遍真实的数据库查询。

4. 云数据库的外网通道很慢 §

同一地域的服务器,经数据库的外网地址拉 64 MB 用了 27.8 秒,大约 2.3 MB/s;打通内网后同样的查询 0.8 秒。外网地址走的是公共代理,带宽不保底。能走内网就走内网,然后把外网地址关掉。外网地址开着的那段时间,错误日志里每 20 秒就有一次陌生 IP 在试 root 密码。

另外,托管实例一般关掉了 local_infile,LOAD DATA LOCAL 用不了,只能用分批的多行 INSERT。每批几千行,一百万行十几分钟。

5. 空间数据的两个限制 §

5.7 可以存 POINT 并建空间索引,也有 ST_Distance_Sphere,做“附近的点”查询够用。但它没有列级 SRID(那是 8.0 才有的),坐标系只能靠约定并写进文档;空间索引也不能建在允许 NULL 的列上。我的做法是把有坐标的行单独放一张 NOT NULL POINT 的表。


回头看,第 1 条和第 3 条是真正会咬人的:一个静默地合并数据,一个静默地卡死服务。其余几条都会报错,查一下就明白。


最后修改:2026-10-24
粤ICP备2026145812号-1
粤公网安备44011302005976号