我需要在完成之前从存储过程返回部分结果(作为简单选择)。
有可能这样做吗?
如果是,该怎么做?
如果没有,有什么解决方法?
编辑:我有该过程的几个部分。在第一部分中,我计算了几个字符串。我将在后面的过程中使用它们进行附加操作。问题是调用者需要尽快使用字符串。因此,我需要计算该字符串并将其传递回(以某种方式,例如通过选择),然后继续工作。调用方可以更快地获取其有价值的字符串。
呼叫者是一个Web服务。
我需要在完成之前从存储过程返回部分结果(作为简单选择)。
有可能这样做吗?
如果是,该怎么做?
如果没有,有什么解决方法?
编辑:我有该过程的几个部分。在第一部分中,我计算了几个字符串。我将在后面的过程中使用它们进行附加操作。问题是调用者需要尽快使用字符串。因此,我需要计算该字符串并将其传递回(以某种方式,例如通过选择),然后继续工作。调用方可以更快地获取其有价值的字符串。
呼叫者是一个Web服务。
Answers:
您可能正在寻找RAISERROR
带有该NOWAIT
选项的命令。
根据备注:
RAISERROR可以用作PRINT的替代方法,以将消息返回给调用应用程序。
这不会返回SELECT
语句的结果,但可以让您将消息/字符串传递回客户端。如果要返回所选数据的快速子集,则可能需要考虑FAST
查询提示。
指定优化查询以快速检索第一个number_rows。这是一个非负整数。返回第一个number_rows之后,查询将继续执行并产生其完整结果集。
由Shannon Severance在评论中添加:
来自Erland Sommarskog的SQL Server中的错误和事务处理:
不过请注意,某些API和工具可能会在其一侧进行缓冲,从而使的作用无效
WITH NOWAIT
。
有关完整的上下文,请参见源文章。
FAST
在我需要同步执行存储过程和C#代码以加剧和重现竞争条件的问题中,为我解决了该问题。以编程方式使用结果集要比使用像这样容易RAISERROR()
。当我开始阅读您的答案时,似乎您是在说无法解决问题SELECT
,所以也许可以澄清一下吗?
UPDATE: 见strutzky的答案(以上)和至少一个例子,我希望在这里描述这种不行为的意见。如果时间允许,我将不得不尝试/进一步阅读以更新我的理解...
如果调用方与数据库异步交互或处于线程/多进程状态,则可以在第二个会话仍在运行时打开第二个会话,可以创建一个表来保存部分数据并在过程进行时对其进行更新。然后,可以通过设置了事务隔离级别1的第二个会话来读取它,以使其能够读取未提交的更改:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table
1:根据评论和srutzky答案中的后续更新,如果所监视的进程未包装在事务中,则不需要设置隔离级别,尽管我倾向于在不引起这种情况的情况下将其设置为习惯在这些情况下不需要时会造成伤害
当然,如果您可以以这种方式运行多个进程(如果您的Web服务器接受并发用户,则很可能会发生这种情况,这种情况很少发生),则需要以某种方式标识此进程的进度信息。也许将新创建的UUID作为密钥传递给过程,将其添加到进度表中,并读取:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>
我已经使用这种方法来监视SSMS中长时间运行的手动过程。我无法决定它是否“太臭”了,我无法考虑在生产中使用它...
OP已经尝试发送多个结果集(而不是MARS),并且确实确实在返回任何结果集之前确实等待存储过程完成。考虑到这种情况,这里有一些选择:
如果您的数据足够小,可以容纳128个字节,那么您很可能会使用SET CONTEXT_INFO
通过使其可见的值SELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
。您只需要执行一个快速查询,然后再将存储过程运行到SELECT @@SPID;
并通过进行抓取即可SqlCommand.ExecuteScalar
。
我刚刚测试了它,它确实起作用。
与@David的将数据放入“进度”表中的建议类似,但是无需担心清理或并发/进程分离问题:
Guid
在应用程序代码中创建一个新代码,并将其作为参数传递给存储过程。将此Guid存储在变量中,因为它将多次使用。CREATE TABLE ##MyProcess_{GuidFromApp};
。该表可以具有所需的任何数据类型的任何列。只要有数据,就将其插入该全局临时表中。
在应用程序代码中,开始尝试读取数据,但将其包装为SELECT
一个,IF EXISTS
这样即使尚未创建表也不会失败:
IF (OBJECT_ID('tempdb..[##MyProcess_{0}]')
IS NOT NULL)
BEGIN
SELECT * FROM [##MyProcess_{0}];
END;
使用String.Format()
,您可以替换{0}
为Guid变量中的值。测试是否为Reader.HasRows
,如果为true,则读取结果,否则调用Thread.Sleep()
或进行其他任何查询,然后再次轮询。
优点:
EXEC
/ sp_executesql
调用)的结尾中幸存下来
我已经对此进行了测试,并且可以正常工作。您可以使用以下示例代码自行尝试。
在一个查询选项卡中,运行以下命令,然后突出显示大注释中的三行并运行:
CREATE
--ALTER
PROCEDURE #GetSomeInfoBackQuickly
(
@MessageTableName NVARCHAR(50) -- might not always be a GUID
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX) = N'CREATE TABLE [##MyProcess_' + @MessageTableName
+ N'] (Message1 NVARCHAR(50), Message2 NVARCHAR(50), SomeNumber INT);';
-- Do some calculations
EXEC (@SQL);
SET @SQL = N'INSERT INTO [##MyProcess_' + @MessageTableName
+ N'] (Message1, Message2, SomeNumber) VALUES (@Msg1, @Msg2, @SomeNum);';
DECLARE @SomeNumber INT = CRYPT_GEN_RANDOM(2);
EXEC sp_executesql
@SQL,
N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
@Msg1 = N'wow',
@Msg2 = N'yadda yadda yadda',
@SomeNum = @SomeNumber;
WAITFOR DELAY '00:00:10.000';
SET @SomeNumber = CRYPT_GEN_RANDOM(3);
EXEC sp_executesql
@SQL,
N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
@Msg1 = N'wow',
@Msg2 = N'yadda yadda yadda',
@SomeNum = @SomeNumber;
WAITFOR DELAY '00:00:10.000';
GO
/*
DECLARE @TempTableID NVARCHAR(50) = NEWID();
RAISERROR('%s', 10, 1, @TempTableID) WITH NOWAIT;
EXEC #GetSomeInfoBackQuickly @TempTableID;
*/
转到“消息”选项卡,然后复制已打印的GUID。然后,打开另一个查询选项卡并运行以下命令,将从另一个会话的“消息”选项卡复制的GUID放入第1行的变量初始化中:
DECLARE @TempTableID NVARCHAR(50) = N'GUID-from-other-session';
EXEC (N'SELECT * FROM [##MyProcess_' + @TempTableID + N']');
继续打F5。您应该在前10秒钟看到1个条目,然后在接下来的10秒钟看到2个条目。
您可以使用SQLCLR通过Web服务或其他某种方式来回调您的应用程序。
您也许可以使用PRINT
/ RAISERROR(..., 1, 10) WITH NOWAIT
立即将字符串传递回去,但是由于以下问题,这会有些棘手:
VARCHAR(8000)
或NVARCHAR(4000)
默认情况下,也不会发送消息,直到该过程完成。这种行为,但是,可以通过设定改变SqlConnection.FireInfoMessageEventOnUserErrors属性来true
。该文档指出:
当您将FireInfoMessageEventOnUserErrors设置为true时,以前被视为异常的错误现在将作为InfoMessage事件处理。所有事件均立即触发,并由事件处理程序处理。如果将FireInfoMessageEventOnUserErrors设置为
false
,则在该过程结束时处理InfoMessage事件。
不利的一面是,大多数SQL错误将不再引发SqlException
。在这种情况下,您需要测试传递到消息事件处理程序中的其他事件属性。这对于整个连接都是正确的,这使事情有些棘手,但并非难以控制。
所有消息都显示在同一级别,没有单独的字段或属性来区分彼此。接收顺序应与发送方式相同,但不确定是否足够可靠。您可能需要包括一个标记或随后可以解析的内容。这样,您至少可以确定哪个是哪个。
RETURN
声明之后)。因此它不起作用。
SqlConnection
?您想传回多少数据?什么数据类型?你有没有试过要么PRINT
还是RAISERROR WITH NOWAIT
?