【问题标题】:How to run a very large query (SQL Server and ColdFusion)如何运行非常大的查询(SQL Server 和 ColdFusion)
【发布时间】:2010-09-18 14:53:13
【问题描述】:

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

【问题讨论】:

  • 你的问题被标记为 w/sql server,但提到了 excel。如果您的数据实际上是在 excel 中,那么将这些数据保存在“真实”数据库中可能是值得的,因为它们在设计时就考虑到了这种用途。

标签: sql-server coldfusion


【解决方案1】:

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

【讨论】:

  • 所以如果我把它扔到一个循环中,一次选择 1000 条记录,然后一遍又一遍地合并结果直到到达终点,它不会超时吗?
  • 假设您是从数据库环境(即应用程序或脚本)外部发出查询,那么它会将每个单独的查询视为单独的,并且不会“将它们集中在一起” " 用于检测超时。
  • 好的,最后一个问题,既然 SQL Server 没有限制,你将如何一次将它们分成 1000 条记录的块?
  • 可以在SQL2005中模拟MySQL的限制,但是比较难: WITH EMPLOYEE_ROWS AS (SELECT ROW_NUMBER() OVER (ORDER BY EMPID ASC) AS ROWID, * FROM EMPLOYEE) SELECT * FROM EMPLOYEE_ROWS WHERE ROWID between #begin# 和 #end#
  • 对于每年运行一次的查询,只需增加 SQL 连接的超时时间(我相信 ColdFusion 有这方面的功能)。超时的不是 SQL 服务器,而是等待时间不够长的 CF 脚本。如果超时时间较长,您将不需要此拆分/合并业务。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    <cfsetting requestTimeout="3600" />
    

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

    您可能还想将列表分成“块”。需要进行一些调整才能找出最佳块大小是多少,但您可以一次获取 100 或 1000 行结果,并在结果可用时使用 将结果推送到屏幕上。这种方法还具有在冷融合服务器上使用更少内存的优点,因为从 SQL 服务器拉回的每一行都被加载到 CFML 内存中,并坐在那里直到查询对象变量被覆盖或超出范围(在这页纸)。这意味着您可以轻松填满读取数十万行的 ColdFusion 内存,尤其是在这些行很“宽”(即包含大的 varchar 或文本)的情况下。

    【讨论】:

    • 好的,所以如果我继续在 CF 变量中存储和合并结果,这是否会自动处理“刷新”过程,因为我没有打印到屏幕上? (完成后存入excel)
    • 只要你继续调用相同的查询,它就会破坏旧的引用并将其标记为准备好进行垃圾回收。这大致相当于从内存中清除它。
    【解决方案4】:

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

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

    根据我的经验,如果您的查询非常复杂,或者返回的数据太多,以至于需要 分钟 才能运行,那么是时候将查询的执行与发起的请求分离它。有很多方法可以做到这一点,例如:

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

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

    3. 一旦计划任务确定请求已完成,它就会启动某种通知,表明处理已准备就绪。例如,您可以通过电子邮件向用户发送电子邮件,告诉他们数据已准备就绪,并附上下载在磁盘上创建的 .csv 文件的链接。

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

    1. 积极攻击查询执行时间。您可以使用索引或编写更好的 T-SQL 吗?
    2. 如果查询需要一两分钟,并且运行频率非常低,增加页面或查询超时可能是可以接受的。
    3. 如果查询经常运行,或者耗时超过 2-3 分钟,请硬着头皮构建批处理或排队系统以在后台处理查询。

    【讨论】:

    • 感谢您的回复!查询将很少运行。一年一次左右。它会为我们的客户生成一个包含大量数据的 Excel 电子表格,然后他们将这些数据重新导入。我正在考虑使用循环查询,因为它现在最适合我的需求:)
    【解决方案5】:

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

    【讨论】:

      【解决方案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>
      

      【讨论】:

      • 我经常惊讶于通过添加或调整一些简单的数据库索引可以节省多少令人头疼的开发时间!
      【解决方案7】:

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

      要非常小心连接和子句,例如如果您的查询中有group by 子句而不是使用where 子句,having 子句会运行得更快。从而减少查询执行时间。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-05-16
        • 1970-01-01
        • 2010-12-07
        • 2012-01-15
        • 2011-03-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多