TEXT字段参与ORDER BY或GROUP BY会强制落盘,因MEMORY引擎不支持TEXT/BLOB类型;即使内存充足,优化器也会直接创建磁盘临时表,需通过排除TEXT字段、物化排序列或拆表等方式规避。

TEXT 字段直接参与 ORDER BY 或 GROUP BY 时,几乎必然触发磁盘临时表 —— 不是因为内存不够,而是 MySQL 禁止在内存临时表中存储 TEXT/BLOB 类型。
为什么 TEXT 字段会让临时表强制落盘
MySQL 的内存临时表(MEMORY 引擎)不支持 TEXT、BLOB 类型。只要查询中 SELECT、ORDER BY 或 GROUP BY 涉及到 TEXT 字段,哪怕只取前 10 行、只排序 id,优化器也会放弃内存临时表,直接创建磁盘临时表(MyISAM 或 InnoDB)。
- EXPLAIN 显示
Using temporary; Using filesort,且执行计划里type是ALL或key为NULL,基本可断定是 TEXT 拖累的 - 即使
tmp_table_size和max_heap_table_size都设到 512M,只要语句里带ORDER BY content或GROUP BY SUBSTRING(content, 1, 100),照样写磁盘 - ORM 自动生成的
SELECT *+ORDER BY created_at是高危组合:TEXT 字段虽未参与排序,但因类型限制,整个临时表无法驻留内存
绕开 TEXT 参与排序/分组的实操写法
核心原则:让排序和分组逻辑完全脱离 TEXT 字段本身,用索引覆盖 + 衍生字段替代。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 把排序字段提前物化:比如正文更新时间实际存在
content_updated_atDATETIME 列,就用它代替ORDER BY SUBSTRING(content, 1, 1) - 对 TEXT 内容做轻量哈希或摘要,存为新列并建索引:
ALTER TABLE article ADD content_sha256 CHAR(64) GENERATED ALWAYS AS (SHA2(content, 256)) STORED,然后ORDER BY content_sha256 - 绝对避免
ORDER BY UPPER(content)、GROUP BY JSON_EXTRACT(content, '$.title')这类函数调用 —— 它们既无法走索引,又强制引入 TEXT 类型到临时表 - 如果必须按内容特征排序(如关键词密度),改用外部处理:应用层读出 ID 列表后,在内存或 Redis 中计算并排序,再回查详情
不改表结构时的紧急缓解手段
当无法加字段、无法拆表,又得快速止血,优先从 SQL 层压制 TEXT 的“存在感”:
- 显式排除 TEXT 字段:
SELECT id, title, author, created_at FROM article WHERE ... ORDER BY created_at DESC,确保 SELECT 列表里没有 TEXT - 用子查询隔离 TEXT 加载:
SELECT a.id, a.title, c.content FROM (SELECT id, title FROM article ORDER BY created_at DESC LIMIT 20) a JOIN article_content c ON a.id = c.article_id(假设 content 已拆到独立表) - 对摘要展示场景,用
SUBSTRING(content, 1, 300)替代全量读取 —— MySQL 8.0+ 对溢出列的 SUBSTRING 有优化,不会加载整块溢出页 - 确认
innodb_large_prefix = ON且ROW_FORMAT = DYNAMIC,减少小 TEXT 值(
监控与验证是否真被 TEXT 拖累
别依赖猜测。上线前后对比三个关键信号:
- 查状态变量:
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables',观察该值在执行可疑 SQL 前后是否跳涨 - 抓慢日志加
log_queries_not_using_indexes = ON,过滤出含Using temporary; Using filesort且涉及content、body等字段名的记录 - 用
EXPLAIN FORMAT=JSON看query_block下的using_temporary_table是否为true,以及filesort的table_type是INNODB还是MEMORY
最隐蔽的坑是:你改了 SQL,但 ORM 在背后又拼了个 SELECT *。所有优化都得落在最终发给 MySQL 的那条语句上,而不是应用代码里的“意图”。

















