Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Including an Aggregated Column's Related Values - Part 2Dealing with Cannot resolve collation conflict for equal to operation errors »
    comments

    The CSS SQL Server Engineers have posted an interesting post, I myself don't have any of the fake identity columns but I did many times suggest in newsgroups to use UPDLOCK and HOLDLOCK in a transaction to guarantee that 2 inserts would not generate the same key value. This stuff below is from the CSS SQL Server Engineers post here: http://blogs.msdn.com/psssql/archive/2009/10/26/reduce-locking-and-other-needs-when-updating-data-better-performance.aspx

    The following pattern typically stems from an old practice used in SQL 4.x and 6.x days, before IDENTITY was introduced.

    1. begin tran
    2. declare @iVal int
    3.  
    4. select @iVal = iVal from CounterTable (HOLDLOCK) where CounterName = 'CaseNumber'
    5.  
    6. update CounterTable
    7. set iVal = @iVal + 1
    8. where CounterName = 'CaseNumber'
    9.  
    10. commit tran
    11.  
    12. return @iVal

    This can be a dangerous construct. Assume that the query is cancelled (attention) right after the select. SQL Server treats this as a batch termination and does not execute other statements. The application now holds a lock under the open transaction and without proper handling it leads to blocking.

    One Statement Fix

    1. declare @iVal int
    2.  
    3. update CounterTable
    4. set @iVal = iVal = iVal + 1
    5. where CounterName = 'CaseNumber'
    6.  
    7. return @iVal

    So what do you think? Will you use that in the future? Is this too dangerous/unfamiliar?

    Take a look at this piece of code that was shown to me by Emtucifor in this forum post: http://forum.lessthandot.com/viewtopic.php?f=17&t=7601&start=50#p41079

    Important Note from Emtucifor:

    The information in this section is outdated. It was true in a previous service pack of SQL 2000 but now performs as one would expect.

    However, the point about this method being unreliable may still be true: please see the comments on this blog post for a new example of why the syntax:

    @var = column = /expression/
    does not always work as one might expect.

    1. UPDATE #Temp SET  @T = Test1 = 99, @T = Test2 = 47

    This proves that behind the scenes SQL Server expands the statement to the following:

    1. UPDATE #Temp SET  @T = 99, @T = 47, Test1 = @T, Test2 = @T
    Because of this proof that in fact the @Variable = Column = Expression syntax is not truly coupled in the way it appears to be (otherwise the first update would set Test1 to 99 and Test2 to 47 instead of both to 47), it is best to avoid that syntax entirely.

    So what do you think? Good or might be prone to some kind of bug in the future and then what?




    *** If you have a SQL related question try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

    About the Author

    User bio imageDenis has been working with SQL Server since version 6.5. Although he worked as an ASP/JSP/ColdFusion developer before the dot com bust, he has been working exclusively as a database developer/architect since 2002. In addition to English, Denis is also fluent in Croatian and Dutch, but he can curse in many other languages and dialects (just ask the SQL optimizer) He lives in Princeton, NJ with his wife and three kids.
    Social SitingsTwitterFacebookLinkedInHomePageFlickrLTD RSS Feed
    2549 views
    Instapaper

    8 comments

    Comment from: Erik [Member] Email
    *****
    Erik You'll have no problems if you use the following syntax:

    GO
    CREATE PROCEDURE GetCounter (@TableName varchar(100), @ID int OUT)
    AS

    UPDATE CounterTable
    SET
    @ID = LastID + 1,
    LastID = LastID + 1
    WHERE CounterName = @TableName
    GO

    Also:

    1. RETURN statements in procedures should only return error codes, not values with other meaning (such as the new ID). Use OUTPUT variables for that.

    2. UPDLOCK is also needed in the original code for it to work properly with high concurrency.

    For reference see http://weblogs.sqlteam.com/mladenp/archive/2009/10/06/Why-I-prefer-surrogate-keys-instead-of-natural-keys-in.aspx#41849

    and http://weblogs.sqlteam.com/dang/archive/2007/10/28/Conditional-INSERTUPDATE-Race-Condition.aspx
    10/26/09 @ 15:35
    Comment from: Ken Tong [Visitor]
    Ken Tong I am trying to reproduce your "proof" but my result shows @Variable = Column = Expression syntax works perfectly.

    My code
    --------------------------
    BEGIN TRAN

    DECLARE @i INT;

    SELECT 0 AS One, 0 AS Two
    INTO #Temp

    UPDATE #Temp
    SET @i = One = 1, @i = Two = 2

    SELECT *, @i AS Var FROM #Temp

    ROLLBACK
    --------------------------

    The result is One=1, Two=2, @i=2 (on SQL 2005).
    Am I missing something?
    10/28/09 @ 00:57
    Comment from: Erik [Member] Email
    Erik My apologies. When I first ran that script back in June of 2004 on a SQL 2000 box, it behaved as I described. That Denis didn't check me on it was because he trusted me. (and evidently shouldn't have!)

    However, it's still not completely resolved for me. Here is another example of how this syntax may not always work as expected:

    CREATE TABLE #Temp (i INT, j INT)
    INSERT INTO #Temp SELECT 0, 0
    INSERT INTO #Temp SELECT 0, 0
    INSERT INTO #Temp SELECT 0, 0
    DECLARE @i INT, @j INT
    SET @i = 1
    SET @j = 1
    UPDATE #Temp
    SET
    @i = @i + 1, @i = i = @i + 1,
    @j = @j + 1, @j = @j + 1, j = @j
    SELECT * FROM #Temp
    DROP TABLE #Temp

    Resultset:
    i j
    3 3
    4 5
    5 7

    Notice how the resultset starts with 3 instead of 2, and i and j don't hold the same values.
    10/28/09 @ 11:43
    Comment from: SQLDenis [Member] Email
    *****
    SQLDenis Thanks Emtucifor...in SQL 2008 with compound operators, multi-row inserts and initalizing variables when declaring, the code you posted


    CREATE TABLE #Temp (i INT, j INT)
    INSERT INTO #Temp SELECT 0, 0
    INSERT INTO #Temp SELECT 0, 0
    INSERT INTO #Temp SELECT 0, 0
    DECLARE @i INT, @j INT
    SET @i = 1
    SET @j = 1
    UPDATE #Temp
    SET
    @i = @i + 1, @i = i = @i + 1,
    @j = @j + 1, @j = @j + 1, j = @j
    SELECT * FROM #Temp
    DROP TABLE #Temp



    can be written as the following in 2008 and will save you a couple of lines of code

    CREATE TABLE #Temp (i INT, j INT)
    INSERT INTO #Temp values(0,0),(0,0),(0,0)
    DECLARE @i INT =1, @j INT =1

    UPDATE #Temp
    SET
    @i += 1, @i = i = @i + 1,
    @j += 1, @j += 1, j = @j
    SELECT * FROM #Temp
    DROP TABLE #Temp
    10/28/09 @ 13:15
    Comment from: Ken Tong [Visitor]
    Ken Tong Emtucifor, thanks for you excellent example. I use this syntax (@var = column = /expression/) a lot and I have to pay extra attention to it in the future.
    10/28/09 @ 19:46
    Comment from: JeffModen [Member] Email
    JeffModen Heh... no wonder they were having problems with it... The following construct in the original post from the start of this thread is still incorrect...
    ______________________________________________
    DECLARE @iVal INT

    UPDATE CounterTable
    SET @iVal = iVal = iVal + 1
    WHERE CounterName = 'CaseNumber'

    RETURN @iVal
    ______________________________________________


    It should be...
    ______________________________________________
    DECLARE @iVal INT

    UPDATE CounterTable
    SET @iVal = iVal = @iVal + 1
    WHERE CounterName = 'CaseNumber'

    RETURN @iVal
    ______________________________________________

    The other thing is that they are missing a few "safeties". To be safe, you must prevent others from inserting/modifying/deleting to or from the table. You must also prevent parallelism. All of that is accomplished as follows...

    ______________________________________________
    DECLARE @iVal INT

    UPDATE CounterTable
    SET @iVal = iVal = @iVal + 1
    FROM CounterTable WITH (TABLOCKX)
    WHERE CounterName = 'CaseNumber'
    OPTION (MAXDOP 1)

    RETURN @iVal
    ______________________________________________

    --Jeff Moden

    10/28/09 @ 22:03
    Comment from: Erik [Member] Email
    Erik Yes, Jeff!! Thanks for mentioning the parallelism issue. I don't grasp how it exactly connects, here, but given huge range of problems with it, I'm not surprised.
    10/29/09 @ 10:54
    Comment from: Erik [Member] Email
    Erik Actually, I'm really curious to know why it must be an exclusive table lock. Isn't an update an atomic operation? Or is there still a race condition window where someone could read the value after someone else has but before they've updated it?

    The use of TABLOCKX suggest to me to use one key-gen table per data table so that high concurrency systems are not harmed.
    10/29/09 @ 13:18

    Leave a comment


    Your email address will not be revealed on this site.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)