Database exists as updated concurrency processing-go

Source: Internet
Author: User
Tags rowcount

Objective

In this section we talk about the most common situation in concurrency is updated, if there is no row record in the concurrency inserted, at this time is not processed very easy to insert duplicate key situation, this article we describe the concurrency in the presence of the update row records seven scenarios and we have to comprehensively analyze the most appropriate solution.

To explore the existence of the update seven scenarios

Let's start by creating a test table

IF object_id (' Test ') is not NULL    DROP table testcreate table Test (    ID int,    Name nchar (+),    [Counter] int , primary key (ID),    Unique (Name)); GO
Solution one (open transaction)

We unify the creation of stored procedures to test concurrency by sqlquerystress, let's look at the first case.

IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     BEGIN transaction
   
    if EXISTS (SELECT  1                from    Test                WHERE   Id = @Id)        UPDATE  Test        SET     [Counter] = [ Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  Test                (Id, Name, [Counter])        VALUES  (@Id, @Name, 1);    Commitgo
   

There is less chance of opening 100 threads and 200 threads to insert duplicate keys at the same time.

Solution two (reduce isolation level to the lowest isolation level uncommited)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     SET TRANSACTION ISOLA tion level READ uncommitted    BEGIN TRANSACTION    IF EXISTS (SELECT  1                from    Test                WHERE   Id = @Id )        UPDATE  Test        SET     [Counter] = [Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  Test                (Id, Name, [Counter])        VALUES  (@Id, @name, 1);    Commitgo

At this point the problem remains the same as the solution (if the lower level is the lowest isolation level, if the row record is empty, if the previous transaction is not committed, the current transaction can read to the row record is empty, if the current transaction is inserted and committed, then the previous transaction commits the problem of inserting duplicate keys)

Solution three (raise isolation level to the highest level serializable)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     SET TRANSACTION ISOLA tion level SERIALIZABLE    BEGIN TRANSACTION    IF EXISTS (SELECT  1                from    dbo. Test                WHERE   Id = @Id)        UPDATE  dbo. Test        SET     [Counter] = [Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  dbo. Test                (Id, Name, [Counter])        VALUES  (@Id, @Name, 1);    Commitgo

Worse in this case, directly to lead to deadlock

Raising the isolation level to the highest isolation level resolves the insertion of a duplicate key, but does not commit for an update to get an exclusive lock, while another process queries for a shared lock will cause the process to block each other and cause a deadlock. So we know that the highest isolation level can sometimes solve concurrency problems but also lead to deadlock problems.

Solution IV (LIFT isolation level + good lock)

Now let's add the update lock on top of adding the highest isolation level, as follows:

IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     SET TRANSACTION ISOLA tion level SERIALIZABLE    BEGIN TRANSACTION    IF EXISTS (SELECT  1                from    dbo. Test with (UPDLOCK)                WHERE   Id = @Id)        UPDATE  dbo. Test        SET     [Counter] = [Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  dbo. Test                (Id, Name, [Counter])        VALUES  (@Id, @Name, 1);    Commitgo

No exception was found running multiple times, by querying data using an update lock instead of a shared lock, so that the data can be read without blocking other transactions, and the data is not changed since the last time the data was read, which resolves the deadlock problem. It seems like such a solution is feasible, if it is high concurrency do not know whether it is feasible.

Solution Five (elevation isolation level for row versioning snapshot)
ALTER DATABASE Upserttestdatabaseset allow_snapshot_isolation on ALTER database Upserttestdatabaseset read_committed_ SNAPSHOT Ongo IF object_id (' Testpro ') is not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     BEGIN transaction
   
    if EXISTS (SELECT  1                from    dbo. Test                WHERE   Id = @Id)        UPDATE  dbo. Test        SET     [Counter] = [Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  dbo. Test                (Id, Name, [Counter])        VALUES  (@Id, @Name, 1);    Commitgo
   

The above solution also appears to insert a duplicate key issue is undesirable.

Solution VI (Elevation isolation level + table variable)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)    DECLARE @updated tabl E (i INT);     SET TRANSACTION Isolation level SERIALIZABLE;    BEGIN TRANSACTION    UPDATE  Test    SET     [Counter] = [Counter] + 1    OUTPUT  DELETED. ID into            @updated    WHERE   id = @Id;     IF not EXISTS (SELECT  i                    from    @updated)        INSERT into  Test                (Id, Name, counter)        VALUES  (@Id, @Name, 1);    

After many authentication is also 0 error, seemingly through table variable form to achieve feasible.

Solution VII (LIFT isolation level +merge)

Through the merge key to implement the presence of the update is otherwise inserted, and we should be aware of setting the isolation level of SERIALIZABLE Otherwise insert duplicate key problem, the code is as follows:

IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)    SET TRAN Isolation LE VEL SERIALIZABLE     BEGIN TRANSACTION    MERGE Test as [target]    USING        (SELECT    @Id as Id        ) as source< C9/>on source. Id = [target]. Id when    matched then        UPDATE SET               [Counter] = [target].[ Counter] + 1 when not    matched then        INSERT (Id, Name, [Counter])        VALUES (@Id, @Name, 1);    Commitgo

Multiple authentication no exception information is still on 100 threads or 200 threads concurrently.

Summarize

In this section, we discuss in detail how to deal with the solution of the problem of insertion in concurrency, which is the case, and now the above three scenarios are feasible.

Solution one (highest isolation level + update lock)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     BEGIN transaction;
   update  dbo. Test with (UPDLOCK, HOLDLOCK)    SET     [Counter] = [Counter] + 1    WHERE   Id = @Id;     IF (@ @ROWCOUNT = 0)        BEGIN            INSERT  dbo. Test                    (Id, Name, [Counter])            VALUES  (@Id, @Name, 1);        END     Commitgo
Solution two (highest isolation level + table variable)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)    DECLARE @updated tabl E (i INT);     SET TRANSACTION Isolation level SERIALIZABLE;    BEGIN TRANSACTION    UPDATE  Test    SET     [Counter] = [Counter] + 1    OUTPUT  deleted.id into            @ Updated    WHERE   id = @id;     IF not EXISTS (SELECT  i                    from    @updated)        INSERT into  Test                (Id, Name, counter)        VALUES  (@Id, @Name, 1);    Commitgo
Solution three (highest isolation level + Merge)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)    SET TRAN Isolation LE VEL SERIALIZABLE     BEGIN TRANSACTION    MERGE Test as [target]    USING        (SELECT    @Id as Id        ) as source< C9/>on source. Id = [target]. Id when    matched then        UPDATE SET               [Counter] = [target].[ Counter] + 1 when not    matched then        INSERT (Id, Name, [Counter])        VALUES (@Id, @Name, 1);    Commitgo

For the time being can only think of these three solutions, personal comparison recommendation one and plan three, what do you have in mind, please leave your comments if feasible, I will follow up.

2017-06-03 Update

This blog post is very exciting, but also for the side dishes I re-learn the existence of the update is the reverse insert solution. This article re-update has been two days, during which I have been looking at this aspect of something more in-depth understanding of some of the basic aspects of things or is too general and I am not very understanding of the cause, the food is not terrible, terrible is not deep learning from the thought of their own is right, you say.

First we have to understand what the role of Updlock and Holdlock locks is, Holdlock is similar to the Serializable isolation level, we can read for shared locks, but cannot be updated and deleted and inserted until the current concurrent transaction is complete. The explanation for the blog in Updlock is that it allows you to read the data (without blocking other transactions) and update the data at a later time, while ensuring that the data has not been changed since the last time the data was read. When we use it to read records, we can add update locks to the records we fetch, and the record of the locks cannot be changed in other threads until the end of the transaction of the thread. It is easy to understand that it does not block concurrent queries and insertions, but blocks updates or deletes data that is queried for the current transaction, and updates locks switch to exclusive locks when queried for the existence of the data. So for the end of the above three solutions, we will elaborate.

Solution One (HOLDLOCK)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     BEGIN transaction;
   update  dbo. Test with (HOLDLOCK)    SET     [Counter] = [Counter] + 1    WHERE   Id = @Id;     IF (@ @ROWCOUNT = 0)        BEGIN            INSERT  dbo. Test                    (Id, Name, [Counter])            VALUES  (@Id, @Name, 1);        END     Commitgo

If we do not add the HOLDLOCK lock hint, although the update acquires an exclusive lock, the exclusive lock does not persist until the end of the transaction, which causes the problem of inserting duplicate keys, and when we add the HOLDLOCK lock hint The above also talks about the highest isolation level in pessimistic concurrency, The lock hint continues until the end of the transaction, and when there is a concurrent request, the update occurs when the query to the data exists, but the transaction has not yet been committed, and the other request will also find that the row record exists, but will be blocked by the current transaction update operation Lock, if the query to the data does not exist at this time.

Solution Two (UPDLOCK + HOLDLOCK)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)     BEGIN transaction
   
    if EXISTS (SELECT  1                from    dbo. Test with (UPDLOCK, HOLDLOCK)                WHERE   Id = @Id)        UPDATE  dbo. Test        SET     [Counter] = [Counter] + 1        WHERE   Id = @Id;    ELSE        INSERT  dbo. Test                (Id, Name, [Counter])        VALUES  (@Id, @Name, 1);    Commitgo
   

