Subject RE: [firebird-support] Deadlock
Author Svein Erling Tysvær
>The following procedure causes a deadlock when the user on the web page navigates rapidly through a javascript object on the web page.  Please
>give me some advice on how best to deal with this.  I've thought about changing it using DATEDIFF so they update only once a minute, but I
>would appreciate advice on if there is a better way to fix this
>
>CREATE PROCEDURE uspValidateSession 
> ( 
>    guid varchar(40)
>    , newsessiontimeout int
>  ) 
>RETURNS 
> ( 
>    subscriberid int
>    , testerid int
>    , subscriberloginid int
>  )
>AS 
>DECLARE VARIABLE sessionlogid int;
>DECLARE VARIABLE lastaccessed timestamp;
>DECLARE VARIABLE currenttimeout int;
>BEGIN
>    SELECT b.SessionLogId, b.LastAccessed, b.CurrentSessionTimeout, COALESCE(a.SUBSCRIBERID, 0), COALESCE(a.TESTERID, 0), >COALESCE(a.SUBSCRIBERLOGINID, 0)
>    FROM tblLoginEvent a
>    JOIN tblSessionLog b ON a.LOGINEVENTID = b.LOGINEVENTID
>    WHERE a.EVENTGUID = :guid
>    INTO :sessionlogid, :lastaccessed, :currenttimeout, :subscriberid, :testerid, :subscriberloginid;
>    
>    IF (DATEADD(:currenttimeout MINUTE TO :lastaccessed) >= current_timestamp) THEN 
>    BEGIN 
>        UPDATE tblSessionLog SET 
>            LastAccessed = current_timestamp
>            , CurrentSessionTimeOut = :newsessiontimeout
>        WHERE SessionLogId = :sessio nlogid;
>    END
<    ELSE 
>    BEGIN 
>        subscriberid = 0;
>        testerid = 0;
>        subscriberloginid = 0;
>    END 
>    
>  SUSPEND;
>END^

In your particular case (reading your reply to Thomas), I would expect that taking explicit control of your transactions would be the best solution. I don't think updates within the same transaction would cause lock conflicts (I think you have a lock conflict, not a deadlock), just updates between separate transactions.

Below is an answer I started before reading your reply to Thomas sufficiently carefully, that can also be a possible solution, but it is far worse than controlling your transactions.

An alternative would be to INSERT into a table rather than UPDATE and then occasionally delete records that doesn't contain the highest value (UPDATE can be bad for concurrency). E.g. you could have

INSERT INTO tblSessionLogTimer(SessionLogID, LatAccessed, CurrentSessionTimeOut)
VALUES(:sessionlogid, current_timestamp, :newsessiontimeout);

and then have a separate procedure that you ran e.g. every night that did:

DELETE FROM tblSessionLogTimer TOld
WHERE EXISTS(SELECT * FROM tblSessionLogTimer TNew
WHERE (TOld.LastAccessed < TNew.LastAccessed
OR (TOld.LastAccessed = TNew.LastAccessed
AND TOld.PrimaryKey < TNew.PrimaryKey))
AND TOld.SessionLogId = TNew.SessionLogId);

Theoretically, you could then update tblSessionLog, but it wouldn't make too much sense since you anyway have to use

SELECT LastAccessed, SessionLogId
FROM tblSessionLogTimer TMax
WHERE NOT EXISTS(SELECT * FROM tblSessionLogTimer TNew
WHERE TMax.SessionLogId = TNew.SessionLogId
AND (TMax.LastAccessed < TNew.LastAccessed
OR (TMax.LastAccessed = TNew.LastAccessed
AND TMax.PrimaryKey < TNew.PrimaryKey)))

if you wanted to know when the last update was done (at least for records that have been updated since last time you deleted). You have to decide for yourself whether this is a better solution than what you currently have or not, I expect that could vary from system to system.

HTH,
Set