4

アップデート:

  • アドビにバグを提出し、この SO の質問を参照しました

  • 問題が発生した実際のコードでは、cfqueryparam の使用を削除することにしました。現在、カスタム関数を使用して、タイプに基づいてパラメーターをフォーマットしています。私が対処しなければならないセキュリティと速度の問題がありますが、現在の負荷の下で特定のプロセスが許容範囲内で動作するようになります。

  • 将来的には、データ ファイルをデータベースの一時テーブルにプルするプロセスを計画しています。次に、ColdFusion に頼るのではなく、可能な限り SQL を使用して、データに対して操作を実行し、ライブ テーブルにデータを転送します。


データの挿入中に cfqueryparam タグを使用してクエリをループすると問題が発生します。(選択クエリまたは更新クエリでテストしていません)。ループは、要求が完了するまで解放されないより多くのメモリを徐々に占有します。ただし、この問題は、関数内でクエリをループする場合にのみ発生します。

使用される cfqueryparam タグの数に非常に敏感であるようです。この例では、15 個の値が挿入されていますが、実際にこれを機能させる必要があるコードでは、問題をより深刻にする可能性のある不明な数の値を挿入しています。

以下は、問題を示すコードです。データソース名 (MSSQL でテスト済み) を指定すると、tmp テーブルが作成され、関数内にある場合とない場合の例としてレコードが挿入されます。メモリ使用量は、関数外ループの前、関数外ループの後、関数内ループの後に表示されます。また、ガベージ コレクションを要求し、メモリ情報を出力する前に 10 秒待機して、できるだけ正確に情報を表示するようにします。

この特定のテストでの私の経験では、関数内ループにより 200 MB を超えるメモリが使用されました。私の実際の使用では、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>

操作中にメモリを解放できることを示すために、より大きな構造体を構築し、変数の上書きとガベージ コレクションの前後で使用されるメモリを示すサンプル コードを次に示します。このメモリの私の実行では、作成後に使用されたメモリは 118 MB で、上書きとガベージ コレクションの後は 31 MB です。

<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")>
4

10 に答える 10

4

管理者でデバッグをオンにしていますか?

もしそうなら、あなたが持っていたとしてもshowdebugoutput="false"、CFはそれらのすべてのクエリに関するデバッグ情報を保持し、その多くのクエリでデバッグ情報がすぐに蓄積される可能性があります.


また、実際に 80,000 行を挿入する必要がある場合は、別の方法で実行することをお勧めします。たとえば、DB に対して直接実行されるインポート スクリプトを生成します (CF/JDBC が邪魔になることはありません)。

于 2009-05-12T22:43:01.210 に答える
3

多分複数の挿入が役立つことができますか?この手法自体は通常、より高速に機能します。時間を節約すると、メモリを節約できます。

はい、「不明な数の値を挿入する」というメモを見ましたが、これは、単一の挿入バッチに一定数のフィールド/値がある場合に機能するはずです。

于 2009-05-13T07:09:48.823 に答える
2

同様の問題が発生しました。

http://misterdai.wordpress.com/2009/06/24/when-not-to-use-cfqueryparam/

アプローチはいくつかのことに依存します。データを信頼できる場合は、cfqueryparam を使用しないでください。これにより、メモリ使用量が大幅に削減されます。そこから、可能な限り SQL を最小化します。行ごとにかなりの DB 作業を行っていたので、代わりにストアド プロシージャを作成しました。メモリ使用量に対処する上での大きな利点は、データベースへの SQL 呼び出しをバッファリングすることでした。配列を作成し、それに SQL を追加してから、50 行 (テスト後の個人的な選択) ごとに、CfQuery タグ内の配列で ArrayToList を実行します。これにより、データベース トラフィックは、多数の小さなトラフィックではなく、より少ないものに制限されますが、より大きくなります。

その後、すべてがうまくいきました。しかし、ColdFusion は実際にはこの種のタスクに対応できず、可能であればデータベース サーバー自体のドメインに対応していないと思います。

于 2011-01-26T16:42:51.270 に答える
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>
于 2009-05-12T23:14:18.943 に答える
1

私の最初の推測は、type="CF_SQL_CHAR" のように、cfqueryparam に値を入力することです。なぜこれが役立つのでしょうか?よくわかりませんが、型指定されていない変数を使用すると、追加のオーバーヘッドが発生すると推測できます。

于 2009-05-13T13:21:56.623 に答える
0

CFはリクエストが終了するまでメモリを解放しないことは、コミュニティ全体で十分に文書化されています。GCを直接呼び出しても、実行中の要求中にメモリを解放することには影響しません。これが仕様によるものなのかバグなのかわからない。

とにかくCFでこのようなことをしたいと思う理由はわかりません。使用しているデータベースエンジンに関係なく、CFを使用してデータベースに80K行を挿入する理由はありません。

アップロードされたCSVファイルやXMLファイルなどからデータを取得するなど、これを行う必要がある理由がある場合は、MSSQLには、これを行うためのより良い方法と回避策がたくさんあります。

私が何年にもわたって行ってきたアプローチの1つは、BCPまたはBULK INSERTを呼び出して、挿入するデータを含むファイルを読み取るストアドプロシージャをMSSQLで作成することです。

このアプローチの最も良い点は、CFが実行しているのはファイルのアップロードを処理することだけであり、MMSQLはファイルを処理するすべての作業を実行していることです。MSSQLは、BCPまたはBULK INSERTを使用して何百万もの行を挿入することに問題はなく、CFが処理できるものよりも無限に高速になります

于 2009-05-13T14:16:32.650 に答える
0

クエリの大規模なループで cfqueryparam からのメモリ リークを防ぐ方法は、cfqueryparam を使用しないことでした。ただし、より広い答えは、CF の非効率性とメモリ リークを回避することであり、これらの状況では CF を使用しないことです。特定のプロセスを当時の負荷に対して許容可能なレベルにしましたが、長期的には別の言語、おそらくデータベース エンジンで直接 C# で書き直すことになります。

于 2010-11-06T17:50:50.500 に答える
0

これで問題が解決するかどうかはわかりませんが、このような複数の挿入がある場合に通常行うことは、cfquery全体ではなく、SQLステートメント自体のループです。

したがって、次の代わりに:

<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>

したがって、データベースへの複数の呼び出しを行う代わりに、大きな呼び出しを 1 つだけ持つことができます。

これがメモリリークの問題にどのように影響するかはわかりませんが、そのようにしてメモリリークが発生したことはありません.

于 2011-12-02T17:14:30.440 に答える
0

「変数」を先頭に追加してみてください。cffunctions 内の各クエリの前。同様の問題があり、これで修正されました。

だから変更:

<cfquery name="testq" datasource="CongressPlus">

<cfquery name="variables.testq" datasource="CongressPlus">

乾杯、

トーマス

于 2009-05-13T12:46:23.967 に答える