Как выполнить очень большой запрос (SQL Server и ColdFusion) - PullRequest
3 голосов
/ 22 октября 2008

У меня достаточно простой запрос (на этот раз), мне нужны ВСЕ результаты обратно (я храню их в электронной таблице Excel). Сам запрос истекает время ожидания сервера, так как мне выполнить его без этого?

Ответы [ 7 ]

7 голосов
/ 22 октября 2008

Вы можете увеличить время ожидания запроса для страницы:

<cfsetting requestTimeout="3600" />

Это обеспечит, чтобы у вас было время обработать все записи.

Вы также можете разбить список на «куски». Потребуется некоторая настройка, чтобы определить оптимальный размер фрагмента, но вы можете получить результаты по 100 или 1000 строк за раз и использовать для вывода результатов на экран по мере их появления. Этот подход также имеет преимущество, заключающееся в использовании меньшего количества памяти на сервере coldFusion, поскольку каждая строка, извлеченная с сервера SQL, загружается в память CFML и остается там до тех пор, пока переменная объекта запроса не будет перезаписана или не выйдет из области видимости (в конце страница). Это означает, что вы можете легко заполнить память ColdFusion чтением нескольких сотен тысяч строк, особенно если строки «широкие» (т. Е. Содержат большие буквы или тексты).

6 голосов
/ 24 октября 2008

сначала я бы проверил, почему этот запрос занимает столько времени.

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

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

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

что касается конца холодного синтеза. убедитесь, что вы используете java.io.BufferedWriter для создания электронной таблицы. использование обычного метода конкатенации строк в CF очень медленное, а BufferedWriter бесконечно быстрее. Прилагаемый CFC, который я создал для создания электронных таблиц с разделителями, вы можете изменить его в соответствии со своими потребностями.

<!--- init --->
<cffunction name="init" access="public" returntype="Any" output="false">
    <cfargument name="name" type="string" required="true">
    <cfset var local = {}>

    <!--- name of file when downloading --->
    <cfset variables.name = arguments.name & ".xls">
    <!--- name of temp file --->
    <cfset variables.filename = CreateUUID() & ".csv">
    <!--- full path to temp file for downloading --->
    <cfset variables.fullfilename = expandpath("/_temp") & "\" & variables.filename>
    <!--- file write java object --->
    <cfset variables.filewriter = CreateObject("java","java.io.FileWriter").init(
            variables.fullfilename
            ,JavaCast("boolean","true")
        )>
    <!--- buffered writer java object --->
    <cfset variables.bufferedwriter = CreateObject("java","java.io.BufferedWriter").init(
                variables.filewriter
            )>
    <!--- row delimeter --->
    <cfset variables.row = chr(10)>
    <!--- col delimeter --->
    <cfset variables.col = chr(9)>
    <!--- header container --->
    <cfset variables.headers = []>
    <!--- data container --->
    <cfset variables.data = []>
    <cfset newrow()>
    <cfreturn this>
</cffunction>


<!--- addheader --->
<cffunction name="addheader" access="public" returntype="void" output="false">
    <cfargument name="str" type="string" required="true">
    <cfset arrayappend(variables.headers, arguments.str)>
</cffunction>

<!--- newrow --->
<cffunction name="newrow" access="public" returntype="void" output="false">
    <cfset arrayappend(variables.data, arraynew(1))>
    <cfset variables.data_counter = arraylen(variables.data)>
</cffunction>

<!--- adddata --->
<cffunction name="adddata" access="public" returntype="void" output="false">
    <cfargument name="str" type="string" required="true">
    <cfset arrayappend(variables.data[variables.data_counter], arguments.str)>
</cffunction>

<!--- flush --->
<cffunction name="flush" access="public" returntype="void" output="false">
    <cfset var local = {}>

    <!--- write headers --->
    <cfset local.counter = 0>
    <cfset local.headers_count = arraylen(variables.headers)>
    <cfloop array="#variables.headers#" index="local.header">
        <cfset local.counter++>
        <cfset variables.bufferedwriter.write(local.header & variables.col)>
    </cfloop>

    <cfif not arrayisempty(variables.headers)>
        <cfset variables.bufferedwriter.write(variables.row)>
    </cfif>

    <!--- write data --->
    <cfloop array="#variables.data#" index="local.data">
        <cfloop array="#local.data#" index="local.cell">
            <cfset variables.bufferedwriter.write(local.cell & variables.col)>
        </cfloop>
        <cfset variables.bufferedwriter.write(variables.row)>
    </cfloop>

    <cfset variables.bufferedwriter.close()>
    <cfsetting showdebugoutput="No">
    <cfheader name="Content-Description" value="File Transfer">
    <cfheader name="Content-Disposition" value="attachment;filename=#variables.name#">
    <cfcontent type="application/vnd.ms-excel" file="#variables.fullfilename#" deletefile="true" reset="true">
