应强制物化子查询的典型场景包括:子查询被外层多次引用(如在SELECT、WHERE、JOIN中重复出现),且结果集行数可控;含volatile函数或统计信息不准导致优化器误判;或明确需避免重复执行开销时,可加MATERIALIZED关键字或调整optimizer_switch参数。

子查询物化不是默认行为,必须显式触发或依赖优化器判断;盲目依赖自动物化反而可能增加I/O——尤其在小结果集、高并发场景下。
什么时候该强制物化子查询?
物化(Materialization)本质是把子查询结果先写入临时表再复用,它对I/O的影响取决于“写一次、读多次”的收益是否大于临时表落盘开销。典型适合场景包括:
- 子查询被外层多次引用(如在
SELECT、WHERE、JOIN中重复出现),且结果集行数可控(建议 - 子查询含聚合或排序,且外层无法利用索引下推(例如
WHERE x IN (SELECT y FROM t GROUP BY z)) - 执行计划中显示子查询被反复执行(
rows_examined在EXPLAIN ANALYZE中显著放大) - 数据库版本支持物化提示:MySQL 8.0.24+ 支持
/*+ MATERIALIZE() */,Doris 支持/*+ SET_VAR(use_spill_to_disk=off) */配合物化,Oracle 可用MATERIALIZE提示
MySQL 8.0+ 中如何用提示控制物化并压测I/O效果?
MySQL 默认对 IN (subquery) 或 FROM (subquery) 可能走半连接或物化,但不保证。要精准干预,必须用优化器提示:
- 强制物化:在子查询前加
/*+ MATERIALIZE(t1) */,其中t1是子查询别名,例如:SELECT * FROM orders o WHERE o.customer_id IN ( /*+ MATERIALIZE(c) */ SELECT c.id FROM customers c WHERE c.status = 'active' );
- 禁用物化(防止误物化大结果):用
/*+ NO_MERGE() */或/*+ NO_MATERIALIZE() */(MySQL 8.0.30+) - 验证是否生效:执行
EXPLAIN FORMAT=TREE,看到MATERIALIZED关键字即确认物化;同时查performance_schema.events_statements_history_long中的tmp_tables和tmp_table_sizes字段,对比物化前后磁盘临时表使用量 - 注意陷阱:若物化结果集超过
tmp_table_size或max_heap_table_size,会强制落盘到磁盘临时表(MyISAM或InnoDB),此时I/O不降反升——务必同步调大这两个参数
为什么物化后物理I/O反而升高?常见三类原因
物化不是银弹,I/O恶化往往源于配置与数据特征错配:
-
tmp_table_size过小,导致本可在内存完成的物化被迫写入磁盘临时表,产生大量innodb_data_written和Handler_write指标飙升 - 子查询结果含大字段(如
TEXT、BLOB、长VARCHAR),即使行数少,单行体积大,极易触发磁盘落盘 - 并发执行同一物化子查询时,MySQL 8.0 默认不共享物化结果(每个会话独立物化),造成N倍I/O放大;此时应改用
CREATE TEMPORARY TABLE手动物化 + 显式重用
真正降低I/O的关键,不在“是否物化”,而在“物化什么、物化多大、物化给谁用”。一个被反复扫描的10万行中间结果,物化后可能减少90%的主表随机IO;但一个含500MB BLOB的200行结果,物化只会让磁盘队列更长。别迷信提示,先看 EXPLAIN ANALYZE 的实际 temp_table_size 和 disk_reads 数值。










