Reputation: 3111
This is the stored procedure for any reason this perma-looping
I have to make some aclarations
its the first time that i use SCROLL KEYSET SCROLL_LOCKS
and WHERE CURRENT OF
the short explanation of what this does:
replacing a temporal id for many rows with a permanent id that is the MAX(num_subproceso)+1
but I want it to do in ACID fashion to solve possible concurrence iusses.
I can't use transaction directly in my app, the reason for this is that I'm using a classes set of my authorship that abstract the structure of the database and the data behavior in this way, only of the downside of my specific implementation is that i can't use a transaction because my code often in rapid opening/closing connection cycles and i need to make a transaction of big chunk of sqls.
To avoid this I finished generating a negative random id to write the rows in the database and later use a stored proc to use a transaction and use a cursor with row lock to avoid (maybe I'm wrong here) that other user get the same id using MAX(...)+1 and occurs a problem where 2 user write the same primary key.
Why I'm saying that I'm preventing concurrency problem (I could be wrong): if I lock a row that I'm using and other user makes an operation (ex. MAX(...) to get a new id) over the whole set of rows in the same table, the lock that is blocking my row prevent that the 2nd client can get the result and stay in wait state until my lock is released
The process is long and get various steps in a way that pass a time between start and finish the whole process .
... the permanent id is the max number of that type of id plus one ... in this way; while the temporal id is the same at first the permanent id can vary
(before)
cve_subproceso (PK) | num_subproceso (PK) | inidate (dd-mm-yyyy hh:MM:ss)
--------------------+---------------------+---------------------
A01 | -27813578125 | 01-05-2011 09:00:00
A02 | -27813578125 | 15-05-2011 10:00:00
A03 | -27813578125 | 16-05-2011 07:30:00
A07 | -27813578125 | 21-05-2011 09:15:00
A12 | -27813578125 | 30-05-2011 10:00:00
... | ... | ...
(after)
cve_subproceso (PK) | num_subproceso (PK) | inidate (dd-mm-yyyy hh:MM:ss)
--------------------+---------------------+---------------------
A01 | 157 | 01-05-2011 09:00:00
A02 | 122 | 15-05-2011 10:00:00
A03 | 15 | 16-05-2011 07:30:00
A07 | 90 | 21-05-2011 09:15:00
A12 | 140 | 30-05-2011 10:00:00
... | ... | ...
the number of the permanent id is based in how many [cve_subproceso] are in the table.
related to this example ... exists or existed at given time 156 'A01' secuenced from 1 to 156 when i'm adding 157 to A01 in this case, is because i calculated MAX([num_subproceso]) and MAX() returned 156
cve_subproceso (PK) | num_subproceso (PK) | inidate (dd-mm-yyyy hh:MM:ss)
--------------------+---------------------+---------------------
A01 | 156 | 01-05-2011 09:00:00
A01 | 155 | 15-04-2011 10:00:00
A01 | 154 | 03-04-2011 07:30:00
A01 | 152 | 11-03-2011 09:15:00
A01 | 151 | 10-01-2011 10:00:00
... | ... | ...
i repeat the same operation for each value of [cve_subproceso] ...
ALTER PROCEDURE [dbo].[ReprocesarEventos]
-- Add the parameters for the stored procedure here
@numSubproceso int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- Insert statements for procedure here
DECLARE @nuevoID integer;
DECLARE @cveSubProc varchar(4);
DECLARE cUpd CURSOR SCROLL KEYSET SCROLL_LOCKS
FOR SELECT [cve_subproceso] FROM [dbo].[calendario] WHERE [num_subproceso]= @numSubproceso
FOR UPDATE OF [num_subproceso];
OPEN cUpd;
FETCH NEXT FROM cUpd INTO @cveSubProc;
WHILE (@@FETCH_STATUS <> -1)
BEGIN
IF (@@FETCH_STATUS <> -2)
BEGIN
SELECT @nuevoID=(ISNULL(MAX([num_subproceso]),0)+1) FROM [dbo].[calendario] WHERE [cve_subproceso]=RTRIM(LTRIM(@cveSubProc));
IF(@nuevoID < 0)
BEGIN
SET @nuevoID = 1;
END;
UPDATE [dbo].[calendario] SET [num_subproceso]=@nuevoID WHERE CURRENT OF cUpd;
END;
FETCH NEXT FROM cUpd INTO @cveSubProc;
END;
CLOSE cUpd;
DEALLOCATE cUpd;
IF(@@ERROR <> 0)
BEGIN
-- Rollback the transaction
ROLLBACK TRANSACTION;
-- Raise an error and return
RAISERROR ('Oops! - something went wrong.', 16, 1);
RETURN;
END;
COMMIT TRANSACTION;
END;
what is wrong here?
Upvotes: 0
Views: 483
Reputation: 453287
I think this should do what you need. You need SERIALIABLE
to lock the range above MAX
. This will cause blocking but that's the point!
CREATE TABLE [dbo].[calendario](
[cve_subproceso] [char](3) NULL,
[num_subproceso] [bigint] NULL
)
INSERT INTO [dbo].[calendario]
SELECT 'A01',-27813578125 UNION ALL
SELECT 'A02',-27813578125 UNION ALL
SELECT 'A03',-27813578125 UNION ALL
SELECT 'A07',-27813578125 UNION ALL
SELECT 'A12',-27813578125 UNION ALL
SELECT 'A01',156 UNION ALL /*Add a few rows of pre-existing data*/
SELECT 'A01',157 UNION ALL
SELECT 'A02',121
DECLARE @numSubproceso BIGINT = -27813578125
/*Run the query*/
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
WITH t1
AS (SELECT *,
MAX(num_subproceso) OVER (PARTITION BY cve_subproceso) AS
max_num_subproceso
FROM [dbo].[calendario]),
t2
AS (SELECT *,
CASE
WHEN max_num_subproceso < 0 THEN 0
ELSE max_num_subproceso
END + Row_number() OVER (PARTITION BY cve_subproceso ORDER BY
cve_subproceso) AS
new_num_subproceso
FROM t1
WHERE [num_subproceso] = @numSubproceso)
UPDATE t2
SET num_subproceso = new_num_subproceso
cve_subproceso num_subproceso
-------------- --------------------
A01 158
A02 122
A03 1
A07 1
A12 1
A01 156
A01 157
A02 121
Upvotes: 1