这个 cfqueryparam 内存泄漏有解决方案吗?
Posted
技术标签:
【中文标题】这个 cfqueryparam 内存泄漏有解决方案吗?【英文标题】:Is there a solution to this cfqueryparam memory leak? 【发布时间】:2010-10-25 16:47:24 【问题描述】:更新:
我已将错误提交给 Adobe 并引用了这个 SO question
在发生问题的实际代码中,我决定只删除对 cfqueryparam 的使用。我现在使用自定义函数来根据类型格式化参数。我必须处理安全和速度方面的问题,但它可以让特定进程在当前负载下正常工作。
将来我计划将数据文件拉入数据库中的临时表中。然后我将尽可能使用 SQL 对数据执行操作并将数据传输到实时表,而不是依赖 ColdFusion
我在插入数据时使用 cfqueryparam 标记循环查询时遇到问题。 (我没有测试过选择或更新查询)。循环逐渐占用更多内存,直到请求完成才释放。但是,仅当在函数中循环查询时才会出现此问题。
它似乎对使用的 cfqueryparam 标签的数量非常敏感。在此示例中,插入了 15 个值,但是在我的代码中实际上需要它才能工作,我插入了未知数量的值,这会使问题更加严重。
下面是显示问题的代码。给它一个数据源名称(在 MSSQL 上测试),它将创建一个 tmp 表并插入记录作为示例,无论是否在函数中。内存使用情况显示在非函数循环之前、之后、函数内循环之后。它还请求垃圾收集并在输出内存信息之前等待 10 秒,以确保它尽可能准确地显示信息。
根据我对这个特定测试的经验,函数内循环导致使用了超过 200mb 的内存。在我的现实世界中使用它会使 ColdFusion 崩溃 :-(
<cfsetting enablecfoutputonly="true">
<cfsetting requesttimeout="600">
<cfset insertCount = 100000>
<cfset dsn = "TmpDB">
<cfset dropTmpTable()>
<cfset createTmpTable()>
<cfset showMemory("Before")>
<cfflush interval="1">
<cfloop from="1" to="#insertCount#" index="i">
<cfquery name="testq" datasource="#dsn#">
INSERT INTO tmp ( [col1],[col2],[col3],[col4],[col5],[col6],[col7],[col8],[col9],[col10],[col11],[col12],[col13],[col14],[col15] )
VALUES ( <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR"> )
</cfquery>
</cfloop>
<cfset showMemory("After Non-Function INSERTS")>
<cfflush interval="1">
<cfset funcTest()>
<cfset showMemory("After Function based INSERTS")>
<cfset dropTmpTable()>
<cffunction name="funcTest" output="false">
<cfset var i = 0>
<cfset var testq = "">
<cfloop from="1" to="#insertCount#" index="i">
<cfquery name="testq" datasource="#dsn#">
INSERT INTO tmp ( [col1],[col2],[col3],[col4],[col5],[col6],[col7],[col8],[col9],[col10],[col11],[col12],[col13],[col14],[col15] )
VALUES ( <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR">, <cfqueryparam value="TestValue" cfsqltype="CF_SQL_CHAR"> )
</cfquery>
</cfloop>
</cffunction>
<cffunction name="showMemory" output="true">
<cfargument name="label" required="true">
<cfset var runtime = "">
<cfset var memoryUsed = "">
<cfset requestGC("10")>
<cfset runtime = CreateObject("java","java.lang.Runtime").getRuntime()>
<cfset memoryUsed = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024>
<cfoutput>
<h2>#arguments.label#</h2>
Memory Used: #Round(memoryUsed)#mb
</cfoutput>
</cffunction>
<cffunction name="requestGC">
<cfargument name="waitSeconds" required="false" default="0" type="numeric">
<cfscript>
createObject("java","java.lang.Runtime").getRuntime().gc();
createObject("java", "java.lang.Thread").sleep(arguments.waitSeconds*1000);
</cfscript>
</cffunction>
<cffunction name="dropTmpTable" output="false">
<cftry>
<cfquery datasource="#dsn#">
DROP TABLE tmp
</cfquery>
<cfcatch type="database"></cfcatch>
</cftry>
</cffunction>
<cffunction name="createTmpTable" output="false">
<cfquery datasource="#dsn#">
CREATE TABLE tmp(
col1 nchar(10) NULL, col2 nchar(10) NULL, col3 nchar(10) NULL, col4 nchar(10) NULL, col5 nchar(10) NULL, col6 nchar(10) NULL, col7 nchar(10) NULL, col8 nchar(10) NULL, col9 nchar(10) NULL, col10 nchar(10) NULL, col11 nchar(10) NULL, col12 nchar(10) NULL, col13 nchar(10) NULL, col14 nchar(10) NULL, col15 nchar(10) NULL
) ON [PRIMARY]
</cfquery>
</cffunction>
只是为了说明可以在操作期间释放内存,下面是示例代码,它构建了一个更大的结构并显示了在变量被覆盖和垃圾收集之前和之后使用的内存。在我的运行中,填充后使用的内存为 118mb,覆盖和垃圾收集后为 31mb。
<cfset showMemory("Before struct creation")>
<cfflush interval="1">
<cfset tmpStruct = >
<cfloop from="1" to="1000000" index="i">
<cfset tmpStruct["index:#i#"] = "testvalue testvalue testvalue testvalue testvalue testvalue testvalue testvalue testvalue testvalue">
</cfloop>
<cfset showMemory("After struct population")>
<cfflush interval="1">
<cfset tmpStruct = >
<cfset showMemory("After struct overwritten")>
【问题讨论】:
嗯,您的两个 cfquery 标记中有不同的数据源 - 不过可能只是示例中的一个错误? 你确定是 cfqueryparam 是罪魁祸首吗?如果不使用 cfqueryparam 会发生什么? 感谢 Peter Al,我刚刚尝试使用 'TestValue' 代替所有 cfqueryparam 标记的请求,并且在函数示例中没有建立内存。由于 cfqueryparam 是我在两个独立测试之间唯一改变的东西,我相信它在某种程度上是相关的。 【参考方案1】:您是否在 Administrator 中进行了调试?
如果是这样,即使您有 showdebugoutput="false"
,CF 也会保留有关所有这些查询的调试信息,并且有这么多查询,调试信息可能会很快积累。
此外,如果您真的有 80,000 行要插入,您可能希望以不同的方式执行此操作 - 例如生成直接针对 DB 运行的导入脚本(没有 CF/JDBC 妨碍)。
【讨论】:
调试未开启(我设置的是 enablecfoutputonly,而不是 showdebugoutput)。我不同意您的观点,即纯粹在数据库端编写脚本可能是个好主意。我想如果我需要做数十万的任何事情,我可能需要这样做。但是现在我被合法代码中的某种错误所困扰,并试图绕过它。 我开始认为 db 方法可能是最安全的选择 Pete 的观点是,即使您正在抑制调试输出,完全打开它也会占用内存。确保未选中“启用请求调试输出”。 刚刚检查到绝对确定,它不是检查。我在运行调试时遇到了问题,所以这也是我最初的想法之一。【参考方案2】:不知道它是否会有所作为,但可以尝试一下 - 缩小函数内循环,并多次循环该函数。
这对内存的作用可能有助于缩小内存的使用范围。
<cffunction name="funcTest" output="false">
<cfargument name="from" />
<cfargument name="to" />
<cfset var i = 0>
<cfset var testq = "">
<cfloop from="#arguments.from#" to="#arguments.to#" index="i">
<cfquery name="testq" datasource="#dsn#">
...
</cfquery>
</cfloop>
</cffunction>
<cfset BlockSize = 100 />
<cfloop index="CurBlock" from="1" to="#(InsertCount/BlockSize)#">
<cfset funcTest
( from : CurBlock*(BlockSize-1) + 1
, to : CurBlock*BlockSize
)/>
</cfloop>
【讨论】:
这是个好主意。明天我将进行测试,看看调用插入子集的函数是否会减少内存积累。我希望这可行,尽管现在我回顾我的示例,在函数调用完成并调用 gc 后内存没有被释放。 不幸的是,这没有帮助。我做了50块3000,消耗的内存和1块150000一样。【参考方案3】:也许multiple insert 可以帮忙?这种技术本身通常工作得更快,节省一些时间可以帮助您节省一些内存。
是的,我已经看到您的注释“插入未知数量的值”,但如果您在单个插入批次中有恒定数量的字段/值,这应该可以工作。
【讨论】:
看起来这项技术带来了重大的速度提升。我希望仍然必须摆脱 cfqueryparam 但至少这样我怀疑会不会有性能损失。【参考方案4】:尝试添加“变量”。在您的 cffunctions 中的每个查询之前。我有一个类似的问题,这解决了它。
所以改变:
<cfquery name="testq" datasource="CongressPlus">
到
<cfquery name="variables.testq" datasource="CongressPlus">
干杯,
托马斯
【讨论】:
我实际上并不希望查询在变量范围内。我的实际代码在 CFC 中,这可能会导致问题。实际上在我的真实代码中我已经一起删除了这个名字。 对于像 cfquery 这样创建变量的标签,对于需要私有的东西,您需要提前 var 变量。我的第一个猜测是在您的 cfqueryparam 中输入值 - 如 type="CF_SQL_CHAR"。为什么会有帮助?我不确定,但我可以猜测非类型变量会产生额外的开销。
【讨论】:
好主意,但我已经进行了更改,并且内存仍然在函数内循环期间建立并且没有释放。【参考方案6】:整个社区都有很好的记录,CF 在请求完成之前不会释放内存。即使直接调用 GC 也不会影响在运行请求期间释放内存。不知道这是设计使然还是错误。
我不知道为什么你甚至想在 CF 中做这样的事情。无论您使用哪种数据库引擎,都没有理由使用 CF 向数据库中插入 80K 行。
现在,如果您出于某种原因需要这样做,例如您从上传的 CSV 或 XML 文件中获取数据; MSSQL 有很多更好的方法和解决方法。
多年来我采用的一种方法是在 MSSQL 中创建一个存储过程,调用 BCP 或 BULK INSERT 来读取包含要插入的数据的文件。
这种方法的最佳之处在于,CF 唯一要做的就是处理文件上传,而 MMSQL 负责处理文件的所有工作。 MSSQL 使用 BCP 或 BULK INSERT 插入数百万行没有问题,并且将 INFINITELY 比 CF 可以处理的任何内容都要快。
【讨论】:
我同意这可能最好用在数据库中。我正在 CF 中进行操作和查找,但我相信它们可以转移到基于集合的查询操作,尽管我还没有查看所有内容。我不同意你的说法,即 CF 在请求之后才会释放内存。我将在上面添加一个代码示例,显示变量被覆盖时释放的内存。【参考方案7】:假设您使用的是 CF8……不确定这是否发生在 CF7 中……
尝试在您的数据源“高级设置”中关闭“Max Pooled Statements”(将其设置为零)......我敢打赌你的内存泄漏会消失......
这就是我发现错误的地方...这导致一些 CF 服务器上的各种崩溃,直到我们发现这个...因为这个我们现在稳定了 100%...
帕特里克·斯泰尔
【讨论】:
我实际上将池化状态设置为零,然后在阅读各种博客关于这是一个可能的解决方案后完全关闭维护连接。现在让我排除各种设置的原因是,我将内存泄漏的范围缩小到代码是否在函数中。 它在 CF8 中。运行测试时是否得到类似的结果? 我自己还没有对此进行任何测试......所以你是说打开“Max Pooled Statements”并且cfqueryparam不在CFFUNCTION中不会发生内存泄漏?该 CFFUNCTION 是在 CFC 中还是仅在 CFM 页面中的常规 UDF 中发生是否重要?【参考方案8】:在大型查询循环中防止 cfqueryparam 内存泄漏的方法是不使用 cfqueryparam。然而,更广泛的答案是避免 CF 的低效率和内存泄漏,即在这些情况下不使用 CF。我当时将特定过程的负载提高到可接受的水平,但从长远来看,我会用另一种语言重写它,可能直接在数据库引擎中使用 C#。
【讨论】:
【参考方案9】:我遇到了类似的问题。
http://misterdai.wordpress.com/2009/06/24/when-not-to-use-cfqueryparam/
方法取决于几件事。如果您可以信任数据,请不要使用 cfqueryparam,这将大大减少内存使用量。从那里,尽可能地减少 SQL。我每行都在做相当多的数据库工作,所以我创建了一个存储过程。对抗内存使用的最大好处是缓冲对数据库的 SQL 调用。创建一个数组,将您的 SQL 附加到它,然后每 50 行(测试后的个人选择)在 CfQuery 标记内的数组上执行一个 ArrayToList。这将数据库流量限制为更少但更大,而不是许多更小的流量。
在所有这些之后,一切都对我有用。但我仍然认为 ColdFusion 确实无法胜任这类任务,如果可能的话,更多的是数据库服务器本身的域。
【讨论】:
【参考方案10】:我不知道这是否能解决你的问题,但是当我有多个这样的插入时,我通常会做的是 SQL 语句本身的循环,而不是整个 cfquery。
所以不要有:
<cfloop from="1" to="#insertCount#" index="i">
<cfquery name="testq" datasource="#dsn#">
...
</cfquery>
</cfloop>
我愿意:
<cfquery name="testq" datasource="#dsn#">
<cfloop from="1" to="#insertCount#" index="i">
...
</cfloop>
</cfquery>
因此,您只需一个大的,而不是多次调用数据库。
我不知道这会如何影响您的内存泄漏问题,但我从来没有遇到过这样的内存泄漏问题。
【讨论】:
出于各种原因,我在各个地方都这样做了,但这并不是解决内存泄漏问题的方法。一个数据库调用中可以输入多少查询是有限制的(我认为 sql/params 的总大小为 64k),因此需要通过记录进行分块。如果使用 cfqueryparam 仍然存在内存问题。以上是关于这个 cfqueryparam 内存泄漏有解决方案吗?的主要内容,如果未能解决你的问题,请参考以下文章