Есть ли решение этой утечки памяти cfqueryparam? - PullRequest
3 голосов
/ 13 мая 2009

Обновление:

  • Я отправил ошибку в Adobe и сослался на этот SO вопрос

  • В моем реальном коде, где возникла проблема, я решил просто отказаться от использования cfqueryparam. Сейчас я использую пользовательскую функцию для форматирования параметра на основе типа. Есть проблемы безопасности и скорости, с которыми мне придется иметь дело, но он заставляет определенный процесс работать приемлемо при текущей нагрузке.

  • В будущем я планирую перейти к процессу, который вытягивает файлы данных во временные таблицы в базе данных. Затем я буду выполнять операции с данными и переносить данные в живые таблицы, используя как можно больше SQL, вместо того, чтобы полагаться на ColdFusion


У меня проблема с зацикливанием запросов с использованием тегов cfqueryparam при вставке данных. (Я не проверял с запросами выбора или обновления). Цикл постепенно занимает больше памяти, которая не освобождается, пока не будет выполнен запрос. Однако проблема возникает только при циклическом выполнении запроса в функции.

Кажется, он очень чувствителен к количеству используемых тегов cfqueryparam. В этом примере вставляются 15 значений, однако в моем коде это действительно нужно для работы. Я вставляю неизвестное число значений, которые могут сделать проблему более серьезной.

Ниже приведен код, который показывает проблему. Присвойте ему имя источника данных (протестировано на MSSQL), и он создаст таблицу tmp и вставит записи в качестве примера с использованием функции и без нее. Использование памяти отображается до, после нефункционального цикла, затем после функционального цикла. Он также запрашивает сборку мусора и ждет 10 секунд перед выводом информации о памяти, чтобы убедиться, что он отображает информацию с максимально возможной точностью.

По моему опыту с этим конкретным тестом, функциональный цикл привел к использованию более 200 МБ памяти. В моем реальном мире это вылетает 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")>

Ответы [ 10 ]

4 голосов
/ 13 мая 2009

У вас есть отладка в Администраторе?

Если это так, даже если у вас есть showdebugoutput="false", CF будет хранить отладочную информацию обо всех этих запросах, и при таком количестве запросов отладочная информация может быстро накапливаться.


Кроме того, если у вас действительно есть 80 000 строк для вставки, вы, вероятно, захотите сделать это по-другому - например, создание сценария импорта, который запускается непосредственно на БД (без вмешательства CF / JDBC).

3 голосов
/ 13 мая 2009

Может быть множественная вставка может помочь? Сам этот метод обычно работает быстрее, экономя время и помогая сэкономить память.

Да, я видел вашу заметку "вставка неизвестного числа значений", но это должно работать, если у вас есть постоянное количество полей / значений в одной партии вставок.

2 голосов
/ 26 января 2011

Я столкнулся с подобной проблемой.

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

Подход зависит от нескольких вещей. Если вы можете доверять данным, не используйте cfqueryparam, это значительно уменьшит использование памяти. Оттуда минимизируйте SQL как можно больше. Я выполнял довольно много работы с БД в строке, поэтому вместо этого я создал хранимую процедуру. Большой бонус в борьбе с использованием памяти заключался в буферизации вызовов SQL в базе данных. Создайте массив, добавьте к нему свой SQL, затем каждые 50 строк (по выбору после тестирования) создайте ArrayToList для массива внутри тега CfQuery. Это ограничивает трафик базы данных меньшим, но большим вместо множества меньших.

После всего этого у меня все заработало. Но я все еще думаю, что ColdFusion на самом деле не подходит для такого рода задач, а больше, если возможно, домена самого сервера баз данных.

2 голосов
/ 13 мая 2009

Не знаю, будет ли это иметь значение, но есть что попробовать - сжать цикл в функции и обойти функцию несколько раз.

Что это делает с памятью, может помочь сузить область ее использования.

<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>
1 голос
/ 14 мая 2009

Предполагается, что вы используете CF8 ... не уверен, что это происходит в CF7 ...

Попробуйте отключить «Max Pooled Statements» (установить его на ноль) в вашем источнике данных «дополнительные настройки» ... Держу пари, что утечка памяти исчезнет ...

Вот где я обнаружил ошибку ... это вызывало всевозможные сбои на некоторых CF-серверах, пока мы не обнаружили это ... мы теперь на 100% более стабильны из-за этого ...

Патрик Стейл

1 голос
/ 13 мая 2009

Моим первым предположением было бы ввести значения в ваш cfqueryparam - как в type = "CF_SQL_CHAR". Почему это поможет? Я не уверен, но могу предположить, что с нетипизированной переменной возникнут дополнительные издержки.

0 голосов
/ 02 декабря 2011

Я понятия не имею, решит ли это вашу проблему, но то, что я обычно делаю, когда у меня несколько таких вставок, это цикл самого оператора 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>

Таким образом, вместо нескольких обращений к базе данных у вас есть только один большой.

Я понятия не имею, как это отразится на вашей проблеме утечки памяти, но у меня никогда не было никаких утечек памяти при этом.

0 голосов
/ 06 ноября 2010

Способ предотвращения утечки памяти из cfqueryparam в большом цикле запросов состоял в том, чтобы не использовать cfqueryparam. Однако более широкий ответ заключается в том, чтобы избежать неэффективности CF и утечек памяти, чтобы не использовать CF в этих ситуациях. Я получил конкретный процесс до приемлемого уровня для загрузки в то время, но в долгосрочной перспективе я буду переписывать его на другом языке, вероятно, C # непосредственно в ядре базы данных.

0 голосов
/ 13 мая 2009

По всему сообществу хорошо задокументировано, что CF не освобождает память до тех пор, пока запрос не будет завершен. Даже вызов GC напрямую не влияет на освобождение памяти во время выполнения запроса. Не знаю, если это по замыслу или ошибка.

Я понятия не имею, почему вы все равно захотите сделать что-то подобное в CF. У вас нет причин вставлять 80К строк в базу данных с использованием CF, независимо от того, какой механизм базы данных вы используете.

Теперь, если есть причина, по которой вам нужно это сделать, например, вы получаете данные, например, из загруженного файла CSV или XML; У MSSQL есть тонна лучших способов сделать это и обходные пути.

Один из подходов, который я использовал на протяжении многих лет, - это создание хранимой процедуры в MSSQL, которая вызывает BCP или BULK INSERT , чтобы прочитать файл, содержащий данные для вставки.

Лучшее в этом подходе состоит в том, что CF выполняет только загрузку файла, а MMSQL выполняет всю работу по обработке файла. MSSQL без проблем вставляет миллионы строк, используя BCP или BULK INSERT, и будет БЕСКОНЕЧНО быстрее, чем все, что может обработать CF.

0 голосов
/ 13 мая 2009

попробуйте добавить «переменные». перед каждым запросом внутри ваших функций. У меня была похожая проблема, и это исправило ее.

Так измените:

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

до

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

Приветствия

Thomas

...