我有一个相当简单的查询(这次),我需要返回所有结果(我将它们存储在 Excel 电子表格中)。查询本身会使服务器超时,那么如何在不发生这种情况的情况下运行它呢?
7 回答
您可以增加页面的请求超时时间:
<cfsetting requestTimeout="3600" />
这将确保您有时间处理所有条目。
您可能还想将列表分成“块”。需要进行一些调整才能确定最佳块大小是多少,但您可以一次获取 100 或 1000 行结果,并在结果可用时使用 <cfflush> 将结果推送到屏幕上。这种方法还具有在冷融合服务器上使用更少内存的优点,因为从 SQL 服务器拉回的每一行都被加载到 CFML 内存中,并坐在那里直到查询对象变量被覆盖或超出范围(在这页纸)。这意味着您可以轻松地填满读取数十万行的coldFusion 内存,特别是如果这些行是“宽”的(即包含大的varchars 或文本)。
首先我会检查为什么这个查询需要这么长时间。
您可以在数据库级别做些什么来提高查询的性能。听起来你可能没有正确索引数据库。获取查询并将其放入可以分析执行计划的程序中。寻找滞后并解决它们。
为了获得更高的性能,如果您的数据库支持这种事情,请考虑创建索引视图。
接下来看看将查询的某些部分缓存出来。没有理由对每个请求的历史数据进行计算,因为它可以完成一次,然后缓存在某个地方的表中。
至于冷聚结束。确保您使用 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 分钟查询完成的请求。
如果您使用的是 SQL Server 或 Oracle,我认为 CFQUERY 公开了您可以设置的每个查询超时属性。同样,这对于真正长时间运行的查询是不可取的。
根据我的经验,如果您的查询非常复杂,或者返回的数据太多,以至于需要几分钟才能运行,那么是时候将查询的执行与启动它的请求分离了。有多种方法可以做到这一点,例如:
创建某种排队系统来记录待处理的服务请求。这可能是一个数据库表、磁盘上的一个 XML 文件等。当您的用户请求他们的数据时,您在此队列中注册该请求。
编写一个计划任务(例如 Java、DTS 或计划的 CF 页面),定期检查此队列的工作。根据您的需要,您可能会分拆一个后台线程来处理每个请求,或者计划任务直接处理它。如果您正在使用计划的 CF 页面,您需要将总工作量分解为可以迭代处理的更小的块,否则您将遇到与现在相同的问题。
一旦计划任务确定请求已完成,它就会启动某种处理已准备就绪的通知。例如,您可能会通过电子邮件向用户发送电子邮件,告诉他们数据已准备就绪,并附上一个链接以下载在磁盘上创建的 .csv 文件。
显然,正确的选择很大程度上取决于要解决的具体问题。一般来说,我会按以下顺序尝试这些事情:
- 积极攻击查询执行时间。您可以使用索引或编写更好的 T-SQL 吗?
- 如果查询需要一两分钟,并且运行频率很低,增加页面或查询超时可能是可以接受的。
- 如果查询经常运行,或者耗时超过 2-3 分钟,则硬着头皮构建一个批处理或排队系统来在后台处理查询。
最简单的方法是将查询的域分成几个部分。例如,向 WHERE 子句添加一个仅选择键范围前半部分的表达式,然后运行第二个查询以选择下半部分。然后合并输出。
尽管合并多个查询可能是更好的方法,但您可以根据每个请求设置超时。
<cfsetting
enableCFoutputOnly = "yes|no"
requestTimeOut = "value in seconds"
showDebugOutput = "yes|no" >
正确使用索引。尽可能创建外键。对于标准化的数据库,查询永远不会超时。
对连接和子句要非常小心,比如如果你group by
的查询中有子句而不是 usingwhere
子句,having
子句会运行得更快。从而减少查询执行时间。
使用成本估算来检查数据库中哪个表花费的时间最多或需要规范化。
我会将查询放入单独的线程中,将其加载到持久范围(例如会话)中。转发到检查查询是否存在的页面。重复检查直到查询存在,然后转发到显示/处理/不管它的页面。