text/blob字段查询慢因innodb将超768字节内容存溢出页,主记录仅留20字节指针,导致多次随机i/o;order by或全字段映射易引发oom或缓存污染;优化需避免select *、用虚拟列哈希过滤、压缩在应用层、大文本应移至对象存储。

TEXT/BLOB字段为什么一查就慢
因为InnoDB默认把超过768字节的TEXT或BLOB值挪到独立溢出页,主记录只留20字节指针。一次SELECT *看似只读1行,实际要触发多次随机I/O——先读主页,再跳去读一个或多个溢出页。机械盘上延迟明显,SSD上也容易打爆buffer pool。
更麻烦的是:如果查询带ORDER BY content或GROUP BY content,MySQL会把整段内容拉进内存排序,sort_buffer_size不够直接OOM;用ORM自动映射全字段时,客户端内存也可能被撑爆。
- EXPLAIN里
type=ALL+rows极大,但Extra没写Using index,大概率是大字段拖了后腿 -
SHOW ENGINE INNODB STATUS中看到Buffer pool hit rate骤降,说明大字段正在污染缓存 - 慢查询日志里语句本身不复杂,但
Query_time动辄秒级,且Rows_examined远大于Rows_sent
必须存数据库时,怎么选类型和压缩
存文本别用BLOB——它不走字符集校验,ORDER BY、LIKE 'xxx%'、前缀索引全失效。一律改用MEDIUMTEXT(上限16MB),并显式指定字符集:
ALTER TABLE log MODIFY content MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
压缩必须在应用层做,MySQL的COMPRESS()函数返回zlib二进制,客户端得配套解压,且压缩后字段类型要升级(比如原BLOB→MEDIUMBLOB),否则可能截断。
- 纯文本压缩率高(60%~80%),JSON/HTML/日志都适合;已压缩图片、PDF再压基本无效,还白耗CPU
- 别对压缩后字段建索引——二进制无语义,
LIKE和前缀索引全失效 - 如果业务强依赖事务原子性(如审计日志必须和主记录一起提交),压缩是唯一可行的库内方案
查询时绕开大字段的三种硬招
核心目标:让执行计划不触碰溢出页。以下写法MySQL 5.7+和8.0均有效,无需改应用代码。
- 永远不用
SELECT *,显式列出非大字段:SELECT id, title, created_at FROM article,确保Extra里不出现Using filesort后还加载content - 需要按内容过滤但又不想读全文?加虚拟列哈希:
ALTER TABLE article ADD COLUMN content_hash CHAR(32) AS (MD5(content)) STORED;,再建索引CREATE INDEX idx_content_hash ON article(content_hash),查询时写WHERE content_hash = MD5('xxx') AND content = 'xxx'(后半段防哈希碰撞) - 真要读大字段内容?用
SUBSTRING(content, 1, 1024)分块取,或应用层流式读取,避免一次性载入几MB到内存
彻底甩掉大字段的终极方案
绝大多数场景下,LONGBLOB或LONGTEXT根本不该进MySQL——它不是为扛1MB+数据设计的。真实可行的路径只有一条:文件系统或对象存储存正文,数据库只留元数据。
- 删掉原
content LONGBLOB列,新增file_path VARCHAR(512),上传时生成uuid4()+ext存到/data/uploads/或OSS/S3 - Web服务收到请求后,直接
readfile()或curl对象存储URL返回流,完全绕过MySQL网络传输 - 务必加清理机制:定时任务扫描
file_path未被任何记录引用的文件,90天未关联则rm,否则磁盘必涨满
这个方案的复杂点不在技术实现,而在于业务逻辑里所有涉及“读内容”的地方,都要从SELECT content切换成GET /files/{path}——漏掉一处,性能优化就归零。











