1

我的代码在数据库很小且记录很少时可以正常工作,它将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>

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

考虑将文件拆分为每个表的 1000 条记录的组合,然后执行类似操作

table_1,table2,table3,同一张表,因为它有数百万条记录,如果记录少于 1000 条,则跳过这些表,只创建 1 个文件。

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

4

3 回答 3

3

首先,让我们分解一下:

来自数据库的结果集

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

于 2020-07-04T15:21:50.207 回答
1

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

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>
于 2020-07-05T05:40:28.933 回答
0

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

CF 可以在每个函数返回后进行 GC,也可以在每个请求结束时进行 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 序列化。

于 2020-07-06T12:44:12.640 回答