将 JSON 数据拧到文本文件,但它会进入堆空间



我有这段代码,当数据库很小但记录很少时,它可以正常工作,它会正确地将 json 写入文件,但是当数据很大时,它只是超时

<cfloop list="t" index="k">
<cfquery name="qry">
select * from #k# 
</cfquery>
<cfset js= serializeJSON(qry,'struct')>         
<cffile action="write" file="#k#" output="#js#">
</cfloop>

我尝试使用线程,但它们也不起作用,如果我将cfthreadjoins一起使用,它只会创建没有值的空表文件

想到将文件拆分为每个表的 1000 条记录的组合,然后像

table_1表2,表3,表3,是表,因为它有数百万条记录,如果记录少于1000条,则跳过这些记录,只创建1个文件。

但我只是在想哪种方法是最好的,需要一个起点

首先,让我们将其拆分:

来自数据库的结果集

<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 语句中使用OFFSETFETCH来限制每个循环的行数。具有多次迭代将允许 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。

这确实是一个评论,但它太长了。

SQL Server 2017可以直接创建JSON。

<cfloop list="t" index="k">
<cfquery name="qry">
SELECT (
SELECT * 
FROM #k#
FOR JSON AUTO
) AS data  
</cfquery>
<cffile action="write" file="#k#" output="#qry.data#">
</cfloop>

其他人已经谈到了JVM和垃圾收集,但由于CF处理GC的方式,没有跟进潜在的快速胜利。

CF 可以在每个函数返回后以及每个请求结束时进行 GC。因此,如果你在一个循环中做了几次使用大量内存的事情,或者在循环中多次使用适量内存的事情,那么你应该将该"某物"抽象为一个函数,并在循环中调用该函数,以便在必要时可以在每次迭代中释放内存, 而不是一直保留到请求结束,并可能在请求结束垃圾回收之前最大化堆空间。

例如,将原始代码重构为此,对GC更加友好:

<cffunction name="tableToFile" output="false">
<cfargument name="tableName" type="variableName" required="true" />
<cfquery name="local.qry">
select * from #arguments.tableName#
</cfquery>
<cfset local.js = serializeJSON(local.qry,'struct') />
<cffile action="write" file="#arguments.tableName#" output="#local.js#" />
</cffunction>
<cfloop list="t" index="k">
<cfset tableToFile(tableName=k) />
</cfloop>

但是,如果该循环的任何单个迭代由于查询太大而消耗太多内存,则此方法无法解决您的问题。如果这是你的问题,那么你应该结合像Alex这样的方法来实现它,以批量获取你的行,并假设你的SQL Server比你的Lucee Server更好地完成任务,然后也是James的方法让SQL Server做JSON序列化。

最新更新