</cffunction>

4 голосов
/ 22 октября 2008

Как уже отмечали другие, вы можете попытаться увеличить время ожидания запроса на странице, хотя это не рекомендуется, если выполнение вашего запроса измеряется в минутах , а не в секундах или миллисекундах. CF будет обслуживать только определенное количество запросов одновременно, поэтому вы должны быть осторожны с блокировкой одного из этих запросов, ожидающих выполнения 5-минутного запроса.

Если вы используете SQL Server или Oracle, я думаю, что CFQUERY предоставляет свой собственный атрибут времени ожидания для запроса, который вы можете установить. Опять же, это не рекомендуется для действительно длительных запросов.

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

  1. Создать какую-то систему очередей для записи ожидающих запросов на обслуживание. Это может быть таблица БД, файл XML на диске и т. Д. Когда ваш пользователь запрашивает свои данные, вы регистрируете этот запрос в этой очереди.

  2. Напишите запланированное задание (например, Java, DTS или запланированную страницу CF), которое периодически проверяет эту очередь на работу. В зависимости от ваших потребностей вы можете выделять фоновый поток для обработки каждого запроса или, может быть, запланированная задача обрабатывает его напрямую. Если вы используете запланированные CF-страницы, вам нужно разбить общую рабочую нагрузку на более мелкие куски, которые можно обрабатывать итеративно, в противном случае у вас возникнет та же проблема, что и сейчас.

  3. Как только запланированное задание определяет, что запрос был выполнен, оно запускает какое-то уведомление о том, что обработка готова. Например, вы можете отправить электронное письмо пользователю, чтобы сообщить ему, что данные готовы, со ссылкой для загрузки файла .csv, созданного на диске.

Очевидно, правильный выбор во многом зависит от конкретной решаемой проблемы. В общем, я бы попробовал эти вещи в следующем порядке:

  1. Агрессивно атаковать время выполнения запроса. Вы можете использовать индексы или лучше писать на T-SQL?
  2. Если запрос занимает минуту или две и выполняется очень редко, допустимо увеличение времени ожидания страницы или запроса.
  3. Если запрос выполняется часто или занимает более 2-3 минут, откусите пулю и создайте систему пакетирования или организации очередей для обработки запроса в фоновом режиме.
2 голосов
/ 22 октября 2008

Самый простой способ - разбить домен запроса на несколько частей. Например, добавьте в предложение WHERE выражение, которое выбирает только первую половину диапазона клавиш, а затем выполните второй запрос, чтобы выбрать нижнюю половину. Затем объедините вывод.

1 голос
/ 01 декабря 2008

Используйте индексирование правильно. Создавайте внешние ключи везде, где только можно. Запросы никогда не будут превышать время ожидания для нормализованной базы данных.

Будьте очень осторожны с соединениями и предложениями, например, если в вашем запросе есть предложение group by вместо использования where, предложение having будет работать быстрее. Таким образом сокращается время выполнения запроса.

Используйте оценку стоимости, чтобы проверить, какая таблица занимает больше времени или нуждается в нормализации в вашей базе данных.

1 голос
/ 22 октября 2008

Вы можете установить время ожидания для каждого запроса, хотя объединение нескольких запросов может быть лучшим подходом.

<cfsetting 
enableCFoutputOnly = "yes|no" 
requestTimeOut = "value in seconds"
showDebugOutput = "yes|no" >
0 голосов
/ 23 октября 2008

Я бы выбросил запрос в отдельный поток, загрузив его в постоянную область (например, сеанс). Перейдите на страницу, которая проверяет наличие запроса. Повторяйте проверку до тех пор, пока запрос не существует, затем перейдите на страницу, которая отображает / обрабатывает / что бы то ни было.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...