text/blob字段强制触发磁盘临时表,因memory引擎不支持该类型;即使tmp_table_size设为2g,只要查询涉及text列(含select *、order by或group by),优化器即放弃内存表,导致i/o激增和性能下降。

因为 MEMORY 引擎根本不支持 TEXT/BLOB 类型,只要排序、分组或 JOIN 涉及这类字段,MySQL 会直接跳过内存临时表,强制创建磁盘临时表——哪怕 tmp_table_size 设到 2G 也没用。
为什么 EXPLAIN 看不出问题,但查询就是慢?
EXPLAIN 只分析执行计划,不模拟内存分配逻辑。它可能显示 Using temporary; Using filesort,但不会告诉你“这个 temporary 是磁盘的”。真正线索藏在运行时指标里:
-
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables'增量突增,说明大量落盘 -
SHOW PROFILE FOR QUERY N中出现Copying to tmp table on disk阶段 - 慢查询日志里
Rows_examined远大于Rows_sent,且Sort_merge_passes持续上涨
TEXT 字段怎么“悄悄”触发落盘?
不是内容长才落盘,而是字段类型本身就会绕过内存临时表。哪怕你只写 SELECT id, title FROM article ORDER BY created_at,只要 article 表里定义了 content TEXT,优化器仍可能因元数据感知而放弃 MEMORY 引擎。
- MySQL 在生成执行计划前,会扫描所有 SELECT 列的字段类型;一旦发现 TEXT/BLOB,立即标记该查询“不可内存化”
- 即使没 SELECT TEXT 字段,但表结构含 TEXT,且查询用了
SELECT *或 ORM 自动生成全字段读取,照样触发 -
GROUP BY SUBSTRING(content, 1, 50)这种操作,本质仍是 TEXT 参与计算,强制落盘
怎么绕开 TEXT 导致的磁盘临时表?
核心是让排序、分组、JOIN 的中间过程完全不触碰 TEXT 字段本体,用轻量代理字段替代。
- 给排序字段单独建覆盖索引,例如
ALTER TABLE article ADD INDEX idx_created_uid (created_at, user_id),确保ORDER BY created_at, user_id全走索引,不拉记录 - 把 TEXT 摘要提前物化:加一列
content_md5 CHAR(32),建索引后用于分组或去重,GROUP BY content_md5不再触发临时表 - 分页场景避免
SELECT *:用主键驱动分页,先查SELECT id FROM article WHERE ... ORDER BY created_at LIMIT 100000, 20,再用IN (id1,id2,...)回表取 TEXT - 应用层截断:ORM 查询时显式指定字段,禁用
SELECT *,尤其避免在分页、导出类接口中拖带 TEXT
最容易被忽略的是:改了 tmp_table_size 却不调 max_heap_table_size,MySQL 实际取两者较小值——哪怕你设了 512M,另一个还是默认 16M,结果仍是磁盘表。另外,云 RDS 上 tmpdir 分区空间不足也会报 The table '/data/mysql/zst/tmp/#sql_13975_23' is full,得一起查 df -h $(mysql -Nse "SELECT @@tmpdir")。











