Locking a stored procedure for updating/reading

  • Hey all.

    I have a stored procedure which will get called very frequently. This stored procedure has to get a unique integer from a table. Basically the table has only one column, with an ID...and this ID is basically the next available number. It only has one row. Everytime the SP gets called it has to read this number to return it, and then update the number by 1 on the table.

    This doesnt seem to be working properly. I have tried to use a transaction and commit it, however this is the first time I have tried something like this in sql server. Any help would be appreciated. My code is as follows:

    BEGIN TRAN TestTran

    SELECT ID

    FROM TestTable

    UPDATE TestTable

    SET ID = ID + 1

    COMMIT TRAN TestTran

    Thanks, Paul.

  • Invert SELECT and UPDATE: you don't lock the row just reading, you have to write to put a lock.

    BEGIN TRAN TestTran

    UPDATE TestTable

    SET ID = ID + 1

    SELECT ID - 1

    FROM TestTable

    COMMIT TRAN TestTran

    Hope this helps

    Gianluca

    -- Gianluca Sartori

  • ah sorry, i typed it out from memory and put it back to front...I did actually have the update first as I think it only locks in teh transaction if you do an update/insert/delete.

    However, when I look at another table which uses tehse numbers...I am seeing some ID's duplicated...so they are getting passed identical IDs from this SP. This is teh only SP used for it.

  • How many rows are in that table? If the answer is one then fine, but I think you need to be more explicit in what you are trying to do

    BEGIN TRAN

    -- Lock the row, nobody else can change it now

    UPDATE dbo.TestTable

    SET ID=ID+0

    -- Get the new value

    SELECT @id = ID + 1

    FROM dbo.TestTable

    -- Update the table with the new value

    UPDATE TestTable

    SET ID = @id

    COMMIT TRAN

    Once you do the first update the record is yours, and nobody else can read it, they will be blocked at that statement for any concurrent calls, then it gets the current value and adds one to it, then it updates the table for your new value and then commits the transaction. Then and ONLY then is the row available for other concurrent processes, you won't have anybody else readying it accidently..

    CEWII

  • If you are porting an application from Oracle, I would strongly suggest you redesign the way keys are created.

    If you really want to proceed with this idea, and do not mind potential gaps in the sequence, then make use of IDENTITY to avoid blocking problems. Something along the lines of:

    SET QUOTED_IDENTIFIER, ANSI_NULLS ON

    GO

    CREATE TABLE dbo.YourSequence

    (

    Sequence int IDENTITY NOT NULL

    )

    GO

    CREATE PROCEDURE dbo.GetNextSequence

    @NextSequence int OUTPUT

    AS

    SET NOCOUNT ON

    BEGIN TRANSACTION

    SAVE TRANSACTION RB

    INSERT INTO dbo.YourSequence DEFAULT VALUES

    SET @NextSequence = SCOPE_IDENTITY()

    ROLLBACK TRANSACTION RB

    COMMIT

    GO

    DECLARE @NextId int

    EXEC dbo.GetNextSequence @NextId OUTPUT

    SELECT @NextId

  • this is not a port from oracle or anything like that.

    Basically all it is, is a table with this one row, which needs to hold a number for the next available integer. I could use an identity to solve the problem. But this seems a bit OTT, as it would do an insert and create a table with many rowqs which is unneeded. I could perhaps insert a row, then delete the oldest row to keep the table rows down, but still I think this is a bit OTT.

    One row, which can only get read and updated one at a time is what I require, which was why I was trying to use the transaction route.

  • One row, which can only get read and updated one at a time is what I require, which was why I was trying to use the transaction route.

    If you look at the code you will see that the table has zero rows so only the identity is updated. This will not hold locks for the rest of an outer transaction.

  • ah, so running this SP all the time will not actually create rows? only update the identity?

  • paul.davidson.uk (7/27/2009)


    ah, so running this SP all the time will not actually create rows? only update the identity?

    Correct - the rollback stops the row being added.

    Rolling back to a savepoint means that any outer transaction will not be rolled back.

    Having a COMMIT after the rollback decrements @@trancount by 1 so the logic of any outer transaction will remain intact.

    ps If this is not a port from Oracle, the only reason I can think of for using this is if you need a sequence over a number of tables. If you just want an identifier on a particular table then make it an IDENTITY.

  • Technically it creates the row, updates the identity, then removes it. The identity update is outside the scope of the transaction (in case other connections need the values), so it does not roll back.

  • CREATE TABLE dbo.TestTable (

    IDINT NOT NULL)

    GO

    INSERT INTO dbo.TestTable VALUES (1)

    -- Use the OUTPUT clause to return the new value in a resultset

    UPDATE dbo.TestTable SET ID = ID + 1

    OUTPUT INSERTED.ID

    GO

    -- Use multiple assignment to save the new value in a variable

    DECLARE @ID INT

    UPDATE dbo.TestTable SET @ID = ID = ID + 1

    PRINT @ID

  • brilliant, thanks lads..

  • Scott Coleman (7/27/2009)


    -- Use multiple assignment to save the new value in a variable

    DECLARE @ID INT

    UPDATE dbo.TestTable SET @ID = ID = ID + 1

    PRINT @ID

    How does the code work?

    SET @ID = ID = ID+1 ?

    the variable @ID contains the updated value of ID. how does it work?

    Thanks,

    KB

    Thanks,
    Santhosh


    Human Knowledge Belongs To The World !!

  • From Books Online topic for UPDATE (Transact-SQL):

    SET @variable = column = expression sets the variable to the same value as the column. This differs from SET @variable = column, column = expression, which sets the variable to the pre-update value of the column.

  • We also have a table which we use to get sids from. The stored proc that gets the next sid does an application lock so that only one process at a time can run the code:

    This is the simplified table definition

    CREATE TABLE [dbo].[TableSIDS](

    [TableSIDSSID] [int] NOT NULL,

    [TableName] [varchar](40) NOT NULL,

    [LastSID] [int] NOT NULL,

    CONSTRAINT [PK__TableSIDS__TableSIDSSID] PRIMARY KEY NONCLUSTERED

    (

    [TableSIDSSID] ASC

    )

    )

    Each row contains the sid for a different table

    The stored procedure to get the next sid for a table can get multiple sids to use in a multiple insert scenario.

    Name:Alloc_Sids

    Author: xxx

    Date:??

    Purpose: Get the next SID for the requested table from the TableSIDS table.

    Outputs: the next SID value

    Returns: 0 = Success

    -200 = Failed to get a Lock within allowed time.

    -201 = invalid input.

    -202 = No record for requested table in TableSIDS

    OR Error updating TableSIDS.

    ****************************************************************************************

    Revision History:

    ***************************************************************************************/

    ALTER PROCEDURE [dbo].[Alloc_Sids](

    @tableName varchar(30),

    @numRequested int,

    @nKeyValue int OUTPUT) AS

    DECLARE @rc int;

    IF (@numRequested < 1) OR (@numRequested > 1000)

    RETURN -201;

    -- get an application lock, max wait of one minute

    EXEC @rc = sp_getapplock 'HIS_Alloc_Sids', 'Exclusive', 'Session', '60000';

    IF @rc >= 0 -- then the lock succeeded.

    BEGIN

    IF NOT EXISTS (SELECT * FROM TableSIDS WHERE TableName = @tableName)

    BEGIN

    EXEC sp_releaseapplock 'HIS_Alloc_Sids', 'Session';

    RETURN -202;

    END

    --===================================================

    BEGIN TRANSACTION;

    UPDATE TableSIDS SET LastSID = LastSID + @numRequested

    WHERE TableName = @tableName;

    IF @@ERROR != 0

    BEGIN

    ROLLBACK TRANSACTION;

    EXEC sp_releaseapplock 'HIS_Alloc_Sids', 'Session';

    RETURN -202;

    END

    SELECT @nKeyValue =

    (SELECT LastSID - @numRequested + 1

    FROM TableSIDS WHERE TableName = @tableName);

    COMMIT TRANSACTION;

    --===================================================

    EXEC sp_releaseapplock 'HIS_Alloc_Sids', 'Session';

    RETURN 0;

    END -- successfully got a lock

    -- if here, lock failed.

    EXEC sp_releaseapplock 'HIS_Alloc_Sids', 'Session'; -- just in case

    RETURN -200; -- lock failed to return within time allowed.

    Hope this helps



    Terri

    To speak algebraically, Mr. M. is execrable, but Mr. C. is
    (x+1)-ecrable.
    Edgar Allan Poe
    [Discussing fellow writers Cornelius Mathews and William Ellery Channing.]

Viewing 15 posts - 1 through 14 (of 14 total)

You must be logged in to reply to this topic. Login to reply