本质是客户端缓冲区率先溢出,驱动默认缓存整结果集致jvm堆或进程内存超限;继而服务端sort/join_buffer超限触发磁盘spill;长连接堆积耗尽连接池;最终系统级oom killer介入杀进程。

单次大 SQL 查询未加限制导致返回百万级记录,本质是内存资源被突发性、非预期地耗尽。从内存区域视角看,崩溃不是“突然发生”,而是多个内存区域在查询执行链路上相继承压、突破阈值后的连锁反应。
客户端缓冲区率先溢出
大多数数据库驱动(如 MySQL 的 mysqlclient、PostgreSQL 的 libpq)默认启用结果集缓存,将整条查询结果一次性拉取到本地内存中。当服务端返回百万行、每行平均 2KB,仅数据体就超 2GB——远超客户端进程默认堆上限(如 Python 的 sys.getsizeof() 不体现实际分配,但 malloc 分配会失败)。
- Java 应用常见报错:java.lang.OutOfMemoryError: Java heap space
- Python 常见表现:进程被 OS OOM Killer 杀死,日志出现 Killed process xxx (python) total-vm:XXXXkB, anon-rss:XXXXkB
- 规避方式:启用流式读取(如 MySQL 的 cursor(buffered=False),PostgreSQL 的 named cursor + fetchmany())
服务端查询工作内存超限
数据库自身也有关键内存区域参与该查询:MySQL 的 sort_buffer_size、join_buffer_size,PostgreSQL 的 work_mem。若查询含 ORDER BY、GROUP BY 或多表 JOIN,这些区域会按需放大——尤其当优化器误判行数(百万行被估为千行),分配的内存远小于实际所需,触发磁盘临时文件(spill to disk),但若磁盘 I/O 拥塞或临时空间不足,查询直接中止并可能拖垮连接池。
- MySQL 查看当前会话内存使用:SELECT * FROM performance_schema.memory_summary_by_thread_by_event_name WHERE event_name LIKE 'memory/sql/%' AND thread_id = CONNECTION_ID();
- PostgreSQL 监控:EXPLAIN (ANALYZE, BUFFERS) 可看到 Temp files 和 Buffers 实际用量
- 建议:对非管理类接口,强制设置会话级限制,如 SET SESSION work_mem = '4MB';
连接与线程栈持续占位,引发雪崩
即使查询最终失败,其占用的连接不会立刻释放:MySQL 线程处于 Sending data 或 Copying to tmp table 状态时,连接仍计入 max_connections;PostgreSQL 后端进程在清理阶段也可能卡住。大量此类长连接堆积,迅速吃光连接池,新请求排队等待,进而拖慢整个实例响应,监控显示 Threads_connected 持续高位、QPS 断崖下跌。
- MySQL 快速识别问题连接:SHOW PROCESSLIST; 找出 Time 值极大且 State 异常的线程
- 主动终止:KILL [connection_id];
- 根本防护:应用层设置查询超时(如 JDBC 的 socketTimeout、queryTimeout),数据库层配置 wait_timeout / idle_in_transaction_session_timeout
系统级内存压力触发全局回收
当数据库进程 RSS 内存持续飙升(如 mysqld 占用 16GB+),OS 内核的 kswapd 频繁扫描页框,pgmajfault 上升,其他进程(如监控 agent、日志收集器)也因内存竞争变慢。极端情况下,内核 OOM Killer 根据 oom_score_adj 选择杀掉高内存进程——不一定是数据库本身,可能是同机器的 Redis 或 Nginx,造成多服务连环故障。
- 检查历史 OOM:dmesg -T | grep -i "killed process"
- 限制数据库内存上限:MySQL 通过 cgroup v2 或容器 memory limit;PostgreSQL 推荐配合 shared_buffers + effective_cache_size 合理规划,避免过度预留
- 上线前压测必须包含“无 LIMIT 的全表扫描”场景,观测各层内存增长曲线











