すべての結果を返す必要がある (今回は) かなり単純なクエリを取得しました (結果を Excel スプレッドシートに保存しています)。クエリ自体がサーバーをタイムアウトさせるので、それを起こさずに実行するにはどうすればよいでしょうか?
7 に答える
ページのリクエスト タイムアウトを増やすことができます。
<cfsetting requestTimeout="3600" />
これにより、すべてのエントリを処理する時間が確保されます。
リストを「チャンク」に分割することもできます。最適なチャンク サイズを見つけるには多少の調整が必要ですが、一度に 100 行または 1000 行の結果を取得し、<cfflush> を使用して、結果が利用可能になったときに画面にプッシュすることができます。このアプローチには、coldFusion サーバーで使用するメモリが少なくなるという利点もあります。SQL サーバーから引き戻されたすべての行が CFML メモリにロードされ、クエリ オブジェクト変数が上書きされるか範囲外になるまでそこに留まるからです (ページ)。つまり、数十万行を読み取ると、coldFusion メモリが簡単にいっぱいになる可能性があります。特に、行が「幅が広い」場合 (つまり、大きな varchar またはテキストを含む場合) はそうです。
まず、このクエリに時間がかかる理由を確認します。
クエリのパフォーマンスを向上させるために、データベースレベルで何ができるか。データベースのインデックスが適切に作成されていないようです。クエリを取得して、実行プランを分析できるプログラムにスローします。ラグを探して対処します。
パフォーマンスを向上させるには、データベースがそのようなことをサポートしている場合は、インデックス付きビューの作成を検討してください。
次に、クエリの一部をキャッシュアウトする方法を見てみましょう。一度実行してからどこかのテーブルにキャッシュできる場合は、すべての要求の履歴データを計算する理由はありません。
コールドフュージョン終了も。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>
他の人が指摘したように、ページのリクエスト タイムアウトを増やしてみることもできますが、クエリの実行が秒やミリ秒ではなく分単位で測定される場合はお勧めできません。CF は一度に設定された数の要求のみを処理するため、5 分間のクエリが完了するのを待っている要求の 1 つをロックしないように注意する必要があります。
SQL Server または Oracle を使用している場合、CFQUERY は、設定可能な独自のクエリごとのタイムアウト属性を公開していると思います。繰り返しますが、これは非常に長時間実行されるクエリにはお勧めできません。
私の経験では、クエリが非常に複雑であるか、実行に数分かかるほど多くのデータを返す場合は、クエリの実行をそれを開始するリクエストから分離する必要があります。これを行うには、次のようないくつかの方法があります。
保留中のサービス要求を記録するために、ある種のキュー システムを作成します。これは、DB テーブル、ディスク上の XML ファイルなどです。ユーザーがデータを要求すると、その要求をこのキューに登録します。
このキューの作業を定期的にチェックするスケジュールされたタスク (Java、DTS、またはスケジュールされた CF ページなど) を作成します。必要に応じて、バックグラウンド スレッドをスピンオフして各リクエストを処理するか、スケジュールされたタスクで直接処理することができます。スケジュールされた CF ページを使用している場合は、合計ワークロードを繰り返し処理できる小さなチャンクに分割する必要があります。そうしないと、現在と同じ問題が発生します。
スケジュールされたタスクは、要求が満たされたと判断すると、処理の準備が整ったことを示す何らかの通知を開始します。たとえば、ディスク上に作成された .csv ファイルをダウンロードするためのリンクを付けて、データの準備ができたことをユーザーにメールで通知できます。
明らかに、正しい選択は、解決する特定の問題に大きく依存します。一般的に、次の順序でこれらのことを試します。
- クエリの実行時間を積極的に攻撃します。インデックスを使用したり、より適切な T-SQL を記述したりできますか?
- クエリに 1 ~ 2 分かかり、実行頻度が非常に低い場合は、ページまたはクエリのタイムアウトを増やしても問題ない可能性があります。
- クエリが頻繁に実行される場合、または 2 ~ 3 分以上かかる場合は、バックグラウンドでクエリを処理するバッチ システムまたはキュー システムを構築してください。
最も簡単な方法は、クエリのドメインをいくつかの部分に分割することです。たとえば、キー範囲の前半のみを選択する式を WHERE 句に追加してから、2 番目のクエリを実行して下半分を選択します。次に、出力をマージします。
リクエストごとにタイムアウトを設定できますが、複数のクエリをマージする方が良い方法かもしれません。
<cfsetting
enableCFoutputOnly = "yes|no"
requestTimeOut = "value in seconds"
showDebugOutput = "yes|no" >
インデックスを適切に使用してください。可能な限り外部キーを作成します。正規化されたデータベースでは、クエリがタイムアウトすることはありません。
句group by
を使用する代わりにクエリに句がある場合など、結合と句には十分注意してください。句はより高速に動作します。したがって、クエリの実行時間が短縮されます。where
having
コスト見積もりを使用して、データベースで最も時間がかかっているテーブルや正規化が必要なテーブルを確認します。
クエリを別のスレッドにスローし、永続的なスコープ (セッションなど) にロードします。クエリの存在を確認するページに転送します。クエリが存在するまでチェックを繰り返し、それを表示/処理/何でもするページに転送します。