Talk about your understanding of MySQL deadlock

1. What is deadlock?

Deadlock refers to the fact that in two or more different processes or threads, due to the competition for common resources or the communication between processes (or threads), each thread suspends and waits for each other. If there is no external force, eventually will cause the entire system to crash.

    2. Necessary conditions for Mysql deadlock

  • Resource Exclusivity Condition

Refers to the mutual exclusion of multiple transactions competing for the same resource, that is, a resource is only occupied by one transaction for a period of time, which can also be called an exclusive resource (such as a row lock).

  • Request and hold conditions

It means that lock A has been acquired in a transaction a, but a new lock B request is made, and the lock B is already occupied by other transaction b. At this time, the transaction a will block, but the lock A that it has acquired will be blocked. hold on.

  • no deprivation condition

It means that a lock A has been acquired in a transaction a, and it cannot be deprived before it is committed. It can only be committed after the transaction is used and then released by itself.

  1. Mutual acquisition lock condition

It means that when a deadlock occurs, there must be a process of mutual acquisition of locks, that is, while transaction a holding lock A acquires lock B, transaction b holding lock B is also acquiring lock A, which eventually leads to mutual acquisition and each transaction are blocked.

    3. Mysql classic deadlock case

Suppose there is a transfer scenario, when account A transfers 50 yuan to account B, and account B also transfers 30 yuan to account A, then will there be a deadlock situation in this process?

    3.1 Create table statement

