Deadlock with ShareLock problem on transaction on update - sql

I am seeing this error in my RDS logs in Postgresql.
Id column is Primary Key in my table.
I also logged the queries in server site and seen that both statements have different IDs.
Do you have any idea how can I fix this ?
Process 3095 waits for ShareLock on transaction 16391215; blocked by process 17623.
Process 17623:
UPDATE dummytable
SET latestheartbeat = $1
WHERE
id = $2
AND (
latestheartbeat < $1
OR latestheartbeat is NULL
)
Process 3095:
UPDATE dummytable
SET latestheartbeat = $1
WHERE
id = $2
AND (
latestheartbeat < $1
OR latestheartbeat is NULL
)

Related

Modify two tables (insert or update) based on existance of a row in the first table

I have a simple thing to do but somehow can't figure out how to do it.
I have to modify two tables (insert or update) based on existance of a row in the first table.
There is a possibility that some other process will insert the row with id = 1
between getting the flag value and "if" statement that examines its value.
The catch is - I have to change TWO tables based on the flag value.
Question: How can I ensure the atomicity of this operation?
I could lock both tables by "select with TABLOCKX", modify them and release the lock by committing the transaction but ... won't it be overkill?
declare #flag int = 0
begin tran
select #flag = id from table1 where id = 1
if #flag = 0
begin
insert table1(id, ...) values(1, ...)
insert table2(id, ...) values(1, ...)
end
else
begin
update table1 set colX = ... where id = 1
update table2 set colX = ... where id = 1
end
commit tran
To sumarize our conversation and generalize to other's case :
If your column [id] is either PRIMARY KEY or UNIQUE you can put a Lock on that row. No other process will be able to change the value of [id]
If not, in my opinion you won't have other choice than Lock the table with a TABLOCKX. It will prevent any other process to UPDATE,DELETE or INSERT a row.
With that lock, it could possibly allow an other process to SELECT over the table depending on your isolation level.
If your database is in read_committed_snapshot, the other process would read the "old" value of the same [id].
To check your isolation level you can run
SELECT name, is_read_committed_snapshot_on FROM sys.databases

Atomic optimistic locking on a PostgreSQL table doesn't fail if clause not satifisfied

Let's say I have the table definition below:
CREATE TABLE dummy_table (
id SERIAL PRIMARY KEY ,
version INT NOT NULL,
data TEXT NOT NULL);
INSERT INTO dummy_table(version, data)
VALUES (1, 'Stuff');
UPDATE dummy_table
SET version = 2
WHERE id = 1
AND version = 1;
Which basically gives a table in the state below:
id version data
1 2 'Stuff'
Now if several optimistic locking update statements are received by the database engine, how to make sure that they fail if the version is not the current one, for instance:
UPDATE dummy_table
SET version = 1
WHERE id = 1
AND version = 1;
If the conditions in the clause cannot be satisfied, the update will not happen.
The problem is that, there is actually no error given as feedback when executing that statement.
I tried the solutions available here but I'm not sure the solutions given are actually atomic:
UPDATE dummy_table
SET version = 1
WHERE id = 1
AND version = 1
RETURNING id;
Does not return anything and does not throw any exception if the clause is not satisfied.
DO $$
BEGIN
UPDATE dummy_table
SET version = 1
WHERE id = 1;
IF NOT FOUND THEN RAISE EXCEPTION 'Record not found.';
END IF;
END $$;
Works but not sure it's actually atomic.
Is there any solution that would make an actual (atomic) optimistic locking update fails if the condition in the UPDATE statement cannot be satisfied?
Both solutions are fine and not subject to a race condition.
If the UPDATE changes no rows, RETURNING will return an empty result set, and FOUND will be set to FALSE.

SQL stored procedure waiting and executing sequentially

