The premise

A strange thing happened yesterday. There was an order in our school bus booking system, but the fee was not deducted normally. No abnormal error was found when we checked the log, and other data were also normal. At this time, an experienced programmer immediately found the key point, the interface out of the order was called at the same time, thread A updated wallet, thread B also updated wallet, resulting in the loss of the wallet data updated by thread A.

Synchronized locks force data synchronization on threads

Thread A and thread B obtain the wallet at the same time, resulting in zero visibility of the wallet between threads, so as long as the code block updating the wallet is in A lock, when thread A obtains the lock, thread B can only wait for thread A to release the lock, to execute the perfect solution to the problem. Pseudo code

public void synchronized updateWallet(Long userId){ getWallet(userId); // Get the wallet do something; UpdateWalletByUserId (userId); // Update wallet}Copy the code

Distributed lock — Redis

Synchronized locks address thread synchronization for only one application, but we deploy two applications to handle concurrency, so we use redis locks.

The transaction

Thread synchronization problems are solved, there is a more important problems unsolved, careless before colleagues forgot to add transaction when processing orders, when we update order interface appear abnormal situation is bad, wallet updated, but there is no update order information, then there should be a manual to solve this problem. The unsuspecting colleague immediately added @Transactional to the service interface of the update order.

Problem representation

The problem appeared yesterday, under the guidance of the old programmer, we quickly completed the repair, and released the corrected version, and slept beautifully at night, but the problem appeared yesterday again today! Transactional thread synchronization has already been tested and worked out. Let’s do a concurrent test right now. Pseudo code

@Test public void testTransaction() { CountDownLatch latch = new CountDownLatch(1); JSONObject orderInfo0 = JSONObject.parseObject{\"userId\":1,}"); JSONObject orderInfo1 = JSONObject.parseObject{\"userId\":1,}"); New Thread(()->{try {// Wait for latch to be 0, execute the following code latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } orderService.updateOrder(orderInfo0); }).start(); New Thread(()->{try {// Wait for latch to be 0, execute the following code latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } orderService.updateOrder(orderInfo1); }).start(); // When the latch is reduced to 0, updateOrder starts executing latch.countdown (); try { Thread.sleep(100000); // Wait for all threads to execute} catch (InterruptedException e) {e.printStackTrace(); }}Copy the code

Log output

-- Thread-1 WALLET $20 -- Thread-2 Wallet $20Copy the code

It was normal when there were no transactions, but now it’s not normal when there are transactions?

REPEATABLE READ is the default isolation level for mysql.

Users A and B start A transaction at the same time. User A inserts data and submits the transaction. User B starts the query, but the data queried by user B does not changeCopy the code

Open the transaction log in the logback < logger name = “org. Springframework. Transaction” level = “debugger” / > to run down the testing methods, Thread A and thread B open the transaction, PROPAGATION_REQUIRED, when both threads enter the orderUpdate method. This level is the default transaction propagation mechanism of Spring. If there is an outer transaction, the current transaction is added to the outer transaction, and the transaction is committed and rolled back together. Therefore, when the updateWallet is added to the outer transaction, thread A updates the wallet and commits the transaction, but thread B still reads the same data.

Spring transaction propagation

  • PROPAGATION_REQUIRED @Transactional(propagation=Propagation.REQUIRED) PROPAGATION_REQUIRED is Spring’s default transaction propagation mechanism, which means that the current transaction is added to an outer transaction, if any.
  • PROPAGATION_REQUIRES_NEW @Transactional(propagation=Propagation.REQUIRES_NEW) PROPAGATION_REQUIRES_NEW The propagation mechanism for “start a new transaction, suspend the outer layer of the transaction, and resume the execution of the upper layer after the current transaction completes.”
  • PROPAGATION_SUPPORTS @Transactional(propagation=Propagation.SUPPORTS) PROPAGATION_SUPPORTS The propagation mechanism is that if there is a transaction in the outer layer, it will join the transaction. If there is no transaction, no new transaction will be created.
  • PROPAGATION_NOT_SUPPORTED @Transactional(propagation=Propagation.NOT_SUPPORTED) PROPAGATION_NOT_SUPPORTED, this propagation mechanism does not support transactions, PROPAGATION_NOT_SUPPORTED, if there is a transaction in the outer layer, suspend it and resume after execution.
  • PROPAGATION_NEVER @Transactional(propagation=Propagation.NEVER) PROPAGATION_NEVER the dissemination mechanism does not support transactions, direct throw an exception if outer exists IllegalTransactionStateException (” Existing transaction found for Transaction marked with propagation ‘never'”)
  • PROPAGATION_MANDATORY @Transactional(propagation=Propagation.MANDATORY) PROPAGATION_MANDATORY, the transmission mechanism is only in the existing transaction method calls, if in No transaction method calls, throw an exception IllegalTransactionStateException (” No existing transaction found for transaction marked with propagation ‘mandatory'”);
  • PROPAGATION_NESTED @Transactional(propagation=Propagation.NESTED) PROPAGATION_NESTED This propagation mechanism can save state savepoints, and a transaction is rolled back to a savepoint, avoiding all nested transactions being rolled back.

Simply add a transaction @transactional (Propagation =Propagation.REQUIRES_NEW) to updateWallet to solve this problem

conclusion

REPEATABLE READ (REPEATABLE READ) is the default isolation level of data when most systems are designed. In this case, we should have a clear understanding of spring transaction propagation, so as to better judge the situation of REPEATABLE READ, and reasonably change transaction propagation level to build our sound code.