如何运行一个非常大的查询(SQL Server和ColdFusion)

时间:2008-10-22 18:01:43

标签: sql-server coldfusion

我有一个相当简单的查询(这次),我需要回复所有结果(我将它们存储在Excel电子表格中)。查询本身会超时服务器,所以如何在没有发生这种情况的情况下运行它?

7 个答案:

答案 0 :(得分:7)

您可以增加页面的请求超时:

<cfsetting requestTimeout="3600" />

这将确保您有时间处理所有条目。

您可能还想将列表分成“块”。需要进行一些调整以找出最佳块大小,但是您可以一次获取结果100或1000行,并使用&lt; cfflush&gt;在结果可用时将结果推送到屏幕。这种方法还具有在coldFusion服务器上使用更少内存的优势,因为从SQL服务器撤回的每一行都被加载到CFML内存中,并且坐在那里直到查询对象变量被覆盖或超出范围(在结束时)这页纸)。这意味着您可以轻松填充读取数十万行的coldFusion内存,特别是如果行是“宽”的(即包含大的varchars或文本)。

答案 1 :(得分:6)

首先,我会检查为什么这个查询花了这么长时间。

您可以在数据库级别执行哪些操作来提高查询性能。听起来好像你没有正确索引数据库。接受查询并将其放入一些程序中,您可以分析执行计划。寻找滞后并解决它们。

为了获得更高的性能,如果您的数据库支持那种事情,请考虑创建索引视图。

接下来看一下缓存查询的某些部分。没有理由对每个请求的历史数据进行计算,只要它可以执行一次,然后在某个表中缓存。

冷冻结束。确保您使用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>

答案 2 :(得分:4)

正如其他人所指出的那样,您可以尝试增加页面的请求超时,但如果查询的执行是在分钟中测量的,而不是秒或毫秒,则不建议这样做。 CF一次只能处理一定数量的请求,因此您需要小心锁定其中一个请求,等待5分钟的查询完成。

如果您使用的是SQL Server或Oracle,我认为CFQUERY会公开您自己可以设置的每个查询超时属性。同样,对于长时间运行的查询,这是不可取的。

根据我的经验,如果您的查询要么太复杂,要么返回太多数据,要运行分钟,那么是时候将查询的执行与启动的请求分离它。您可以通过多种方式执行此操作,例如:

  1. 创建某种排队系统来记录待处理的服务请求。这可以是数据库表,磁盘上的XML文件等。当您的用户请求他们的数据时,您使用此队列注册该请求。

  2. 编写一个计划任务(例如Java,DTS或计划的CF页面),定期检查此队列是否有效。根据您的需要,您可能会剥离后台线程来处理每个请求,或者计划任务可能直接处理它。如果您正在使用预定的CF页面,则需要将总工作量分解为可以迭代处理的较小块,否则您将遇到同样的问题。

  3. 一旦计划任务确定已填写请求,它就会启动某种处理准备就绪的通知。例如,您可以通过电子邮件通知用户数据已准备就绪,并提供下载在磁盘上创建的.csv文件的链接。

  4. 显然,正确的选择很大程度上取决于要解决的具体问题。一般来说,我会按以下顺序尝试这些事情:

    1. 积极攻击查询执行时间。你可以使用索引或编写更好的T-SQL吗?
    2. 如果查询花了一两分钟,并且很少运行,则可能会增加页面或查询超时。
    3. 如果查询经常运行,或者需要超过2-3分钟,请咬紧牙关并构建一个批处理或排队系统,以便在后台处理查询。

答案 3 :(得分:2)

最简单的方法是将查询域分成几个部分。例如,向WHERE子句添加一个表达式,该表达式仅选择键范围的前半部分,然后运行第二个查询以选择下半部分。然后合并输出。

答案 4 :(得分:1)

您可以根据每个请求设置超时,但合并多个查询可能是更好的方法。

<cfsetting 
enableCFoutputOnly = "yes|no" 
requestTimeOut = "value in seconds"
showDebugOutput = "yes|no" >

答案 5 :(得分:1)

正确使用索引。尽可能创建外键。对于标准化的数据库,查询永远不会超时。

对连接和子句要非常小心,比如在查询中有group by子句而不是使用where子句,having子句将更快地工作。从而减少查询执行时间。

使用成本估算来检查哪个表占用的时间最多或需要在数据库中进行规范化。

答案 6 :(得分:0)

我会将查询抛出到一个单独的线程中,将其加载到持久范围(例如会话)中。转发到检查查询是否存在的页面。重复检查直到查询存在,然后转发到显示/处理/淡化它的页面。