非常に大きなクエリを実行する方法(SQL ServerおよびColdFusion)
-
03-07-2019 - |
質問
合理的な単純なクエリ(今回)があり、すべての結果が必要です(Excelスプレッドシートに保存しています)。クエリ自体がサーバーをタイムアウトするので、それを実行せずに実行するにはどうすればよいですか?
解決
最も簡単な方法は、クエリのドメインをいくつかの部分に分割することです。たとえば、キー範囲の前半のみを選択する式をWHERE句に追加し、2番目のクエリを実行して下半分を選択します。次に、出力をマージします。
他のヒント
ページのリクエストタイムアウトを増やすことができます:
<cfsetting requestTimeout="3600" />
これにより、すべてのエントリを処理する時間を確保できます。
リストを<!> quot; chunks <!> quot;に分割することもできます。最適なチャンクサイズを確認するには調整が必要ですが、結果を一度に100行または1000行取得して、<!> lt; cfflush <!> gt;を使用できます。結果が利用可能になったときに画面に結果をプッシュします。このアプローチには、SQLサーバーから引き戻されたすべての行がCFMLメモリに読み込まれ、クエリオブジェクト変数が上書きされるかスコープから外れるまでそこにとどまるため、coldFusionサーバーで使用するメモリが少ないという利点もあります(ページ)。これは、特に行が<!> quot; wide <!> quot;である場合、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分以上かかる場合は、弾丸を噛んで、バックグラウンドでクエリを処理するバッチ処理またはキューイングシステムを構築します。
リクエストごとにタイムアウトを設定できますが、複数のクエリをマージする方がよい場合があります。
<cfsetting
enableCFoutputOnly = "yes|no"
requestTimeOut = "value in seconds"
showDebugOutput = "yes|no" >
インデックスを適切に使用します。可能な限り外部キーを作成します。正規化されたデータベースのクエリはタイムアウトしません。
結合と句には十分注意してください。たとえば、クエリにgroup by
句がある場合、where
句を使用する代わりに、having
句が高速になります。したがって、クエリの実行時間を短縮できます。
コスト見積もりを使用して、どのテーブルが最も時間がかかっているか、データベースで正規化が必要かを確認します。
クエリを別のスレッドにスローし、永続的なスコープ(セッションなど)に読み込みます。クエリの存在を確認するページに転送します。クエリが存在するまでチェックを繰り返し、それを表示/処理/何でもするページに転送します。