如何对可以取消的DataTable操作执行SQL查询

时间:2012-06-28 07:07:12

标签: c# sql sql-server sqldatareader sqlcommand

我试图让标题尽可能具体。基本上我在后台工作线程中运行的是现在的一些代码:

 SqlConnection conn = new SqlConnection(connstring);
                    SqlCommand cmd = new SqlCommand(query, conn);
                    conn.Open();
                    SqlDataAdapter sda = new SqlDataAdapter(cmd);
                    sda.Fill(Results);
                    conn.Close();
                    sda.Dispose();

其中query是表示大量耗时查询的字符串,conn是连接对象。

我现在的问题是我需要一个停止按钮。我已经意识到杀死背景工作者将毫无价值,因为我仍然希望在查询取消后保留结果。此外,在查询之后,它将无法检查已取消的状态。

到目前为止我想出了什么:

我一直在努力构思如何有效地处理这个问题,而不会造成太大的性能损失。

我的想法是使用SqlDataReader一次从查询片段中读取数据,以便我有一个“循环”来检查我可以通过按钮从GUI设置的标志。问题是据我所知,我不能使用数据表的Load()方法,仍然可以取消sqlcommand。如果我错了请告诉我,因为这会使取消稍微容易一些。

根据我发现的内容,我实现了我可能只能取消sqlcommand mid-query,如果我做了类似下面的内容(伪代码):

while(reader.Read())
{
 //check flag status
 //if it is set to 'kill' fire off the kill thread

 //otherwise populate the datatable with what was read
}

然而,在我看来,这将是非常无效的,并且可能代价高昂。这是杀死正在进行的绝对需要在数据表中的sqlcommand的唯一方法吗?任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:5)

确实有两个阶段取消事项:

  1. 在返回第一行之前取消初始查询执行
  2. 在提供行时,中止读取行的过程
  3. 根据实际sql语句的性质,这些步骤中的任何一个都可能是99%的时间,因此它们都应该被考虑。例如,在具有十亿行的某个表上调用SELECT *将基本上没有时间执行,但需要很长时间才能读取。相反,在调优不佳的表上请求超级复杂的连接,然后将它们全部包含在一些聚合子句中可能需要几分钟才能执行,但是一旦实际返回它们,读取少量行的时间可以忽略不计。

    经过良好调整的高级数据库引擎也会一次缓存大量的行以进行复杂的查询,因此您将看到交替暂停,其中引擎在下一批行上执行查询,然后在返回时快速突发数据下一批结果。

    取消查询执行

    为了能够在执行时取消查询,您可以使用SqlCommand.BeginExecuteReader之一的重载来启动查询,并调用SqlCommand.Cancel来中止查询。或者,您可以在一个线程中同步调用ExecuteReader(),并仍然从另一个线程调用Cancel()。我没有包含代码示例,因为文档中有很多代码示例。

    中止阅读操作

    这里使用简单的布尔标志可能是最简单的方法。并且记住使用带有对象数组的Rows.Add()重载填充数据表行非常容易,即:

    object[] buffer = new object[reader.FieldCount]
    while(reader.Read()) {
        if(cancelFlag) break;
        reader.GetValues(buffer);
        dataTable.Rows.Add(buffer);
    }
    

    取消阻止调用Read()

    如前所述,对reader.Read()的调用导致数据库引擎进行另一批密集处理时,会发生一种混合情况。如MSDN文档中所述,即使原始查询是使用Read()执行的,在这种情况下对BeginExecuteReader的调用也可能会被阻止。您仍然可以通过在一个线程中调用Read()来解决这个问题,该线程处理所有读取但在另一个线程中调用Cancel()。您知道读者是否处于阻塞Read调用中的方式是在监视线程读取时让读者线程更新另一个标志:

    ...
    inRead = true
    while(reader.Read()) {
        inRead = false
        ...
        inRead = true
    }
    
    // Somewhere else:
    private void foo_onUITimerTick(...) {
       status.Text = inRead ? "Waiting for server" : "Reading";
    }
    

    关于Reader与适配器的性能

    DataReader通常比使用DataAdapter.Fill()更快。对于阅读,DataReader的重点是真正,非常快速和响应。每行检查一次布尔标志,即使在数百万行上也不会增加可测量的时间差。

    大数据库查询的限制因素不是本地CPU处理时间,而是I / O管道的大小(远程数据库的网络连接或本地CPU的网络连接速度)或db的组合服务器自身的磁盘速度和复杂查询的CPU处理时间。 DataAdapter和DataReader都会花费时间(可能大部分时间)只是等待几纳秒一次为下一行服务。

    DataAdapter.Fill()的一个便利是它能够动态生成DataTable列以匹配查询结果,但这并不难做到(请参阅SqlDataReader.GetSchemaTable())。

答案 1 :(得分:0)

试一试

我建议您在 BackgroundWorker 中添加耗时的查询,并将命令传递给它。这样你就可以控制命令对象了。当取消命令到来时,只需将command.Cancel()

传递给(正在进行中的BackgroundWorker)命令