使用Slick处理MySQL大表触发OutOfMemoryError的原因与解决
Akka Streams + Slick 查询大表触发 OOM 问题排查与解决
嘿,你猜的完全没错——Slick默认的查询行为确实会把整个结果集一股脑塞进内存,这才导致你查大表时触发OutOfMemoryError。我来给你拆解下原因,再说说具体解决办法:
问题根源
Slick的as[T]方法本质是一次性加载全量结果:它会通过JDBC的ResultSet把所有行全部读取出来,转换成你的MyRow实例,打包成内存集合后再交给Akka Streams。这就导致哪怕你用了Akka Streams的流式框架,源头已经把所有数据加载完成,流处理的优势根本发挥不出来,大表数据直接撑爆堆内存。
解决办法
1. 切换到Slick的流式查询API(核心方案)
Slick本身就支持流式查询,你只需要把as[T]换成stream[T]就行。这样Slick会用游标分批从数据库拉取数据,而非一次性全量加载:
// 把as[MyRow]替换为stream[MyRow] val largeTableSource: Source[MyRow, NotUsed] = Slick.source(sql"SELECT * FROM large_table".stream[MyRow])
关键配置补充:MySQL驱动默认不会启用游标取数,所以你需要在JDBC URL里添加两个参数:
jdbc:mysql://你的数据库地址/你的库名?useCursorFetch=true&defaultFetchSize=1000
useCursorFetch=true:开启游标式结果集传输;defaultFetchSize=1000:每次从数据库拉取1000行(这个数值可根据内存情况调整,比如500或2000,平衡内存占用和数据库请求频率)。
2. 利用Akka Streams的背压机制
只要你用了stream[T],Akka Streams的背压机制会自动帮你控速——当下游处理不过来时,会暂停请求上游的新数据,避免内存中积压过多数据,不需要额外编写控制逻辑。
3. 额外优化小技巧
- 优先过滤数据:尽量通过
WHERE条件缩小查询范围,从根源减少需要处理的数据量; - 调优
defaultFetchSize:太小会增加数据库请求次数,太大仍会占用过多内存,建议根据业务场景测试调整; - 精简
MyRow结构:如果case class包含大字段(如TEXT、BLOB)且业务不需要,就不要查询这些字段; - 用
async隔离线程:如果处理逻辑较重,可以在流中添加.async,将查询和处理逻辑放到不同线程池,避免阻塞数据库查询线程。
内容的提问来源于stack exchange,提问作者David Portabella
相关产品推荐
相关产品推荐

