A mysql deadlock troubleshooting process

A mysql deadlock investigation process

1. The background
  is going to have dinner on the 17th. Seeing that the girl next to you and Zuo Ge are still adjusting the code, they asked what questions they are still working on. When sending cards and coupons, there is a deadlock, but there is no deadlock in the code. The problem is as shown in the figure below. The



  log does indeed have a deadlock. According to the cause of the deadlock: Generally, the deadlock is two locks and two people compete for each other. Get one of them, no one will let them, wait for the other party to release the lock, which is caused by an infinite loop, the picture is as follows,

 
   but this time I said that there is no problem with reading the code, I feel that this problem is rather strange, I told them to eat first, finish eating, and group together Team Brainstorming researched this.

2. Problem points
1. ### SQL: select * from score_user where user_id = ? for update, this sql query sends a deadlock

3.
Troubleshooting process 1. According to experience and the conditions of deadlock, guess the code to execute concurrently, One thread locks the records of table A first, and another thread locks the records of table B because it has no clue table A records for some reason. Next, the thread that locks the records of A waits for the lock of B to lock the records of B The thread waits for the lock of A to be released, so there is a reason, so let's look at code
2 first. The code is as follows, you can see that the basic logic is to insert the score_gain_stream first
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,rollbackFor = Exception.class)
	public boolean generateScoreInfo(String userId, Integer score,
			Long scoreRuleId, int scoreType, int scoreStatus, String scoreWay,
			String orderId, String inviteeId, String reqId, Integer eventVersion) {

		//0: parameter judgment
		if(null == score || score <= 0) {
			log.warn("score null or < 0, userId:" + userId + "scoreRuleId:" + scoreRuleId + ",scoreWay:" + scoreWay);
			return true;
		}
		
		//1: Get user level   
		int memberLevel = MemberLevel.GENERAL_MEMBER;
		
		ScoreUser dbScoreUser = scoreUserManager.getScoreUserByUserIdForUpdate(userId);
		boolean isCreate = null == dbScoreUser ? true : false;
		if (!isCreate) {
			memberLevel = dbScoreUser.getMemberLevel();
		}
		
		// 2: Construct/Generate Integral Pipeline
		ScoreGainStream scoreGainStream = contructSocreGainStream(userId, score, scoreRuleId, scoreType, scoreStatus, scoreWay,
				orderId, inviteeId, reqId, eventVersion,memberLevel);
		
		boolean streamFlag = addScoreGainStream(scoreGainStream);
		
		if(!streamFlag){
			log.error("addScoreGainStream error,data:" + scoreGainStream.toString());
			return false;
		}
		
		// 3: Determine the user type
		if(isCreate){//Add points user information
			try {
				boolean addFlag = addScoreUser(userId, memberLevel, scoreType, score);
				if(!addFlag){
					log.error("generateScoreInfo addScoreUser error, userId:" + userId + "|" + "score:" + score );
					throw new RuntimeException("generateScoreInfo addScoreUser error");
				}
			} catch (Exception e) {
				if(e instanceof DuplicateKeyException){
					log.warn("addScoreUser DuplicateKeyException,userId:" + userId + "|" + "score:" + score);
					//Query user information
					ScoreUser updateUser = constructUpdateScoreUser(scoreUserManager.getScoreUserByUserIdForUpdate(userId), score, scoreStatus);
					
					boolean flag = scoreUserManager.updateUserScoreInfoById(updateUser) > 0 ? true : false;
					if(!flag){
						log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateUser.toString());
						throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");
					}
					
					return true;
					
				}else{
					log.error("addScoreUser error,userId:" + userId + "|" + "score:" + score, e);
					return false;
				}
			}
			
			return true;
			
		}else{//Update points user information
			ScoreUser updateScoreUser = contructUpdateScoreUser(dbScoreUser, score, scoreStatus);
			
			boolean flag = scoreUserManager.updateUserScoreInfoById(updateScoreUser) > 0 ? true : false;
			if(!flag){
				log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateScoreUser.toString());
				throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");
			}
			
			return true;
		}
	}

3. Looking at the code, there will be no deadlock, multiple threads are executing at the same time, each thread starts a transaction, each thread locks the query score_user, and finds that there is no query, then each thread performs the insert score_gain_stream operation , all are successful, next, insert score_user, there is only one thread that can succeed, there is a unique primary key, other threads will report an error here, then the code grabs an exception, performs a lock query, and reports an error at this time, deadlock

4. In theory, the error is reported. There is no competition for resources here. Everyone is waiting for the release of score_user. There is only one lock. How can there be a deadlock? It seems that the code cannot solve the problem.

5. Check the deadlock log of mysql again. See how the deadlock is generated, and how to query the deadlock log in the link below
http://825635381.iteye.com/blog/2339503



Look at the three parts in purple,
TRANSACTION 1292943095 requires
RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj`.`score_user
The X lock at this position has been waiting for this X lock

TRANSACTION 1292943097 This already holds
RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj` .`score_user
is an S lock at this position, so that TRANSACTION 1292943095 cannot obtain an X lock at this position.

TRANSACTION 1292943097 This transaction is also in the next
RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj`.`score_user
is waiting for the X lock , so the problem is: 1. Why does a thread hold the S lock, see the previous The code structure has not been added with S lock? 2. Why can't the transaction of TRANSACTION 1292943097 continue to add X lock and submit? 6. I started to investigate why there is an S lock here. After checking a lot of information, I finally found it in the official website document, as follows [b]







INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.
The general meaning is: insert will add an exclusive lock to the successfully inserted row. This exclusive lock is a record lock, not a next-key lock (of course, not a gap lock), and will not prevent other concurrent transactions from going here. Insert records before records. Before inserting, an insertion intention gap lock (I lock for short) will be added to the gap where the inserted record is located. Concurrent transactions can add an I lock to the same gap. If a duplicate-key error occurs in the insert transaction, the transaction will add a shared lock to the duplicate index record. This shared lock will cause deadlock in the case of concurrency. For example, there are two concurrent inserts that need to add a shared lock to the same record, and this record is added to an exclusive lock by other transactions. After the transaction of the exclusive lock is committed or rolled back, two concurrent insert operations will deadlock.
[/b] Principle analysis: This finds why the above problem is added with S lock. When a duplicate exception occurs during concurrent insertion, mysql will add S lock by default, which is why there is a transaction in the deadlock log. The addition of the S lock also explains the second question, why the transaction failed to commit, because the duplicate exception also occurred in the first transaction, and the S lock was also added to the same position, so there was a problem. In this case, multiple threads hold S locks at the same location, and each thread competes for X locks at this location. Both S and X locks are mutually exclusive, so a circular wait occurs, and a deadlock occurs. About mysql locks 4. Solutions 1. When inserting concurrently, do not re-submit the transaction within a transaction 2. Solve the problem of concurrent insertion by other means, such as pre-creating accounts 3. Change the concurrency to Serial execution 5. Solution process 6. Problem summary 1. MySQL is inserted concurrently. When duplicate occurs, S lock will be added by default . 1. The knowledge system needs to be improved again, and the technology is endless . Welcome everyone to pay attention to my official account:























Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326903153&siteId=291194637