我使用带有内部激活的SQL Service Broker将作业列表移动到内部激活的存储过程以完成,而不保持主线程/请求者等待实际的单个作业完成。本质上我正试图释放UI线程。问题是,我向服务经纪人传递了2000多个工作,并且消息在大约25分钟内到达队列并释放了用户界面,但即使一小时后,它也只完成了近600个工作岗位 我使用下面的查询来计算等待完成的数字,它看起来非常慢
SELECT COUNT(*)
FROM [HMS_Test].[dbo].[HMSTargetQueueIntAct]
WITH(NOLOCK)
以下是我的ref的激活存储过程。有人可以看看,让我知道这有什么问题吗?如何让SB快速完成队列中的这些项目?在此先感谢:)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_SB_HMSTargetActivProc]
AS
BEGIN
DECLARE @RecvReqDlgHandle UNIQUEIDENTIFIER;
DECLARE @RecvReqMsg NVARCHAR(1000);
DECLARE @RecvReqMsgName sysname;
DECLARE @XMLPtr int
DECLARE @ExecuteSQL nvarchar(1000)
DECLARE @CallBackSP nvarchar(100)
DECLARE @CallBackSQL nvarchar(1000)
DECLARE @SBCaller nvarchar(50)
DECLARE @LogMsg nvarchar(1000)
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
WAITFOR
( RECEIVE TOP(1)
@RecvReqDlgHandle = conversation_handle,
@RecvReqMsg = message_body,
@RecvReqMsgName = message_type_name
FROM HMSTargetQueueIntAct
), TIMEOUT 5000;
IF (@@ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION;
BREAK;
END
IF @RecvReqMsgName = N'//HMS/InternalAct/RequestMessage'
BEGIN
DECLARE @ReplyMsg NVARCHAR(100);
SELECT @ReplyMsg = N'<ReplyMsg>ACK Message for Initiator service.</ReplyMsg>';
SEND ON CONVERSATION @RecvReqDlgHandle
MESSAGE TYPE
[//HMS/InternalAct/ReplyMessage]
(@ReplyMsg);
EXECUTE sp_xml_preparedocument @XMLPtr OUTPUT, @RecvReqMsg
SELECT @ExecuteSQL = ExecuteSQL
,@CallBackSP = CallBackSP
,@SBCaller = SBCaller
FROM OPENXML(@XMLPtr, 'RequestMsg/CommandParameters', 1)
WITH (ExecuteSQL nvarchar(1000) 'ExecuteSQL'
,CallBackSP nvarchar(1000) 'CallBackSP'
,SBCaller nvarchar(50) 'SBCaller'
)
EXEC sp_xml_removedocument @XMLPtr
IF ((@ExecuteSQL IS NOT NULL) AND (LEN(@ExecuteSQL)>0))
BEGIN
SET @LogMsg='ExecuteSQL:' + @ExecuteSQL
EXECUTE(@ExecuteSQL);
SET @LogMsg='ExecuteSQLSuccess:' + @ExecuteSQL
EXECute sp_LogSystemTransaction @SBCaller,@LogMsg,'SBMessage',0,''
END
IF ((@CallBackSP IS NOT NULL) AND (LEN(@CallBackSP)>0))
BEGIN
SET @CallBackSQL = @CallBackSP + ' @Sender=''sp_SB_HMSTargetActivProc'', @Res=''' + @ExecuteSQL + ''''
SET @LogMsg='CallBackSQL:' + @CallBackSQL
EXECute sp_LogSystemTransaction @SBCaller,@LogMsg,'SBMessage',0,''
EXECUTE(@CallBackSQL);
END
END
ELSE IF @RecvReqMsgName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
BEGIN
SET @LogMsg='MessageEnd:';
END CONVERSATION @RecvReqDlgHandle WITH CLEANUP;
END
ELSE IF @RecvReqMsgName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
DECLARE @message_body VARBINARY(MAX);
DECLARE @code int;
DECLARE @description NVARCHAR(3000);
DECLARE @xmlMessage XML;
SET @xmlMessage = CAST(@RecvReqMsg AS XML);
SET @code = (
SELECT @xmlMessage.value(
N'declare namespace
brokerns="http://schemas.microsoft.com/SQL/ServiceBroker/Error";
(/brokerns:Error/brokerns:Code)[1]',
'int')
);
SET @description = (
SELECT @xmlMessage.value(
'declare namespace
brokerns="http://schemas.microsoft.com/SQL/ServiceBroker/Error";
(/brokerns:Error/brokerns:Description)[1]',
'nvarchar(3000)')
);
IF (@code = -8462)
BEGIN
SET @LogMsg='MessageEnd:';
--EXECute sp_LogSystemTransaction @SBCaller,@LogMsg,'SBMessage',0,'';
END CONVERSATION @RecvReqDlgHandle WITH CLEANUP;
END
ELSE
BEGIN
SET @LogMsg='ERR:' + @description + ' ' + CAST(@code AS VARCHAR(20));
EXECute sp_LogSystemTransaction @SBCaller,@LogMsg,'SBError',0,'';
END CONVERSATION @RecvReqDlgHandle;
END
END
COMMIT TRANSACTION;
END
END
答案 0 :(得分:1)
我注意到的一件事是,这件事似乎做。大多数代码行似乎都在为服务代理对话框制作一条回复消息。
也就是说,服务代理的存在意味着您不必使用sp_xml_preparedocument来满足您的xml需求。看看XQuery。简而言之,这样的事情应该有效:
SELECT @ExecuteSQL = @RcvReqMsg.value('(RequestMsg/CommandParameters/ExecuteSQL)[1]', 'nvarchar(1000)')
,@CallBackSP = @RcvReqMsg.value('(RequestMsg/CommandParameters/CallBackSP)[1]', 'nvarchar(1000)')
,@SBCaller = @RcvReqMsg.value('(RequestMsg/CommandParameters/SBCaller)[1]', 'nvarchar(1000)')
其次,看起来消息包含要在包含此队列的数据库的上下文中执行的SQL。这些的表现形象是什么?那就是那些瓶颈吗?如果这些都很慢,那么添加服务代理就不会让事情变得更快
第三,您是否允许一次激活多个激活程序?检查sys.service_queues中的max_readers列以回答此问题。如果它设置为1并且您的流程不需要串行运行,请增加该数量以并行运行它们。
第四,看起来你已经编写了激活程序,在完成之前只处理一条消息。查看this tutorial中的示例。注意while (1=1)
循环。这使得激活过程在完成当前消息
最后,你为什么关心?服务代理是一种固有的异步技术。如果某人/某人正在等待处理给定的消息,我会质疑。
答案 1 :(得分:0)
首先,我建议将此威胁视为性能问题,并将其作为任何其他性能问题处理: measure 。有关如何测量会话或语句的等待,IO,CPU总体以及如何识别瓶颈的简要介绍和明确建议,请参阅How to analyse SQL Server performance。一旦你知道瓶颈在哪里,你就可以考虑解决它的方法。
现在针对SSB更具体的内容。我会说你的程序有三个对这个问题很有意思的组件:
RECEIVE
,END CONVERSATION
EXECUTE(@ExecuteSQL)
)对于队列处理,我建议Writing Service Broker Procedures了解如何加快速度。 RECEIVE TOP(1)
是最慢的方法。批量处理更快,甚至更快,如果可能。要使批处理出列,您需要队列中的相关消息,这意味着SEND
在单个会话句柄上显示许多消息,请参阅Reusing Conversation。这可能会显着地使应用程序复杂化。因此,我强烈建议您在做出如此剧烈的改变之前测量并确定瓶颈。
对于XML碎化我同意@BenThul,使用XML data type methods比使用MSXML程序更好。
最后有EXECUTE(@ExecuteSQL)
。这对我们来说是一个黑盒子,只有你知道实际执行的是什么。不仅SQL执行的成本有多昂贵/复杂,而且还有可能阻塞的可能性。此后台执行与前端代码之间的锁争用可能会大大减慢队列处理速度。再次,衡量,你会知道。作为旁注:从您发布的数字来看,我希望问题出在这里。根据我的经验,一个完全按照你的方式执行的激活程序(RECEIVE TOP(1)
,XML解析,SEND
响应),没有EXECUTE,应该以每秒约100条消息的速度进行排空你的队列2000个工作约20秒。你观察到一个慢得多的速度,这让我怀疑实际执行的SQL。
最后,轻松尝试:提升MAX_QUEUE_READERS(再次,正如@BenThul已经指出的那样):
ALTER QUEUE HMSTargetQueueIntAct WITH ACTIVATION (MAX_QUEUE_READERS = 5)
这将允许并行处理请求。
您在程序中缺少正确的错误处理,您应该有一个BEGIN TRY/BEGIN CATCH
块。请参阅Error Handling in Service Broker procedures,Error Handling and Activation,Handling exceptions that occur during the RECEIVE statement in activated procedures和Exception handling and nested transactions。