首先,让我们分解一下:
来自数据库的结果集
<cfquery name="qry">
select * from #k#
</cfquery>
- 数据库服务器检索数据并通过网络将其流式传输到 ColdFusion 服务器
- ColdFusion 将数据存储在查询对象中,并将其存储在堆中
从数据库中序列化结果集
<cfset js= serializeJSON(qry,'struct')>
- ColdFusion 递归地序列化整个查询对象
- ColdFusion 创建一个包含序列化数据的字符串对象并将其存储在堆中
将序列化的结果集从内存写入文件系统
<cffile action="write" file="#k#" output="#js#">
- ColdFusion 将字符串对象写入文件系统上的文件中
在同一个请求/线程中完成所有这些
<cfloop list="t" index="k">
...
</cfloop>
结论
您的代码会折磨 JVM 堆,因为必须保留引用直到每次迭代结束。GC 只能在处理完一个完整的表后进行清理。大表(1.000.000+ 行)可能会杀死线程甚至挂起 JVM。
修复:来自数据库的结果集
一次检索大型结果集总是会损害性能。虽然在本地网络中流式传输大量数据(假设数据库位于同一网络中)只需要更多时间,但存储完整结果集所需的内存将成为 JVM 的问题。
与其一次性完成所有事情,不如考虑将其拆分为更小的数据块。在 SQL 语句中使用OFFSET
andFETCH
来限制每个循环的行数。进行多次迭代将允许 Java GC 释放先前迭代使用的内存,从而减轻堆。
修复:从数据库中序列化结果集
同样的问题。大数据集会损害性能。通过逐行序列化而不是一次序列化所有行来拆分结果集。
将序列化的结果集从内存写入文件系统
虽然这可能不需要修复,但您最终必须逐行切换到写作。
一些代码
<cfset maxRowsPerIteration = 50000>
<cfloop list="t" index="k">
<!--- create empty file to append lines later --->
<cfset fileWrite(k, "")>
<cfset rowsOffset = 0>
<!--- NOTE: you might want to lock the table (prevent write access) here --->
<!--- infinite loop will be terminated as soon the query no longer returns any rows --->
<cfloop condition="true">
<!--- fetch a slice of the full table --->
<cfquery name="qry">
select * from #k# OFFSET #rowsOffset# ROWS FETCH NEXT #maxRowsPerIteration# ROWS ONLY
</cfquery>
<cfif not qry.recordCount>
<cfbreak>
</cfif>
<cfset rowsOffset += maxRowsPerIteration>
<cfloop query="qry">
<cfset rowJSON = serializeJSON(
queryRowToStruct(qry, qry.currentRow)
)>
<cfset fileAppend(k, rowJSON, "UTF-8", true)>
</cfloop>
</cfloop>
<!--- NOTE: if you locked the table previously, unlock it here --->
</cfloop>
有关 的参考实现queryRowToStruct
,请查看CFLib。