CREATE TABLE `account` (
  `id` int(11) NOT NULL COMMENT 'primary key',
  `user_id` varchar(56) NOT NULL COMMENT 'user id',
  `balance` float(10,2) DEFAULT NULL COMMENT 'balance',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Account Balance Sheet';

    3.2 Initialize related data

INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (1, 'A', 80.00);
INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (2, 'B', 60.00);

    3.3 Normal transfer process

Before talking about the deadlock problem, let's take a look at the normal transfer process. normal

In this case, user A transfers 50 yuan to user B, which can be completed in one transaction. It is necessary to obtain the balance of user A and the balance of user B first. Because these two data need to be modified later, it is necessary to pass a write lock (for UPDATE) Lock them to prevent dirty data caused by other transactional changes causing our changes to be lost.

The relevant sql is as follows:

Before starting the transaction, you need to turn off the automatic commit of mysql

set autocommit=0;
# View transaction auto-commit status status

show VARIABLES like 'autocommit';![在这里插入图片描述](https://img-blog.csdnimg.cn/a486a4ed5c9d4240bd115ac7b3ce5a39.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_

Q1NETiBA6ZqQIOmjjg==,size_20,color_FFFFFF,t_70,g_se,x_16)

# transfer sql
START TRANSACTION;
# Get the balance of A and store it in the A_balance variable: 80
SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;
# Get the balance of B and store it in the B_balance variable: 60
SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;

# Modify the balance of A
UPDATE account set balance = @A_balance - 50 where user_id = 'A';
# Modify the balance of B
UPDATE account set balance = @B_balance + 50 where user_id = 'B';
COMMIT;

Result after execution:

It can be seen that the data update is normal

    3.4 Deadlock transfer process

The initialized balance is:

Suppose this scenario exists under high concurrency. When user A transfers 50 yuan to user B, user B also transfers 30 yuan to user A.

Then the process and timeline of our java program operation are as follows:

  1. User A transfers 50 yuan to user B, and needs to open transaction 1 in the program to execute sql, and obtain the balance of A and lock the data of A.

# Transaction 1
set autocommit=0;
START TRANSACTION;
# Get the balance of A and store it in the A_balance variable: 80
SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;

2. When user B transfers 30 yuan to user A, he needs to open transaction 2 in the program to execute sql, obtain the balance of B, and lock the data of B.

# Transaction 2
set autocommit=0;
START TRANSACTION;
# Get the balance of A and store it in the A_balance variable: 60
SELECT user_id,@A_balance:=balance from account where user_id = 'B' for UPDATE;

3. Execute the remaining sql in transaction 1

# Get the balance of B and store it in the B_balance variable: 60
SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;

# Modify the balance of A
UPDATE account set balance = @A_balance - 50 where user_id = 'A';
# Modify the balance of B
UPDATE account set balance = @B_balance + 50 where user_id = 'B';
COMMIT;

It can be seen that a timeout occurred when the write lock of B data was acquired in transaction 1. Why is this so? Mainly because we have obtained the write lock of B data in transaction 2 in step 2, so transaction 1 will never get the write lock of B data before transaction 2 commits or rolls back.

4. Execute the remaining sql in transaction 2

# Get the balance of A and store it in the B_balance variable: 60
SELECT user_id,@B_balance:=balance from account where user_id = 'A' for UPDATE;

# Modify the balance of B
UPDATE account set balance = @A_balance - 30 where user_id = 'B';
# Modify the balance of A
UPDATE account set balance = @B_balance + 30 where user_id = 'A';
COMMIT;

Similarly, a timeout also occurs when the write lock of A data is acquired in transaction 2. Because the write lock of data A has been acquired in transaction 1 in step 1, transaction 2 will never get the write lock of data A before transaction 1 commits or rolls back.

5. Why does this happen?

Mainly because transaction 1 and transaction 2 have the process of waiting for each other to acquire locks, causing both transactions to hang and block, and finally throw an exception of lock acquisition timeout.

    3.5 Problems caused by deadlock

As we all know, the connection resources of the database are very precious. If a connection is not released for a long time due to transaction blocking, the sql to be executed by the new request will also be queued and waited, and the accumulation will eventually drag down the entire application. Once your application is deployed in the microservice system without fuse processing, since the entire link is blocked, an avalanche effect will occur, resulting in serious production accidents.

    4. How to solve the deadlock problem?

To solve the deadlock problem, we can start from the four necessary conditions of deadlock

obtain.

because

The resource exclusive condition and the non-deprivation condition are the functional manifestation of the essence of the lock and cannot be modified, so we try to solve it from the other two conditions.

    4.1 Breaking Request and Hold Conditions

According to the above definition, this happens because transaction 1 and transaction 2 compete for lock A and lock B at the same time, so can we guarantee that lock A and lock B can only be competed and held by one transaction at a time?
The answer is yes. Let's take a look at the pseudo code below:

/**
* Transaction 1 input parameters (A, B)
* Transaction 2 input parameters (B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
     // get distributed lock
     Lock lock = Redisson.getLock();
     // start transaction
     JDBC.excute("START TRANSACTION;");
     // Execute transfer sql
     JDBC.execute("# Get the balance of A and store it in the A_balance variable: 80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# Get the balance of B and store it in the B_balance variable: 60\n" +
             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
             "\n" +
             "# Modify A's balance\n" +
             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
             "# Modify B's balance\n" +
             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
     // commit the transaction
     JDBC.excute("COMMIT;");
     // release the lock
     lock.unLock();
}

The above pseudo code can obviously solve the deadlock problem, because all transactions are executed serially through distributed locks.

So is it really all right?

It seems to be no problem in the case of small traffic, but in high concurrency scenarios this will become the performance bottleneck of the entire service, because even if you deploy more machines, your business will only be limited due to distributed locks. It can be carried out in series, and the service performance does not increase the concurrency due to cluster deployment. It cannot meet the requirements of fast, accurate, and stable distributed services. Therefore, let's look at how to solve the deadlock problem in another way.

    4.2 Break the mutual acquisition lock condition (recommended)

To break this condition is actually very simple, that is, in the process of acquiring the lock, the transaction can ensure the sequential acquisition, that is, the lock A is always acquired before the lock B.
Let's take a look at how to optimize the previous pseudocode?

/**
* Transaction 1 input parameters (A, B)
* Transaction 2 input parameters (B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
     // Sort users A and B so that userFrom is always user A and userTo is always user B
     if (userFrom.hashCode() > userTo.hashCode()) {
         String tmp = userFrom;
         userFrom = userTo;
         userTo = tmp;
     }
     // start transaction
     JDBC.excute("START TRANSACTION;");
     // Execute transfer sql
     JDBC.execute("# Get the balance of A and store it in the A_balance variable: 80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# Get the balance of B and store it in the B_balance variable: 60\n" +
             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
             "\n" +
             "# Modify A's balance\n" +
             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
             "# Modify B's balance\n" +
             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
     // commit the transaction
     JDBC.excute("COMMIT;");
 }

Assume that the input parameters of transaction 1 are (A, B), and the input parameters of transaction 2 are (B, A). Since we have sorted the two user parameters, in transaction 1, we need to acquire lock A first and then acquire lock B. The same is true for transaction 2, which needs to acquire lock A first and then acquire lock B. Both transactions acquire locks sequentially, so the condition for acquiring locks from each other is broken, and the deadlock problem is finally solved perfectly.

    5. Summary

Because mysql is widely used in the Internet, the deadlock problem is often asked. I hope brothers can master this knowledge and improve their competitiveness.

source:

https://www.cnblogs.com/yin-feng/p/16041014.html

dry goods sharing

Recently, I organized my personal study notes into a book and shared them using PDF. Follow me, reply to the following code, you can get the Baidu disk address, no routines to receive!

• 001: "Java Concurrency and High Concurrency Solutions" study notes; • 002: "In-depth JVM Kernel - Principle, Diagnosis and Optimization" study notes; • 003: "Java Interview Collection" • 004: "Docker Open Source Book" • 005: "Kubernetes Open Source Book" • 006: "DDD Crash (Domain Driven Design Crash)"

Guess you like

Origin blog.csdn.net/Trouvailless/article/details/124435851