3

Several web servers access a SQL Server to get a numeric code, when this code doesn't exist, it has to be autogenerated by the SQL Server.

I need to ensure that even if two concurrent calls come in and the code doesn't exist, only one code is created and both calls return the same code. So I have to do something like this:

begin lock
  if code exists
    return code
  else
    generate code
    return code
end lock

I've been reading a little about isolation levels and table locking, but I have a terrible mess with all that. First I thought that a SERIALIZABLE isolation level is what I need, but apparently it's not.

So, what would you do to accomplish a "lock" in TSQL?

Thanks a lot.

UPDATE:

I got this error when I try to set the serializable level using this as example:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE get_code 
AS
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    GO
    BEGIN TRANSACTION

    select code from codes where granted is null;
END
GO

Msg 1018, Level 15, State 1, Procedure get_code, Line 4 Incorrect syntax near 'SERIALIZABLE'. If this is intended as a part of a table hint, A WITH keyword and parenthesis are now required. See SQL Server Books Online for proper syntax. Msg 102, Level 15, State 1, Line 5 Incorrect syntax near 'END'.

What does it means?

vtortola
  • 34,709
  • 29
  • 161
  • 263
  • How do you know if a code already exists? – Hogan Jun 14 '11 at 11:39
  • damn go's! I didn't find the usefulness of that keyword yet :D – vtortola Jun 14 '11 at 11:50
  • More on "GO" in SQL Server: [One](http://stackoverflow.com/questions/971177/using-go-within-a-transaction/971199#971199) and [Two](http://stackoverflow.com/questions/2299249/what-is-the-use-of-go-in-sql-server-management-studio/2299275#2299275). Please read my answer if you want this to work... – gbn Jun 14 '11 at 12:14
  • To this day, i still don't know what GO is for. COMMIT, yes. But not GO. I consider it yet another Microsoft lame feature. – Henley Aug 08 '14 at 15:55

3 Answers3

9

SERIALIZABLE is an isolation level for locking, not a semaphore.

It won't work in this case all you'll do is persist a read lock to the end of the TXN that doesn't prevent another process into the code reading.

You need to use sp_getapplock in Transaction mode. You can configure it to wait, bomb immediately etc: up to you

This is based on my template from Nested stored procedures containing TRY CATCH ROLLBACK pattern?

ALTER PROCEDURE get_code 
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int, @result int;

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0 BEGIN TRANSACTION

    EXEC @result = sp_getapplock 'get_code', 'Exclusive', 'Transaction', 0 
    IF @result < 0
        RAISERROR('INFO: One at a time please`!', 16, 1);

    [...Perform work...]


    IF @starttrancount = 0 
        COMMIT TRANSACTION
    ELSE
        EXEC sp_releaseapplock 'get_code';
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION
    RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO
Pang
  • 9,564
  • 146
  • 81
  • 122
gbn
  • 422,506
  • 82
  • 585
  • 676
  • just wondering since we're on this topic, do you see any mistakes in the way I did it (see my answer)? – Hogan Jun 14 '11 at 13:22
  • That's fantastic, just what I wanted. Only one thing... shoudln't you release the lock also in the CATCH block? (from a C# developer perspective, it looks like if a error is raised, the release won't be called) – vtortola Jun 14 '11 at 13:58
  • 1
    @vtortola: you could, but it's already released and you'd have to test/trap that. When it's "transaction" mode it only exists for the transaction: SET XACT_ABORT ON auto rolls back the transaction. So does my redundant ROLLBACK. I do release it if it's a nested transaction – gbn Jun 14 '11 at 14:02
  • 1
    I see, so for that reason you don't release the lock either if you commit. Interesting. Thanks a million. – vtortola Jun 14 '11 at 14:05
2

This is how I did it. Given a table MetaInfo with columns MetaKey varchar(max) and MeatValueLong bigInt.

Note, in my case there goal was to exclusively get a increasing value without duplicates. I used a rowlock to create the isolation on this single operation. (Yes I know I could have used inserting and an auto-increment key, but there was an addition requirement that the caller can pass in a minimum value.)

CREATE PROCEDURE [dbo].[uspGetNextID]
(
  @inID bigInt 
)
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION

    -- This section can be removed if you want to pass in an id.
    SET @inID = 0

    UPDATE MetaInfo WITH (ROWLOCK) 
      SET MetaValueLong = CASE 
                            WHEN ISNULL(MetaValueLong,0) > @inID THEN MetaValueLong+1 
                            ELSE @inID+1
                          END 
    WHERE MetaKey = 'Internal-ID-Last'

    SELECT MetaValueLong 
    FROM MetaInfo
    WHERE MetaKey = 'Internal-ID-Last'

    COMMIT TRANSACTION 

END
Hogan
  • 69,564
  • 10
  • 76
  • 117
  • should make this a new question: it's different with an UPDATE. And it isn't table-as-a-queue" eg http://stackoverflow.com/questions/939831/sql-server-process-queue-race-condition/940001#940001 – gbn Jun 14 '11 at 13:40
  • @gbn -- You comment implies it is wrong -- I believe it works as expected -- should I make it a question so you can show me how it is not working correctly? – Hogan Jun 14 '11 at 14:26
  • I think it's worth discussing separately with a wider audience because, to be honest, I'm not 100% sure :-). It is the same row being updated (you are emulating a SEQUENCE) which isn't the same as my race condition link, nor the same as using a semaphore like this question. Sorry! – gbn Jun 14 '11 at 14:32
  • See http://dba.stackexchange.com/questions/3307/emulate-a-tsql-sequence-via-a-stored-procedure – Hogan Jun 14 '11 at 17:20
1

yes, SET ISOLATION LEVEL SERIALIZABLE is exactly what you need. It does not permit dirty writes and dirty reads. All db-objets that are inside serializable transactions are locked so other connections will be able read/write only when first one does commit or rollback.

heximal
  • 10,327
  • 5
  • 46
  • 69