I have a stored procedure that reads the ID of a row with status x, then immediately sets that rows id to status y.
Since this stored procedure is being called by multiple client apps, somehow the same values are being returned whereas really it 2 executions should not find any in status x.
I'm not using anything other than wrapping the actions in a begin transaction / commit.
Rough example:
Begin Transaction
IF (#Param = '2') -- all
BEGIN
#resultID = (SELECT ... WHERE STATUS_ID = X
END
ELSE
BEGIN
#resultID = (SELECT ... WHERE STATUS_ID = X
END
IF (#ResultID > 0)
BEGIN
UPDATE JOB_QUEUE SET STATUS_ID = Y WHERE ID = #ResultID
END
COMMIT
SELECT * from JOB_QUEUE WHERE ID = #ResultID
Somehow the query has returned the same #resultID from the table .. so I would presume I need some locking or something to prevent this.
Is there a method to ensure that executions of the stored procedure at the same time result in one executing and then the other (sequentially)?
Thanks.
The simple answer is to speed up the whole process - if its a slow running query, then the select can run before the update is finished.
If you need to select the values for some other report, you could effectively run the update as the first statement, and use the OUTPUT keyword to return the ID's of the updated records eg:
UPDATE JOB_QUEUE
SET STATUS_ID = Y WHERE STATUS_ID = X
OUTPUT inserted.ID

If an update....Where affects no rows, are any locks created?

If I run a SQL statement like
UPDATE table
SET col = value
WHERE X=Y
And no rows match, therefore no rows are changed, are any locks created by the update?
The DBMS is Sybase + SQL Server
You can play with this script and see for yourself that sometimes locks are acquired and held even when no rows are updated:
CREATE TABLE dbo.Test
(
i INT NOT NULL
PRIMARY KEY ,
j INT NULL
) ;
go
INSERT dbo.Test
( i, j )
VALUES ( 1, 2 ) ;
GO
SELECT ##spid ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
BEGIN TRANSACTION ;
UPDATE dbo.Test
SET j = 3
WHERE i = 3 ;
SELECT *
FROM sys.dm_tran_locks
WHERE request_session_id = ##spid;
COMMIT ;
If field x is indexed, then there will probably be a shared lock on that index while your UPDATE is checking it for matching records.
There should not be any row locks, but all locking behavior is contingent on your server-level isolation settings.
In case an update statement is used which does not effect the records then an exclusive intent lock is being taken for the update statement while in transaction as first the rows effected are to be selected followed by the update on the table, however as there are no rows that need to be updated this intent lock is taken on the table for the transaction in an exclusive mode.

SQL Server 2005 Deadlock Problem

I’m running into a deadlock problem when trying to lock some records so that no process (Windows service) picks the items to service them, then update the status and then return a recordset.
Can you please let me know why am I getting the deadlock issue when this proc is invoked?
CREATE PROCEDURE [dbo].[sp_LoadEventsTemp]
(
#RequestKey varchar(20),
#RequestType varchar(20),
#Status varchar(20),
#ScheduledDate smalldatetime = null
)
AS
BEGIN
declare #LoadEvents table
(
id int
)
BEGIN TRANSACTION
if (#scheduledDate is null)
Begin
insert into #LoadEvents (id)
(
Select eventqueueid FROM eventqueue
WITH (HOLDLOCK, ROWLOCK)
WHERE requestkey = #RequestKey
and requesttype = #RequestType
and [status] = #Status
)
END
else
BEGIN
insert into #LoadEvents (id)
(
Select eventqueueid FROM eventqueue
WITH (HOLDLOCK, ROWLOCK)
WHERE requestkey = #RequestKey
and requesttype = #RequestType
and [status] = #Status
and (convert(smalldatetime,scheduleddate) <= #ScheduledDate)
)
END
update eventqueue set [status] = 'InProgress'
where eventqueueid in (select id from #LoadEvents)
IF ##Error 0
BEGIN
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
select * from eventqueue
where eventqueueid in (select id from #LoadEvents)
END
END
Thanks in advance.
Do you have a non-clustered index defined as:
CREATE NONCLUSTERED INDEX NC_eventqueue_requestkey_requesttype_status
ON eventqueue(requestkey, requesttype, status)
INCLUDE eventqueueid
and another on eventqueueid?
BTW the conversion of column scheduleddate to smalldatetime type will prevent any use of an index on that column.
First of all, as you're running SQL Server I'd recommend you to intall Performance Dashboard which is a very handy tool to identify what locks are currently being made on the server.
Performance Dahsboard Link
Second, take a trace of your SQL Server using SQL Profiler (Already Installed) and make sure that you select on the Events Selection the item Locks > Deadlock graph which will show what is causing the deadlock.
You got to have very clear on your mind what a deadlock is to start troubleshooting it.
When any access is made to any table or row on the DB a lock is made.
Lets call SPID 51 and SPID 52 (SPID = SQL Process ID)
SPID 51 locks Cell A
SPID 52 locks Cell B
if on the same transaction SPID 51 requests for the Cell B, it'll wait SPID 52 till it releases it.
if on the same transaction SPID 52 requests for the Cell A, you got a deadlock because this situation will never finish (51 waiting for 52 and 52 for 51)
Got to tell you that it ain't easy to troubleshoot, but you you to dig deeper to find the resolution
Deadlocks happen most often (in my experience) when differnt resources are locked within differnt transactions in different orders.
Imagine 2 processes using resource A and B, but locking them in different orders.
- Process 1 locks resource A, then resource B
- Process 2 locks resource B, then resource A
The following then becomes possible:
- Process 1 locks resource A
- Process 2 locks resource B
- Process 1 tries to lock resource B, then stops and waits as Process 2 has it
- Process 2 tries to lock resource A, then stops and waits as Process 1 has it
- Both proceses are waiting for each other, Deadlock
In your case we would need to see exactly where the SP falls over due to a deadlock (the update I'd guess?) and any other processes that reference that table. It could be an trigger or something, which then gets deadlocked on a different table, not the table you're updating.
What I would do is use SQL Server 2005's OUTPUT syntaxt to avoid having to use the transaction...
UPDATE
eventqueue
SET
status = 'InProgress'
WHERE
requestkey = #RequestKey
AND requesttype = #RequestType
AND status = #Status
AND (convert(smalldatetime,scheduleddate) <= #ScheduledDate OR #ScheduledDate IS NULL)
OUTPUT
inserted.*