MySQL视图含GROUP BY等时强制DERIVED物化,EXPLAIN见select_type为DERIVED且rows接近总行数即证实;SHOW STATUS查Created_tmp_disk_tables超15%表明临时表落盘;ORDER BY未下推会导致全量排序;嵌套超31层MySQL解析器栈溢出;sp_head::main_mem_root内存长期占用超1GB且伴游标未CLOSE可致OOM。

查EXPLAIN确认是否触发DERIVED物化
MySQL遇到含GROUP BY、DISTINCT、UNION或非相关子查询的视图,会放弃MERGE优化,强制走DERIVED路径——整个中间结果先生成临时表再处理。哪怕你只加了LIMIT 10,它也得把几百万行全读进内存。
执行EXPLAIN SELECT * FROM my_view LIMIT 10,重点看select_type列:出现DERIVED就坐实物化;再对比rows值是否接近底层表总行数(比如500万)。如果接近,说明数据库真在内存里塞了全部数据。
顺手查下SHOW STATUS LIKE 'Created_tmp_disk_tables',若该值持续上涨、且与Created_tmp_tables比值超过15%,基本确认临时表已落盘——这是内存撑不住的直接证据。
验证ORDER BY是否被下推到视图内部
视图定义里写了ORDER BY created_at DESC,外层再加LIMIT 10,绝大多数情况下不会下推。PostgreSQL对CTE或LATERAL视图不支持下推;MySQL 5.7+虽能下推,但前提是视图里没用HAVING、窗口函数或DISTINCT。
用EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM my_view LIMIT 10(PG)或EXPLAIN FORMAT=TRADITIONAL SELECT * FROM my_view ORDER BY id DESC LIMIT 10(MySQL),看Plan Rows或rows是否远大于10。如果是,说明排序发生在截断前,必须全量排序。
注意:字段类型影响索引效果。VARCHAR(2000)建了索引也可能退化为Using filesort,需配合innodb_large_prefix=ON;时间戳字段没索引,ORDER BY created_at就是纯内存排序炸弹。
检查嵌套层级是否超出数据库承受能力
MySQL在约31层嵌套时解析器栈溢出,报错ERROR 1038 (HY001): Out of sort memory或更隐蔽的Lost connection,且该限制不可配置。PostgreSQL/SQL Server虽不硬报错,但嵌套超5–7层后,优化器放弃精确代价估算,执行计划中MATERIALIZE节点暴增,Actual Rows暴涨数个数量级。
视图链v1 → v2 → v3 → v4算4层;SELECT * FROM (SELECT * FROM (SELECT ...))这种结构最危险——每层SELECT *都阻止谓词下推,外层WHERE白加。
别信“现在跑得通”,数据量涨、连接池复用线程栈、版本升级都可能突然崩。用EXPLAIN FORMAT=TREE(MySQL 8.0+)或SET STATISTICS XML ON(SQL Server)看执行计划是否出现大量Spool或Materialize节点,那是优化器已放弃稳定决策的信号。
定位sp_head::main_mem_root是否长期占用内存
MySQL存储过程中声明游标却不CLOSE,会导致sp_head::main_mem_root内存永远挂着——这不是配置问题,是逻辑缺陷。过程结束不会自动释放,哪怕LEAVE跳出循环也无效。
先确认监控开启:SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'performance_schema_instrument',返回值里必须含memory/% = COUNTED。
再查内存分布:SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME = 'memory/sql/sp_head::main_mem_root'。如果返回值超1GB(尤其远超innodb_buffer_pool_size),基本就是它在吃内存。
配合SHOW PROCESSLIST看有没有长期卡在executing或Sending data的连接,且SQL含DECLARE CURSOR;再翻dmesg -t找killed process.*mysqld,三者时间戳对齐,就能锁定是游标未关导致的OOM。
真正卡住内存的,往往不是视图嵌套本身,而是物化+全排序+游标漏关这三者叠加。删掉视图里的ORDER BY很容易,但不补索引,问题只是从内存转移到磁盘IO;重写查询不配CLOSE,内存照样越积越多。

















