“光标就像”在CLR过程/函数内读取

时间:2011-08-22 20:25:28

标签: c# sql-server-2008 sqlclr

我必须在数据上实现一个算法,这个算法存储在SQL服务器中(有充分理由)。该算法不适合SQL,因此我想将其实现为CLR函数或过程。这就是我想要做的事情:

  • 执行多个查询(通常为20-50,但最多为100-200),这些查询都具有select a,b,... from some_table order by xyz形式。有一个符合该查询的索引,因此无需任何计算即可获得或多或少的结果。

  • 逐步消耗结果。确切的步进取决于结果,因此它不是完全可预测的。

  • 通过逐步调整结果来汇总一些结果。我只会消耗结果的第一部分,但无法预测我需要多少。停止标准取决于算法中的某个阈值。

我的想法是打开几个SqlDataReader,但我遇到了两个问题:

  • 每个连接只能有一个SqlDataReader,在CLR方法中我只有一个连接 - 据我所知。

  • 我不知道如何告诉SqlDataReader如何以块的形式读取数据。我找不到SqlDataReader应该如何表现的文档。据我所知,它正在准备整个结果集,并将整个结果加载到内存中。即使我只消耗它的一小部分。

任何提示如何解决这个问题作为CLR方法?或者是否有更低级别的SQL服务器接口,这更适合我的问题?

更新:我应该更清楚地说明两点:

  1. 我在谈论大数据集,因此查询可能会产生1 mio记录,但我的算法只会消耗前100-200个记录。但正如我之前所说:我事先并不知道确切的数字。

  2. 我知道SQL可能不是那种算法的最佳选择。但由于其他限制,它必须是SQL服务器。所以我正在寻找最好的解决方案。

3 个答案:

答案 0 :(得分:4)

SqlDataReader不会读取整个数据集,您会将其与Dataset类混淆。它正在逐行读取,因为正在调用.Read()方法。如果客户端不使用结果集,则服务器将暂停查询执行,因为它没有空间将输出写入(所选行)。当客户端消耗更多行(正在调用SqlDataReader.Read)时,执行将恢复。甚至还有一个特殊的命令行为标志SequentialAccess,它指示ADO.Net不要在内存中预加载整行,这对于以流方式访问大型BLOB列非常有用(请参阅Download and Upload images from SQL Server via ASP.Net MVC了解实际情况例子)。

MARS处于活动状态时,您可以在单个连接上激活多个活动结果集(SqlDataReader)。但是,MARS与SQLCLR上下文连接不兼容。

因此,您可以创建一个CLR streaming TVF来完成CLR中所需的一些操作,但前提是您只有一个SQL查询源。要求您放弃上下文连接和使用的多个查询并不是完全成熟的连接,即。在回送中连接回同一个实例,这将允许MARS,从而消耗多个结果集。但是loopback有自己的问题,因为它打破了你从上下文连接中获得的事务边界。特别是使用环回连接,您的TVF将无法读取由调用TVF的同一事务所做的更改,因为不同的连接上的不同的事务。

答案 1 :(得分:1)

SQL旨在对付大量数据集,并且功能非常强大。使用基于集合的逻辑,通常不需要迭代数据来执行操作,并且在SQL本身中有许多内置方法可以执行此操作。

1)基于写集的逻辑来更新没有游标的数据

2)将确定性用户定义函数与基于集合的逻辑一起使用(您可以使用SqlFunction attribute in CLR code执行此操作)。非确定性会产生内部将查询转换为游标的影响,这意味着在给定相同输入的情况下,值输出并不总是相同。

[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static int algorithm(int value1, int value2)
{
    int value3 = ... ;
    return value3;
}

3)使用游标作为最后的手段。这是在数据库上每行执行逻辑但是会对性能产生影响的强大方法。它出现在this article CLR可以执行SQL游标(感谢Martin)。

我看到你的评论说使用基于集合的逻辑的复杂性太多了。你能提供一个例子吗?有许多SQL方法可以解决复杂问题 - CTE,视图,分区等。

当然,你可能在你的方法中是正确的,我不知道你想做什么,但我的直觉说利用SQL的工具。产生多个读取器不是接近数据库实现的正确方法。您可能需要多个线程调用SP来运行并发处理,但不要在CLR中执行此操作。

要回答您的问题,使用CLR实现(和IDataReader),您实际上不需要以块的形式分页结果,因为您没有将数据加载到内存或通过网络传输数据。 IDataReader使您可以逐行访问数据流。根据声音,您的算法会确定需要更新的记录数量,因此当发生这种情况时,只需停止调用Read()并在此时结束。

SqlMetaData[] columns = new SqlMetaData[3];
columns[0] = new SqlMetaData("Value1", SqlDbType.Int);
columns[1] = new SqlMetaData("Value2", SqlDbType.Int);
columns[2] = new SqlMetaData("Value3", SqlDbType.Int);

SqlDataRecord record = new SqlDataRecord(columns);
SqlContext.Pipe.SendResultsStart(record);

SqlDataReader reader = comm.ExecuteReader();

bool flag = true;

while (reader.Read() && flag)
{
    int value1 = Convert.ToInt32(reader[0]);
    int value2 = Convert.ToInt32(reader[1]);

    // some algorithm 
    int newValue = ...;

    reader.SetInt32(3, newValue);        

    SqlContext.Pipe.SendResultsRow(record);

    // keep going?
    flag = newValue < 100;
 }

答案 2 :(得分:0)

游标是仅限SQL的函数。如果您想一次读取数据块,则需要进行某种分页,以便只返回一定数量的记录。如果使用Linq,

.Skip(Skip)


.Take(PageSize)

Skips and takes可用于限制返回的结果。

您可以通过执行以下操作简单地遍历DataReader:

using (IDataReader reader = Command.ExecuteReader())
{
    while (reader.Read())
    {
        //Do something with this record
    }
}

这将一次迭代一次结果,类似于SQL Server中的游标。

对于多个记录集,请尝试MARS  (如果是SQL Server)

http://msdn.microsoft.com/en-us/library/ms131686.aspx