For the above query comparison the first solution we added the Updlock update lock instead of the shared lock for Select, which is intended to not cause blocking when the row record queried by the passed variable ID does not exist, so that it does not block the insertion of other transactions and ensures that the row record has not been modified since the last time , for Holdlock to ensure that the lock is released to the transaction, thus achieving our expectations. Summing up a sentence, if the query period row record exists then the locked resource is queried for the existence of the row record, if the row record does not exist during the query, then the holdlock to get the range lock on the primary key to prevent the insertion of a duplicate key before the lock is released. So updlock in order to resolve the concurrency update does not block other transactional queries, holdlock prevents the concurrent insertion of duplicate keys.

Solution Three (SERIALIZABLE + Merge)
IF object_id (' Testpro ') is a not NULL    DROP PROCEDURE Testpro; GO CREATE PROCEDURE Testpro (@Id INT) as    DECLARE @Name NCHAR (+) = CAST (@Id as NCHAR)    BEGIN transaction
   
    merge Test with (SERIALIZABLE) as [target]    USING        (SELECT    @Id as Id        ) as source on    source. Id = [target]. Id when    matched then        UPDATE SET               [Counter] = [target].[ Counter] + 1 when not    matched then        INSERT (Id, Name, [Counter])        VALUES (@Id, @Name, 1);    Commitgo
   

Database exists as updated concurrency processing-go

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.