Bog*_*nov 9 stored-procedures t-sql sql-server-2012 multi-thread web-service
我需要在完成之前从存储过程返回部分结果(作为简单的选择)。
有可能这样做吗?
如果是,如何做到这一点?
如果没有,有什么解决方法吗?
编辑:我有几个部分的程序。在第一部分我计算了几个字符串。我稍后会在过程中使用它们来进行附加操作。问题是调用者尽快需要字符串。因此,我需要计算该字符串并将其传回(不知何故,例如从选择中),然后继续工作。调用者更快地获得其有价值的字符串。
调用者是一个 Web 服务。
Eri*_*rik 11
您可能正在寻找RAISERROR
带有NOWAIT
选项的命令。
根据备注:
RAISERROR 可用作 PRINT 的替代方法,以将消息返回给调用应用程序。
这不会从SELECT
语句返回结果,但它会让您将消息/字符串传递回客户端。如果您想返回您选择的数据的一个快速子集,那么您可能需要考虑FAST
查询提示。
指定为快速检索第一个 number_rows 优化查询。这是一个非负整数。返回第一个 number_rows 后,查询将继续执行并生成其完整结果集。
新增由香遣散的评论:
来自Erland Sommarskog在 SQL Server 中的错误和事务处理:
但是请注意,某些 API 和工具可能会在其一侧进行缓冲,从而使
WITH NOWAIT
.
有关完整上下文,请参阅源文章。
OP 已经尝试发送多个结果集(不是 MARS)并且已经看到它确实在返回任何结果集之前等待存储过程完成。考虑到这种情况,这里有一些选择:
如果您的数据小到足以容纳 128 个字节,您很可能会使用SET CONTEXT_INFO
which 使该值通过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;
Run Code Online (Sandbox Code Playgroud)使用String.Format()
,您可以替换{0}
为 Guid 变量中的值。测试 if Reader.HasRows
,如果 true 则读取结果,else 调用Thread.Sleep()
或其他方法再次轮询。
好处:
EXEC
/sp_executesql
调用)
我已经对此进行了测试,它按预期工作。您可以使用以下示例代码自行尝试。
在一个查询选项卡中,运行以下命令,然后突出显示块注释中的 3 行并运行:
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;
*/
Run Code Online (Sandbox Code Playgroud)
转到“消息”选项卡并复制打印的 GUID。然后,打开另一个查询选项卡并运行以下命令,将从另一个会话的消息选项卡复制的 GUID 放入第 1 行的变量初始化中:
DECLARE @TempTableID NVARCHAR(50) = N'GUID-from-other-session';
EXEC (N'SELECT * FROM [##MyProcess_' + @TempTableID + N']');
Run Code Online (Sandbox Code Playgroud)
继续打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
. 在这种情况下,您需要测试传递到消息事件处理程序的其他事件属性。这适用于整个连接,这使事情变得有点棘手,但并非无法管理。
所有消息都出现在同一级别,没有单独的字段或属性来区分彼此。接收它们的顺序应该与发送它们的顺序相同,但不确定这是否足够可靠。您可能需要包含一个标签或一些您可以解析的内容。这样你至少可以确定哪个是哪个。
更新: 请参阅 strutzky 的回答(上面)以及至少一个示例的评论,其中的行为与我的预期和描述不符。在时间允许的情况下,我将不得不进一步试验/阅读以更新我的理解......
如果您的调用方与数据库异步交互或者是线程/多进程的,因此您可以在第一个会话仍在运行时打开第二个会话,您可以创建一个表来保存部分数据并随着过程的进行更新。然后,它可以被设置为事务隔离级别1的第二个会话读取,以使其能够读取未提交的更改:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table
Run Code Online (Sandbox Code Playgroud)
1:根据 srutzky 回答中的评论和后续更新,如果被监视的进程没有包含在事务中,则不需要设置隔离级别,尽管在这种情况下我倾向于将其设置为不习惯,因为它不会导致在这些情况下不需要时伤害
当然,如果您可以有多个进程以这种方式运行(如果您的 Web 服务器接受并发用户,这很可能,但这种情况很少见),您将需要以某种方式识别此进程的进度信息. 也许将新创建的 UUID 作为键传递给程序,将其添加到进度表中,然后阅读:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>
Run Code Online (Sandbox Code Playgroud)
我已经使用这种方法来监控 SSMS 中长时间运行的手动进程。我无法决定它是否“闻起来”太多让我考虑在生产中使用它虽然......
归档时间: |
|
查看次数: |
2998 次 |
最近记录: |