Есть ли решение этой утечки памяти cfqueryparam?
Обновления:
Я отправил ошибку в Adobe и сослался на этот ТАК вопрос
В моем реальном коде, где возникла проблема, я решил просто отказаться от использования 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 ответов
Способ предотвращения утечек памяти из cfqueryparam в большом цикле запросов состоял в том, чтобы не использовать cfqueryparam. Однако более широкий ответ заключается в том, чтобы избежать неэффективности CF и утечек памяти, чтобы не использовать CF в этих ситуациях. Я получил конкретный процесс до приемлемого уровня для загрузки в то время, но в долгосрочной перспективе я буду переписывать его на другом языке, вероятно, C# непосредственно в ядре базы данных.
У вас есть отладка в администраторе?
Если так, даже если у вас есть showdebugoutput="false"
CF будет хранить отладочную информацию обо всех этих запросах, и при таком количестве запросов отладочная информация может быстро накапливаться.
Кроме того, если у вас действительно есть 80000 строк для вставки, вы, вероятно, захотите сделать это по-другому - например, сгенерировать скрипт импорта, который будет работать непосредственно с БД (без вмешательства CF/JDBC).
Может быть, несколько вставок может помочь? Сам этот метод обычно работает быстрее, экономя некоторое время, и вы можете сэкономить память.
Да, я видел вашу заметку "вставка неизвестного числа значений", но это должно работать, если у вас есть постоянное количество полей / значений в одном пакете вставки.
Я столкнулся с подобной проблемой.
http://misterdai.wordpress.com/2009/06/24/when-not-to-use-cfqueryparam/
Подход зависит от нескольких вещей. Если вы можете доверять данным, не используйте cfqueryparam, это значительно уменьшит использование памяти. Оттуда минимизируйте SQL как можно больше. Я выполнял довольно много работы с БД в каждой строке, поэтому вместо этого я создал хранимую процедуру. Большим преимуществом в борьбе с использованием памяти была буферизация вызовов SQL в базе данных. Создайте массив, добавьте к нему свой SQL, затем каждые 50 строк (по выбору после тестирования) создайте ArrayToList для массива внутри тега CfQuery. Это ограничивает трафик базы данных меньшим, но большим вместо множества меньших.
После всего этого у меня все получилось. Но я все еще думаю, что ColdFusion на самом деле не подходит для такого рода задач, больше, если это возможно, домена самого сервера базы данных.
Не знаю, будет ли это иметь значение, но что-то нужно попробовать - сократить цикл внутри функции и обойти функцию несколько раз.
То, что это делает с памятью, может помочь сузить область ее использования.
<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>
Моим первым предположением было бы ввести значения в ваш cfqueryparam - как в type="CF_SQL_CHAR". Почему это поможет? Я не уверен, но я могу предположить, что были бы дополнительные издержки с нетипизированной переменной.
Предполагая, что вы используете CF8... не уверен, что это происходит в CF7...
Попробуйте отключить "Max Pooled Statements" (установить его на ноль) в вашем источнике данных "дополнительные настройки"... Держу пари, что утечка памяти уходит...
Вот где я обнаружил, что ошибка... это вызывало всевозможные сбои на некоторых CF-серверах, пока мы не обнаружили это... мы теперь на 100% более стабильны из-за этого...
Патрик Стейл
Во всем сообществе хорошо задокументировано, что CF не освобождает память до тех пор, пока запрос не будет завершен. Даже прямой вызов GC не влияет на освобождение памяти во время выполнения запроса. Не знаю, если это дизайн или ошибка.
Я понятия не имею, почему вы все равно захотите сделать что-то подобное в CF. У вас нет причин вставлять 80К строк в базу данных с использованием CF, независимо от того, какой механизм базы данных вы используете.
Теперь, если есть причина, по которой вам нужно это сделать, например, вы получаете данные, например, из загруженного файла CSV или XML; MSSQL имеет множество лучших способов сделать это и обходные пути.
Один из подходов, который я использовал на протяжении многих лет, - это создание хранимой процедуры в MSSQL, которая вызывает BCP или BULK INSERT для чтения файла, который содержит данные для вставки.
Самое лучшее в этом подходе - это то, что CF выполняет только загрузку файлов, а MMSQL выполняет всю работу по обработке файлов. MSSQL без проблем вставляет миллионы строк, используя BCP или BULK INSERT, и будет БЕСКОНЕЧНО быстрее, чем все, что может обработать CF.
Попробуйте добавить "переменные". перед каждым запросом внутри ваших функций. У меня была похожая проблема, и это решило ее.
Так что поменяйте:
<cfquery name="testq" datasource="CongressPlus">
в
<cfquery name="variables.testq" datasource="CongressPlus">
Ура,
Томас
Я понятия не имею, решит ли это вашу проблему, но то, что я обычно делаю, когда у меня несколько таких вставок, это цикл самого оператора 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>
Таким образом, вместо нескольких обращений к базе данных у вас есть только один большой.
Я понятия не имею, как это отразится на вашей проблеме утечки памяти, но я никогда не испытывал никаких утечек памяти при этом.