I am would like to display progress feedback for user while stored procedure is running (multiple setps). All sql code is in transaction so I am using a service broker to get a real time updates. Could you suggest what is wrong with code below as I am not getting any messages in my queue?
Is there a better way of doing it?
--queue
create queue myQueue
--service
create service myLogs
on queue myQueue ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
--log handler
create event notification myLogHandler
on server
for userconfigurable_0
to service 'myLogs', 'current database' ;
--message
EXEC master..sp_trace_generateevent #event_class = 82, #userinfo = N'test msg'
---transaction test
begin transaction
EXEC master..sp_trace_generateevent #event_class = 82, #userinfo = N'tran test msg'
rollback transaction
--receive message
declare #message_body xml;
receive top (1) #message_body = message_body
from myQueue
select #message_body
--display queue
select * from myQueue
An arguably simpler way would be to:
--------------------------------
--Worker process
--------------------------------
BEGIN TRANSACTION
--While in your loop or at each stage of the proc you can do this
INSERT INTO MyLoggingTable VALUES('My Message')
COMMIT
--------------------------------
--Reporting process
--------------------------------
SELECT * FROM MyLoggingTable WITH (NOLOCK)
The NOLOCK lets you read the progress of the proc even though its logging to it inside the transaction.
NOTE that this allows you to read the data "LIVE" but if the worker process dose a rollback then the data will be removed.
You can get around that by also logging to a #MyTable, this will survive the rollback and so you can rollback and then copy the content of #MyTable into MyLoggingTable.
That works fine for me though I needed to wait a few moments and run the receive from queue bit again before I saw anything.
Perhaps you need to ENABLE_BROKER on the database.
ALTER DATABASE YourDB SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE
(Warning: Will kill existing connections to the DB)
Related
Using SQL Server 2016, I wish to merge data from a SourceTable to a DestinationTable with a simple procedure containing a simple insert/update/delete on the same table.
The SourceTable is filled by several different applications, and they call the MergeOrders stored procedure to merge their uploaded rows from SourceTable into DestinationTable.
There can be several instances of MergeOrders stored procedure running in parallel.
I get a lot of lock, but that's normal, the issue is that sometimes I get "RowGroup deadlocks", which I cannot afford.
What is the best way to execute such merge operation in this parallel environment.
I am thinking about TABLOCK or SERIALIZABLE hints, or maybe application locks to serialize the access, but interested if there is better way.
An app lock will serialize sessions attempting to run this procedure. It should look like this:
create or alter procedure ProcWithAppLock
with execute as owner
as
begin
set xact_abort on;
set nocount on;
begin transaction;
declare #lockName nvarchar(255) = object_name(##procid) + '-applock';
exec sp_getapplock #lockName,'Exclusive','Transaction',null,'dbo';
--do stuff
waitfor delay '00:00:10';
select getdate() dt, object_name(##procid);
exec sp_releaseapplock #lockName, 'Transaction', 'dbo';
commit transaction;
end
There are a couple of subtle things in this template. First off it doesn't have a catch block, and relies on xact_abort to release the applock in case of an error. And you want to explicitly release the app lock in case this procedure is called in the context of a longer-running transaction. And finally the principal for the lock is set to dbo so that no non-dbo user can acquire a conflicting lock. This also requires that the procedure be run with execute as owner, as the application user would not normally be dbo.
I am reading an expert from a google preview book.
shorturl.at/htF38
I want to basically simulate a poision message in a Service Broker Queue.
The author presents this code fragment to simulate 5 rollbacks .
My question is how does this pieces of code simulate 5 rollbacks beats me.
Per me On line 31 if it does not see a message in the queue the rollback happens once
and the break statement breaks the loop no ?
DECLARE #ch UNIQUEIDENTIFIER
DECLARE #messagetypename NVARCHAR(256)
DECLARE #messagebody XML
WHILE (1=1)
BEGIN
BEGIN TRANSACTION
WAITFOR (
RECEIVE TOP (1)
#ch = conversation_handle,
#messagetypename = message_type_name,
#messagebody = CAST(message_body AS XML)
FROM TargetQueue
),
TIMEOUT 60000
IF (##ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION
BREAK **--Line No 31**
END
-- Rolling back the current transaction
PRINT 'Rollback the current transaction - simulating a poison message...'
ROLLBACK TRANSACTION
END
GO
Now, when you place a message into the TargetQueue and execute the service program
shown in Listing ,you'll see that Service Broker deactivates the queue automatically, and
you’ll get an error message
I am writing a stored procedure that would perform the following operations:
BEGIN a transaction
perform some tasks
use Service Broker to kick-off a background process
wait for a response message (with job status) from Service Broker
COMMIT or ROLLBACK transaction, depending on the response message
The issue is that Service Broker communication is not working inside a TRANSACTION:
the message queue has activation enabled, but the associated stored procedure is not executed (PRINT statements in the stored procedure are not written to ERRORLOG file)
RECEIVE command times out
Here's an extract of my code:
-- Comment out the following line to make everything work
begin tran t1
DECLARE #Update_Msg XML([sb].[Service_Broker_xxx_Schemas]) = '
<Request xmlns="xxx">
<Table xmlns="xxx">
<Fields>
xxx
</Fields>
</Table>
<Requested_By>xxx</Requested_By>
</Request>'
DECLARE #conversation_handle UNIQUEIDENTIFIER
,#message_body varbinary(max)
,#message_type_name nvarchar(256)
,#timestamp datetime2
BEGIN DIALOG CONVERSATION #conversation_handle
FROM SERVICE [xxx_Initiating_Service]
TO SERVICE 'xxx_Target_Service'
ON CONTRACT xxx_Contract
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #conversation_handle
MESSAGE TYPE [xxx_Command](#Update_Msg);
select * from sys.transmission_queue with(nolock)
--PRINT #conversation_handle
WAITFOR (
-- just handle one message at a time
RECEIVE TOP(1) #conversation_handle = conversation_handle -- the identifier of the dialog this message was received on
,#message_type_name = message_type_name
,#message_body=message_body -- the message contents
,#timestamp = GETDATE()
FROM [sb].[xxx_Initiator_Queue]
WHERE conversation_handle = #conversation_handle
), TIMEOUT 1000 -- if the queue is empty for one second, give UPDATE and go away
IF ##ROWCOUNT > 0
BEGIN
SELECT ##ROWCOUNT, #message_type_name, CONVERT(XML, #message_body)
END CONVERSATION #conversation_handle;
END
ELSE
BEGIN
PRINT 'Did not receive any response from Service Broker.'
END
-- Comment out the following line to make everything work
commit tran t1
What is the correct way to implement Service Broker messaging inside a transaction?
Sending messages via Service Broker is transactional. That is, if you do begin tran; send;, the message isn't actually sent until you commit.
I have an INSERT trigger on a table that simply executes a job.
Example:
CREATE TABLE test
(
RunDate smalldatetime
)
CREATE TRIGGER StartJob ON test
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
When I insert a record to this table, the job is fired of without any issue. There are a few people, however, that have lower permissions than I do (db_datareader/db_datawriter on the database only); they are able to insert a record to the table, but the trigger does not fire.
I am a SQL Server novice and I was under the impression that users did not need elevated permissions to fire off a trigger (I thought that was one of the big benefits!). Is this a permission issue at the trigger level, or at the job level? What can I do to get around this limitation?
The trigger will execute in the context of the caller, which may or may not have the permissions to access msdb. That seems to be your problem. There are a few ways to extend these permissions using Execute As; they are greatly detailed in this link
Use impersonation within trigger:
CREATE TRIGGER StartJob ON test
with execute as owner
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
And set database to trustworthy (or read about signing in above link):
alter database TestDB set trustworthy on
Another way to go (depending on what operations the agent job performs) would be to leverage a Service Broker queue to handle the stored procedure activation. Your users' context would simply call to Send On the queue while, in an asynchronous process SvcBroker would activate a stored procedure which executed in context of higher elevated user. I would opt for this solution rather than relying on a trigger calling an agent job.
I wanted to test the call to Service Broker, so I wrote this simple test example. Instead of calling an SSIS package I simply send an email, but it is very similar to your situation. Notice I use SET TRUSTWORTHY ON at the top of the script. Please read about the implications of this setting.
To run this sample you will need to substitute your email profile info below, <your_email_address_here>, etc.
use Master;
go
if exists(select * from sys.databases where name = 'TestDB')
drop database TestDB;
create database TestDB;
go
alter database TestDB set ENABLE_BROKER;
go
alter database TestDB set TRUSTWORTHY ON;
use TestDB;
go
------------------------------------------------------------------------------------
-- create procedure that will be called by svc broker
------------------------------------------------------------------------------------
create procedure dbo.usp_SSISCaller
as
set nocount on;
declare #dlgid uniqueidentifier;
begin try
-- * figure out how to start SSIS package from here
-- for now, just send an email to illustrate the async callback
;receive top(1)
#dlgid = conversation_handle
from SSISCallerQueue;
if ##rowcount = 0
begin
return;
end
end conversation #dlgid;
exec msdb.dbo.sp_send_dbmail
#profile_name = '<your_profile_here>',
#importance = 'NORMAL',
#sensitivity = 'NORMAL',
#recipients = '<your_email_address_here>',
#copy_recipients = '',
#blind_copy_recipients = '',
#subject = 'test from ssis caller',
#body = 'testing',
#body_format = 'TEXT';
return 0;
end try
begin catch
declare #msg varchar(max);
select #msg = error_message();
raiserror(#msg, 16, 1);
return -1;
end catch;
go
------------------------------------------------------------------------------------
-- setup svcbroker objects
------------------------------------------------------------------------------------
create contract [//SSISCallerContract]
([http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer] sent by initiator)
create queue SSISCallerQueue
with status = on,
activation (
procedure_name = usp_SSISCaller,
max_queue_readers = 1,
execute as 'dbo' );
create service [//SSISCallerService]
authorization dbo
on queue SSISCallerQueue ([//SSISCallerContract]);
go
return;
-- usage
/*
-- put a row into the queue to trigger the call to usp_SSISCaller
begin transaction;
declare #dlgId uniqueidentifier;
begin dialog conversation #dlgId
from service [//SSISCallerService]
to service '//SSISCallerService',
'CURRENT DATABASE'
on contract [//SSISCallerContract]
with encryption = off;
begin conversation timer (#dlgId)
TIMEOUT = 5; -- seconds
commit transaction;
*/
It would be permissions at the job level. You can possibly assign those users the SQLAgentReaderRole in MSDB to be able to start a job, considering that they would be added to a group that owned the job. If they are not in a group which owns the job, it gets more difficult.
I wish to have a stored proc that is called every n seconds, is there a way to do this in SQL Server without depending on a separate process?
Use a timer and activation. No external process, continues to work after a clustering or mirroring failover, continues to work even after a restore on a different machine, and it works on Express too.
-- create a table to store the results of some dummy procedure
create table Activity (
InvokeTime datetime not null default getdate()
, data float not null);
go
-- create a dummy procedure
create procedure createSomeActivity
as
begin
insert into Activity (data) values (rand());
end
go
-- set up the queue for activation
create queue Timers;
create service Timers on queue Timers ([DEFAULT]);
go
-- the activated procedure
create procedure ActivatedTimers
as
begin
declare #mt sysname, #h uniqueidentifier;
begin transaction;
receive top (1)
#mt = message_type_name
, #h = conversation_handle
from Timers;
if ##rowcount = 0
begin
commit transaction;
return;
end
if #mt in (N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
, N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if #mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer'
begin
exec createSomeActivity;
-- set a new timer after 2s
begin conversation timer (#h) timeout = 2;
end
commit
end
go
-- attach the activated procedure to the queue
alter queue Timers with activation (
status = on
, max_queue_readers = 1
, execute as owner
, procedure_name = ActivatedTimers);
go
-- seed a conversation to start activating every 2s
declare #h uniqueidentifier;
begin dialog conversation #h
from service [Timers]
to service N'Timers', N'current database'
with encryption = off;
begin conversation timer (#h) timeout = 1;
-- wait 15 seconds
waitfor delay '00:00:15';
-- end the conversation, will stop activating
end conversation #h;
go
-- check that the procedure executed
select * from Activity;
You can set up a SQL Agent job - that's probably the only way to go.
SQL Server Agent is a component of SQL Server - not available in the Express editions, however - which allows you to automate certain tasks, like database maintenance etc. but you can also use it to call stored procs every n seconds.
I once set up a stored procedure that ran continuously, uisng a loop with a WAITFOR at the end of it.
The WHILE condition depended upon the value read from a simple configuration table. If the value got set to 0, the loop would be exited and the procedure finished.
I put a WAITFOR DELAY at the end, so that however long it took to process a given iteration, it would wait XX seconds until it ran it again. (XX was also set in and read from the configuration table.)
If it must run at precise intervales (say, 0, 15, 30, and 45 seconds in the minute), you could calculate the appropriate WATIFOR TIME value at the end of the loop.
Lastly, I had the procedure called by a SQL Agent job once a minute. The job would always be "running" showing that the procedure was running. If the procedure was killed or crashed, the job would start it up in no more than 1 minute. If the procedure was "turned off", the procedure still gets run but the WHILE loop containing the processing does not get entered making the overhead nill.
I didn't much like having it in my database, but it fulfilled the business requirements.
WAITFOR
{
DELAY 'time_to_pass'
| TIME 'time_to_execute'
| [ ( receive_statement ) | ( get_conversation_group_statement ) ]
[ , TIMEOUT timeout ]
}
If you want to keep a SSMS query window open:
While 1=1
Begin
exec "Procedure name here" ;
waitfor delay '00:00:15';
End