根本原因是compact行格式+antelope文件格式导致text/blob内容被截断存储,引发多次随机i/o;应改用dynamic行格式、显式查询字段、应用层压缩并定期optimize table。

MySQL 5.7 中 TEXT/BLOB 查得慢、占缓存、易 OOM,根本原因不是字段大,而是默认用 COMPACT 行格式 + Antelope 文件格式,导致长内容被截前 768 字节存主页、其余扔溢出页——一次读触发多次随机 I/O。
确认当前表是否踩了 COMPACT + Antelope 的坑
先查配置和表结构,别猜:
-
SELECT @@innodb_file_per_table必须为1(否则所有表共用 ibdata1,溢出页更难管理) -
SHOW VARIABLES LIKE 'innodb_file_format'必须是Barracuda(Antelope不支持 DYNAMIC) -
SELECT TABLE_NAME, ROW_FORMAT FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table'—— 若返回COMPACT,就是性能瓶颈源头
必须改行格式为 DYNAMIC 并重建表
COMPACT 是 MySQL 5.7 默认,但对 TEXT/BLOB 最不友好;DYNAMIC 才能让整段内容全挪到溢出页,主页只留 20 字节指针,减少 page split 和随机读。
- 先确保
innodb_file_format = Barracuda已在 my.cnf 中设置,并重启 mysqld - 执行
ALTER TABLE your_table ROW_FORMAT=DYNAMIC;(注意:仅修改元数据,不立即重写数据) - 真正生效要
ALTER TABLE your_table ENGINE=InnoDB;或OPTIMIZE TABLE your_table,强制重建聚簇索引 - 重建后验证:
SHOW CREATE TABLE your_table应含ROW_FORMAT=DYNAMIC
查询时永远绕开大字段,别信 ORM 的 SELECT *
哪怕行格式已改,SELECT * 仍会拉取溢出页,Buffer Pool hit rate 会断崖下跌,EXPLAIN 里 rows_examined >> rows_sent 就是典型信号。
- 显式列出非大字段:
SELECT id, title, created_at FROM articles,彻底避开content MEDIUMTEXT - 子查询中绝不能出现
SELECT *,尤其嵌套在 JOIN 或 CTE 里——中间结果集会把 BLOB 全载入内存 - 需要内容时,用主键二次查:
SELECT content FROM articles WHERE id = ?,让应用层按需加载 - 若必须裁剪展示,用
SUBSTRING(content, 1, 500)替代全量读,避免传输和解析开销
TEXT 比 BLOB 更合适存文本,但压缩必须在应用层做
BLOB 不走字符集校验,ORDER BY、LIKE 'xxx%'、前缀索引全失效;而 TEXT 支持 utf8mb4、能建全文索引、排序语义明确。
- 把
MEDIUMBLOB改成MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci - 压缩别用
COMPRESS()函数——它返回 zlib 二进制,客户端解压逻辑耦合紧,且字段类型必须升级(如MEDIUMBLOB),否则插入时可能截断 - 纯文本压缩率 60%~80%,JSON/HTML/日志都适合;但图片、PDF 再压无效,白耗 CPU
- 压缩后字段别建任何索引:
UNCOMPRESS(content)无法被索引加速,LIKE和前缀索引也完全失效
最常被忽略的一点:DYNAMIC 行格式不会自动清理旧溢出页。表重建后,原溢出页仍留在 .ibd 文件里,直到执行 OPTIMIZE TABLE 或重启并触发 purge 线程回收——磁盘空间不会立刻释放。











