根本原因是innodb在compact/redundant格式下,text/blob超768字节即移至溢出页,主记录仅存20字节指针,导致select *等操作必须多次随机读取溢出页。

TEXT/BLOB字段触发溢出页读取,导致多次随机I/O
根本原因不是“数据大”,而是InnoDB的存储机制:只要行格式是COMPACT或REDUNDANT,且TEXT/BLOB内容长度超过768字节,就会把主体内容挪到独立的溢出页(off-page),主记录只留20字节指针。一次SELECT *看似读1行,实际要先读聚簇索引页,再根据指针跳转读1个或多个溢出页——机械盘上单次跳转可能耗时几毫秒,SSD上虽快,但频繁触发仍会打爆innodb_buffer_pool_size。
常见错误现象:
- EXPLAIN显示
type=ALL、rows不大,但Query_time动辄1s+,且Rows_examined远大于Rows_sent -
SHOW PROFILE FOR QUERY N中Handler_read_rnd_next值异常高 -
SHOW ENGINE INNODB STATUS里Buffer pool hit rate从99%骤降到85%以下
ORDER BY或GROUP BY TEXT会强制落盘临时表
MySQL的MEMORY引擎不支持TEXT/BLOB类型,所以只要排序或分组涉及这类字段(哪怕只是ORDER BY created_at但表里有TEXT列),优化器就会放弃内存临时表,直接创建磁盘临时表(MyISAM格式)。这和你设多大的tmp_table_size无关。
实操判断方式:
- EXPLAIN结果出现
Using temporary; Using filesort,且key为NULL - 慢查询日志里
Sort_merge_passes持续上涨 - 即使只查10条记录,
Created_tmp_disk_tables也在增长
绕开办法:用虚拟列物化排序依据,比如ALTER TABLE article ADD content_hash CHAR(32) AS (MD5(content)) STORED,然后ORDER BY content_hash——前提是业务允许哈希等价替代。
SELECT * 是最隐蔽也最普遍的性能陷阱
很多应用没意识到,只要表结构里定义了TEXT/BLOB字段,即使SQL里没写它,ORM自动映射、驱动预取、甚至某些隔离级别下的MVCC版本链访问,都可能导致整行(含溢出页)被加载。这不是bug,是InnoDB在保证一致性时的保守策略。
真正有效的规避方式只有三个:
- 永远显式列出所需字段,禁用
SELECT * - 对高频查询路径,把TEXT/BLOB字段垂直拆到独立表(如
article_content),主表只留id,JOIN时按需拉取 - 超1MB的内容直接移出数据库,主表只存URL和
content_md5校验值
注意:WHERE content LIKE '%xxx%'这种写法无法走任何B-tree索引,前缀索引(INDEX(content(100)))只对LIKE 'xxx%'有效;全文索引又受限于ft_min_word_len,短词搜不到。
选错类型会让问题雪上加霜
TEXT和BLOB不是“越大越好”。滥用LONGTEXT会导致InnoDB更激进地启用off-page存储——哪怕你只存1KB,只要行格式是DYNAMIC且内容超½页(约8KB),就可能溢出;而TINYTEXT(255字节)放在COMPACT下根本不会溢出,完全走本地页。
关键区别:
- 存纯文本、需要
ORDER BY、LIKE 'xxx%'、字符集校验 → 用TEXT系列 - 存图片、PDF、加密二进制流 → 必须用
BLOB系列,否则字符集转换会损坏数据 -
BLOB字段不能建前缀索引以外的索引,WHERE blob_col = ?只能全表扫描
最容易被忽略的一点:ALTER TABLE ... ROW_FORMAT=DYNAMIC必须配合innodb_file_per_table=ON才生效,否则即使指定DYNAMIC,也会退化回COMPACT行为。











