Just want to get some views/possible leads on an issue I have.
I have a stored procedure that updates/deletes a record from a table in my database, the table it deletes from is a live table, that temporary holds the data, and also updates records on a archive table. (for reporting etc..) it works normally and havent had an issues.
However recently I had worked on a windows service to monitor our system (running 24/7), which uses a HTTP call to initiate a program, and once this program has finished it then runs the mention stored procedure to delete out redundant data. Basically the service just runs the program quickly to make sure its functioning correctly.
I have noticed recently that the data isnt always being deleted. Looking through logs I see no errors being reported. And Even see the record in the database has been updated correctly. But just doesnt get deleted.
This unfortunately has a knock on effect with the monitoring service, as this continously runs, and sends out alerts because the data cant be duplicated in the live table, hence why it needs to delete out the data.
Currently I have in place a procedure to clear out any old data. (3 hours).
Result has the value - Rejected.
Below is the stored procedure:
DECLARE #PostponeUntil DATETIME;
DECLARE #Attempts INT;
DECLARE #InitialTarget VARCHAR(8);
DECLARE #MaxAttempts INT;
DECLARE #APIDate DATETIME;
--UPDATE tCallbacks SET Result = #Result WHERE CallbackID = #CallbackID AND UPPER(Result) = 'PENDING';
UPDATE tCallbacks SET Result = #Result WHERE ID = (SELECT TOP 1 ID FROM tCallbacks WHERE CallbackID = #CallbackID ORDER BY ID DESC)
SELECT #InitialTarget = C.InitialTarget, #Attempts = LCB.Attempts, #MaxAttempts = C.CallAttempts
FROM tConfigurations C WITH (NOLOCK)
LEFT JOIN tLiveCallbacks LCB ON LCB.ID = #CallbackID
WHERE C.ID = LCB.ConfigurationID;
IF ((UPPER(#Result) <> 'SUCCESSFUL') AND (UPPER(#Result) <> 'MAXATTEMPTS') AND (UPPER(#Result) <> 'DESTBAR') AND (UPPER(#Result) <> 'REJECTED')) BEGIN
--INSERT A NEW RECORD FOR RTNR/BUSY/UNSUCCESSFUL/REJECT
--Create Callback Archive Record
SELECT #APIDate = CallbackRequestDate FROM tCallbacks WHERE Attempts = 0 AND CallbackID = #CallbackID;
BEGIN TRANSACTION
INSERT INTO tCallbacks (CallbackID, ConfigurationID, InitialTarget, Agent, AgentPresentedCLI, Callee, CalleePresentedCLI, CallbackRequestDate, Attempts, Result, CBRType, ExternalID, ASR, SessionID)
SELECT ID, ConfigurationID, #InitialTarget, Agent, AgentPresentedCLI, Callee, CalleePresentedCLI, #APIDate, #Attempts + 1, 'PENDING', CBRType, ExternalID, ASR, SessionID
FROM tLiveCallbacks
WHERE ID = #CallbackID;
UPDATE LCB
SET PostponeUntil = DATEADD(second, C.CallRetryPeriod, GETDATE()),
Pending = 0,
Attempts = #Attempts + 1
FROM tLiveCallbacks LCB
LEFT JOIN tConfigurations C ON C.ID = LCB.ConfigurationID
WHERE LCB.ID = #CallbackID;
COMMIT TRANSACTION
END
ELSE BEGIN
-- Update the Callbacks archive, when Successful or Max Attempts or DestBar.
IF EXISTS (SELECT ID FROM tLiveCallbacks WHERE ID = #CallbackID) BEGIN
BEGIN TRANSACTION
UPDATE tCallbacks
SET Attempts = #Attempts
WHERE ID IN (SELECT TOP (1) ID
FROM tCallbacks
WHERE CallbackID = #CallbackID
ORDER BY Attempts DESC);
-- The live callback should no longer be active now. As its either been answered or reach the max attempts.
DELETE FROM tLiveCallbacks WHERE ID = #CallbackID;
COMMIT
END
END
You need to fix your transaction processing. What is happening is that one statement is failing but since you don't have a try-catch block all changes are not getting rolled back only the statement that failed.
You should never have a begin tran without a try catch block and a rollback on error. I personally also prefer in something like this to put the errors and associated data into a table variable (which will not rollback) and then insert then to an exception table after the rollback. This way the data retains integrity and you can look up what the problem was.
Related
I am trying to write an update trigger on a table which would cause it to run an additional update statement only if a certain column has been changed, so far the trigger runs the update no matter what, hoping maybe someone can see what I am doing wrong here.
Here is the trigger.
ALTER TRIGGER [dbo].[StatusChangedUpdateTrigger]
ON [dbo].[Trans_Order]
AFTER UPDATE
AS
DECLARE #OldOrderStatusId INT, #NewStatusOrderId INT, #ERRNUM INT;
BEGIN
SET #OldOrderStatusId = (SELECT OrderStatusId FROM deleted);
SET #NewStatusOrderId = (SELECT OrderStatusId FROM inserted);
IF (#OldOrderStatusId != #NewStatusOrderId)
SET NOCOUNT ON;
UPDATE Trans_Order
SET StatusChanged = 1
WHERE Id = (SELECT ID FROM inserted)
END
For some reason this is running no matter what, I can never set StatusChanged to 0 as it will automatically flip it back to 1 even if the OrderStatusId hasn't changed. So my update statement is running no matter what, so I am guessing I am doing something wrong in the if statement.
Hmmmm . . . Your logic seems strange. I would expect:
UPDATE t
SET StatusChanged = 1
FROM Trans_Order t JOIN
Inserted i
ON t.id = i.id JOIN
Deleted d
ON t.id = d.id
WHERE i.OrderStatusId <> d.OrderStatusId;
You might need to take NULL values into account -- although your code does not.
Note that your code is just a bug waiting to happen, because it assumes that inserted and deleted have only one row.
The specific problem with your code is that it is really:
IF (#OldOrderStatusId != #NewStatusOrderId)
BEGIN
SET NOCOUNT ON;
END;
UPDATE Trans_Order
SET StatusChanged = 1
WHERE Id = (SELECT ID FROM inserted);
Your indentation has confused the logic. However, you should still use the set-based version so the trigger does not fail.
The correct way to approach your trigger is as follows:
create or alter trigger [dbo].[StatusChangedUpdateTrigger] on [dbo].[Trans_Order]
after update
as
set nocount on
if ##RowCount=0 return
if Update(OrderStatusId)
begin
update t
set statusChanged=1
from inserted i join deleted d on d.id=i.id and d.OrderStatusId != i.OrderStatusId
join Trans_Order t on t.id=i.id
end
Always test ##rowcount and return if no rows updated.
Always put set options before DML
As you are only looking to update if a specific column is updated you can test specifically for that and if the update statement that's run doesn't touch that column the trigger will not run.
This will correctly account for multiple rows being updated and only update those where the new value is different to the old value.
My stored procedure is like this
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Bid_Create]
#BidType int,
#ClientId int,
#BidDate date,
#EmailNotificationStatus int,
#BidStatus int,
#BidAmount int,
#ProductId int
AS
DECLARE #highestBid int;
BEGIN
BEGIN TRY
BEGIN TRANSACTION
SET NOCOUNT ON;
SET #highestBid = (SELECT Max(wf_bid.BidAmount) AS HighestBitAmount
FROM wf_bid
WHERE wf_bid.ProductId = #ProductId)
IF #highestBid is NULL OR #highestBid < #BidAmount
BEGIN
UPDATE wf_bid
SET BidStatus = '1'
WHERE Id = (SELECT TOP 1 id
FROM [wf_bid]
WHERE BidAmount = (SELECT MAX(BidAmount)
FROM [wf_bid]
WHERE ProductId = #ProductId
AND ClientId = #ClientId))
INSERT INTO wf_bid (BidType, ClientId, BidDate, EmailNotificationStatus, BidStatus)
VALUES (#BidType, #ClientId, #BidDate, #EmailNotificationStatus, #BidStatus)
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
Everything looks okay to me. But once I run this, table is getting locked. No other query on the table works (I think it is because transaction is not getting committed).
Can anyone point out what is wrong with this query? And how can I unlock the table?
But once I run this, table is getting locked
This may be due to update taking many locks,which in turn may be due to predicate not being sargable. Though this update locks(U) lock will be released as soon as the predicate is not matched.You will experience blocking
One more reason ,why this update may block your whole table is when this transaction acquires more than 5000 locks..
another reason can be when your transaction fails after committing so many rows and it has to do a lot of rollback work
Above are the reasons ,i could think of,where you can experience table is locked feeling
to troubleshoot that,you will need to check lockings blokcings using below query
select resource_type,resource_Database_id,
request_mode,request_type,request_Status,request_session_id
from sys.dm_tran_locks
where request_session_id=<<your update session id>>
also you are accessing table many times,for getting max.you can rewrite it like below
;with cte
as
(
select top (1) with ties id,bidstatus from
wf_bid
where ProductId=#ProductId and ClientId=#ClientId)
order by
row_number() over (partition by id order by bid_Amount desc)
)
update cte
set bidstatus=1
I'm not from SQL background so I'll need a very clear answer. On daily basis I'm building interfaces in .net and sometimes pulling data from SQL using simple statements. I never had anything to do with the store procedures before. Both our developer(away on monthly vacation) and our DBA(who is sick for unforeseeable future) are away and being the only person left who at least saw SQL Server I ended up replacing both of them. I feel very much out of my depth and there is no one in my office to ask. My boss who used to be a SQL developer before he became boss and stopped doing technical things insisted today that there is a problem with this stored procedure and that I am to fix it. I truly have no idea how. C# is so much easier...
This (apparently faulty) stored procedure was designed to populate a new field - Sequence. It was meant to take a ClientID, and then update the User records for that ClientID. If the number of updated records varies from the expected number of updates, the transaction is supposed to rollback.
My boss claims that this procedure is written wrong. Could you please help and correct it for me so that I can use it with my limited SQL knowledge?
DECLARE #TargetRowCount INT
SELECT #TargetRowCount = COUNT(*)
FROM dbo.Users
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name)
BEGIN TRAN
UPDATE dbo.Users
SET Sequence = UPPER(Name)
SELECT COUNT(*)
FROM dbo.Users
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name)
IF(##RowCount <> #TargetRowCount)
ROLLBACK TRAN
ELSE
COMMIT TRAN
Thank you in advance!
i have a hard time to understad the purpose of that script but anyway.
see the comments i included. hopefully it'll help you:
DECLARE #TargetRowCount INT;
DECLARE #ChckRC1 INT;
DECLARE #ChckRC2 INT;
-- select how many rows with wrong name exist
SELECT #TargetRowCount = COUNT(*)
FROM dbo.Users
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name)
BEGIN TRAN
-- update users sequence, but really all?
UPDATE dbo.Users
SET Sequence = UPPER(Name)
-- i'd add the where clause from above
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name);
-- store how many rows the update hit
SET #ChckRC1 = ##ROWCOUNT;
-- select again how many rows with wrong name exist
SELECT #ChckRC2 = COUNT(*)
FROM dbo.Users
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name);
-- first count is different than actually updated rows,
-- some disappeared or new appeared.
IF (#ChckRC1 <> #TargetRowCount )
-- second count should be zero
-- any other value suggest, there is still a user with wrong sequence
OR (#ChckRC2 <> 0 )
ROLLBACK TRAN
ELSE
COMMIT TRAN
i have no idea why you are checking the counts 3 times. what i'd do is:
-- update users sequence of some client with wrong sequence
UPDATE dbo.Users
SET Sequence = UPPER(Name)
WHERE ClientId = #ClientId
AND Sequence <> UPPER(Name);
return ##ROWCOUNT;
In my SQL table (2008), I have a timestamp column called Timestamp.
In my .Net project, I have a POCO class with a property
public byte[] Timestamp {get; set;}
In my configuration code, I have the following:
Property(p => p.Timestamp).IsRowVersion();
Now, if I open two edit screens make a change in one and save it (save is accepted), then make a change in the second and save it (save is rejected with DbUpdateConcurrencyException).
One thing that I find odd is that SQL Profiler shows an update request being sent to the SQL database even when I receive the concurrency exception. The update is not committed on the database but it is sent. Is that normal? I kind of expected EF to check that ahead of time and not even send the request.
Lastly, if I enable the trigger on this table:
ALTER TRIGGER [dbo].[trg_person_log_changes]
ON [dbo].[Person]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #auditBody XML
DECLARE #actionType char(1)
DECLARE #username nvarchar(100)
if not exists(select * from deleted)
Begin
SET #actionType = 'I'
SET #username = (select Case
When lastchangedby is null or lastchangedby = '' then Suser_name()
Else lastchangedby
End
from inserted)
End
else if not exists(select * from inserted)
Begin
SET #actionType = 'D'
SET #username = Suser_name()
End
else
Begin
SET #actionType = 'U'
SET #username = (select distinct Case
When lastchangedby is null or lastchangedby = '' then Suser_name()
Else lastchangedby
End
from inserted)
End
If #actionType = 'I'
Set #auditBody = (select 'Person' as "#tableName", 'True' as "#synch",
(select * from inserted for xml path('DataItem'), type, binary base64)
for xml path('Root'))
Else
SET #auditBody = (select 'Person' as "#tableName", 'True' as "#synch",
(select * from deleted for xml path('DataItem'), type, binary base64)
for xml path('Root'))
insert into [dbo].[AuditLog]
([Action]
,[ActionDate]
,[ActionUser]
,[AuditData]
)
values (
#actionType
,getdate()
,#username
,#auditBody)
END
Now when I try to save the second edit, I no longer get a DbUpdateConcurrencyException, I get an error stating that ActionUser can't be null. Once again, I think my trigger is being executed because EF is allowing the update to go through even though there is a conflict.
Any ideas on what I may be doing wrong?
NOTE
This is an MVC project. In my Edit POST controller, I'm receiving a DTO object that contains all the form properties (one of which is Timestamp -- a hidden field on my edit form). I'm loading the edited person from the datacontext and mapping the edited properties from the DTO object into the Domain object returned from the datacontext.
The only way to do optimistic concurrency right is doing the version check and being 100% sure that nothing changes until the actual update is done. The most convenient way to do this is incorporate the version check in the UPDATE statement, so that's what EF does. So it does send an update statement, which looks like
UPDATE table SET columnA = value
WHERE rowversion = xxxx
However, when the statement does not find the row with the rowversion it had in memory it returns a different number of affected rows than expected and the exception is throw. And the transaction is rolled back. So yes, you see a statement in SQL Profiler, but it is never committed.
The trigger runs as part of the update statement, i.e. before EF is reported back about the update command. Apparently, #username doesn't get a value in the process, so this throws a SQL exception that spoils the update party before any concurrency conflict is noticed.
I've got a table where I need to auto-assign an ID 99% of the time (the other 1% rules out using an identity column it seems). So I've got a stored procedure to get next ID along the following lines:
select #nextid = lastid+1 from last_auto_id
check next available id in the table...
update last_auto_id set lastid = #nextid
Where the check has to check if users have manually used the IDs and find the next unused ID.
It works fine when I call it serially, returning 1, 2, 3 ... What I need to do is provide some locking where multiple processes call this at the same time. Ideally, I just need it to exclusively lock the last_auto_id table around this code so that a second call must wait for the first to update the table before it can run it's select.
In Postgres, I can do something like 'LOCK TABLE last_auto_id;' to explicitly lock the table. Any ideas how to accomplish it in SQL Server?
Thanks in advance!
Following update increments your lastid by one and assigns this value to your local variable in a single transaction.
Edit
thanks to Dave and Mitch for pointing out isolation level problems with the original solution.
UPDATE last_auto_id WITH (READCOMMITTEDLOCK)
SET #nextid = lastid = lastid + 1
You guys have between you answered my question. I'm putting in my own reply to collate the working solution I've got into one post. The key seems to have been the transaction approach, with locking hints on the last_auto_id table. Setting the transaction isolation to serializable seemed to create deadlock problems.
Here's what I've got (edited to show the full code so hopefully I can get some further answers...):
DECLARE #Pointer AS INT
BEGIN TRANSACTION
-- Check what the next ID to use should be
SELECT #NextId = LastId + 1 FROM Last_Auto_Id WITH (TABLOCKX) WHERE Name = 'CustomerNo'
-- Now check if this next ID already exists in the database
IF EXISTS (SELECT CustomerNo FROM Customer
WHERE ISNUMERIC(CustomerNo) = 1 AND CustomerNo = #NextId)
BEGIN
-- The next ID already exists - we need to find the next lowest free ID
CREATE TABLE #idtbl ( IdNo int )
-- Into temp table, grab all numeric IDs higher than the current next ID
INSERT INTO #idtbl
SELECT CAST(CustomerNo AS INT) FROM Customer
WHERE ISNUMERIC(CustomerNo) = 1 AND CustomerNo >= #NextId
ORDER BY CAST(CustomerNo AS INT)
-- Join the table with itself, based on the right hand side of the join
-- being equal to the ID on the left hand side + 1. We're looking for
-- the lowest record where the right hand side is NULL (i.e. the ID is
-- unused)
SELECT #Pointer = MIN( t1.IdNo ) + 1 FROM #idtbl t1
LEFT OUTER JOIN #idtbl t2 ON t1.IdNo + 1 = t2.IdNo
WHERE t2.IdNo IS NULL
END
UPDATE Last_Auto_Id SET LastId = #NextId WHERE Name = 'CustomerNo'
COMMIT TRANSACTION
SELECT #NextId
This takes out an exclusive table lock at the start of the transaction, which then successfully queues up any further requests until after this request has updated the table and committed it's transaction.
I've written a bit of C code to hammer it with concurrent requests from half a dozen sessions and it's working perfectly.
However, I do have one worry which is the term locking 'hints' - does anyone know if SQLServer treats this as a definite instruction or just a hint (i.e. maybe it won't always obey it??)
How is this solution? No TABLE LOCK is required and works perfectly!!!
DECLARE #NextId INT
UPDATE Last_Auto_Id
SET #NextId = LastId = LastId + 1
WHERE Name = 'CustomerNo'
SELECT #NextId
Update statement always uses a lock to protect its update.
You might wanna consider deadlocks. This usually happens when multiple users use the stored procedure simultaneously. In order to avoid deadlock and make sure every query from the user will succeed you will need to do some handling during update failures and to do this you will need a try catch. This works on Sql Server 2005,2008 only.
DECLARE #Tries tinyint
SET #Tries = 1
WHILE #Tries <= 3
BEGIN
BEGIN TRANSACTION
BEGIN TRY
-- this line updates the last_auto_id
update last_auto_id set lastid = lastid+1
COMMIT
BREAK
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() as ErrorMessage
ROLLBACK
SET #Tries = #Tries + 1
CONTINUE
END CATCH
END
I prefer doing this using an identity field in a second table. If you make lastid identity then all you have to do is insert a row in that table and select #scope_identity to get your new value and you still have the concurrency safety of identity even though the id field in your main table is not identity.