当前位置:网站首页>Self inspection is recommended! The transaction caused by MySQL driver bug is not rolled back. Maybe you are facing this risk!

Self inspection is recommended! The transaction caused by MySQL driver bug is not rolled back. Maybe you are facing this risk!

2022-06-22 15:26:00 InfoQ


null
Share one today
Open source document online preview project solution kkFileView
Recent findings by authors .

As the title , The problem was finally identified because  mysql-connector-java:8.0.28  One of the  bug  As a result of . But before the truth comes out , The whole problem is complicated , The blogger hasn't checked such a powerful for a long time  bug , With layers of  debug  thorough , The truth also surfaced . This problem belongs to the bottom  jdbc  Driven issues , It's universal , May unknowingly , Your app also suffers from this online  bug  The devastation of , therefore , Please listen to me patiently when I finish this story , Then go back and check your application status , Have you stepped on the pit . What you like can be directly pulled to the end of the article to see the results .

background

When you tell a story, you usually introduce the characters first 、 background . This is not an exception , Let's introduce the interested parties first . Usually , The richer the story, the more wonderful , But here bloggers will consider space  ( No nonsense )  Some details irrelevant to the results will be ignored , Try to make the narration complete .

  • commons-db :  We maintain it internally , Is an annotation driven  Spring  Most resource management components in the ecosystem . Component to each  DataSource  Some default values for performance optimization are preset , Not all of them are listed , But it contains attributes that affect the direction of the problem (useLocalSessionState), as follows :

Properties defaultProperties = new Properties();
defaultProperties.put("prepStmtCacheSize", 300);
defaultProperties.put("prepStmtCacheSqlLimit", 2048);
defaultProperties.put("useLocalSessionState", true);
defaultProperties.put("cacheResultSetMetadata", true);
defaultProperties.put("elideSetAutoCommits", true);

  • java-project :  A project used to test component functionality , It will be used as a behavior test comparison with the project with problems .spring-boot:2.5.4、mysql-connector-java:8.0.26
  • store: Game library project , It was this project that found the problem .spring-boot:2.6.6 、mysql-connector-java:8.0.28
  • Alibaba cloud  RDS (MySQL):  Alibaba cloud  MySQL  The default isolation level is  READ_COMMITTED, and  MySQL  The default isolation level is  REPEATABLE_READ

explain :java-project  and  store  Of  commons-db  The version is actually different , Because it won't affect the result . This is consistent with their version .

problem

One day , Development feedback , stay  store  Use... In projects  commons-db  When the component , The transaction rollback does not take effect . As shown in the code below :

@Transactional
@DataSource(type = Type.MASTER,value = "developer")
public void addUser(ApolloUser user){
 userRepository.save(user);
 int i = 1/0; // Throw exceptions
}

  • Specific performance: : perform  addUser  Method , When  1/0  Throw out  RuntimeException  When the type is abnormal ,user  The object was added successfully . In one sentence ,【 Transaction rollback does not take effect 】.

hypothesis

  • hypothesis  1: I have assumed whether it is  @Transactional  Of  aop  It didn't work , As a result, the explicit transaction is not opened .
  • hypothesis  1  Don't set up , Because it's on  debug  After log mode , Clearly output the behavior log of each phase of the transaction , Such as :
    null
  • hypothesis  2: Considering the use of  commons-db ,  If the framework layer connection management problem , This causes the transaction to start 、 The connections obtained during transaction rollback are inconsistent , It may also lead to this problem .
  • hypothesis  2  Don't set up : It will be over soon , Because you can see from the above log that the connection is the same connection . And different connections perform unexpected opening 、 Rollback transactions should have exceptions .

So over here , The problem is deadlocked . I can't help thinking , A seemingly harmless code , A transaction log that looks logical , Why does transaction rollback fail ?????

Turning point

Turning point  1

And then , I am here  java-project  In the project , Use the same  MySQL  Tested under , It is found that the transaction rollback succeeded . Explain that this problem only affects a specific environment , And you can find the problem by comparing the differences between the two projects , Closer to the truth .

Turning point  2

Another key message came from the development side , stay  store  In the project , When the isolation level is set to  REPEATABLE_READ  when , Transaction rollback takes effect . Code such as :

 @Transactional(isolation = Isolation.REPEATABLE_READ)
 @DataSource(type = Type.MASTER,value = "developer")
 public void addUser(ApolloUser user){
 userRepository.save(user);
 int i = 1/0;
 }

Come here , Should we suspect that it is the isolation level ? It's obviously not true , Because the cognitive Dictionary of affairs , There is no note that the isolation level affects transaction rollback . And then from  java-project  We can also see that , In the same  RC  Under isolation level ,java-project  Can succeed .

The first solution

After all, it is a step forward , It can be solved temporarily by setting the isolation level 【 Transaction rollback does not take effect 】. however , Different isolation levels , Lock transactions 、 Concurrency performance is different , This must be expected before adjustment .

Turning point  3

When things go out of whack, they will , I don't believe the problem is caused by the isolation level , I am here  store  The project will  isolation  Set to  Isolation.READ_UNCOMMITTED , It is found that transaction rollback is also effective . This also shows that there is no direct relationship with the isolation level . Then in the spirit of exploration 【 Why the default reason  READ_COMMITTED  Cause the transaction not to take effect ?】 The idea of checking the next , We found some problems , The following code is part of the transaction logic ( See source code :DataSourceUtils.prepareConnectionForTransaction ()):

null
Find out , comparison  RR、RU ,  The difference is when the isolation level is  READ_COMMITTED  when , Not again  session  There is an update operation . At this stage, there is only one more clear phenomenon , Can explain the behavior after knowing the truth , Did not touch the edge of the truth .

analysis

The above is a pile of , I haven't found any real problems yet . So don't do any other tests , First analyze what you expect , And then targeted verification .

First, let's take a look at the common normal  Spring Transactional  Complete transaction rollback process , Generally, it refers to those without special parameter configuration , Generally, these parameters are not configured .

  • 1、 I added  @Transactional  Before the execution of the method , Will execute the transaction manager (DataSourceTransactionManager) Of  doBegin  Method to create a transaction , stay  doBegin  In the method , Will be set  autoCommit = false. It will determine whether the current isolation level is consistent with the user-defined , Otherwise, update the isolation level .
    null
  • 2、 After method execution fails , Will execute the transaction manager (DataSourceTransactionManager) Of  doRollback  Method to roll back the transaction .

from  Spring Transactional  There is no problem in the transaction log of , Create transaction 、 Set up manual commit transactions 、 Rollback transactions have log printing . So let's go deep into the driver layer 、 Or grab a bag , Whether these instructions are sent to  MySQL Server  了 .

Location problem

Such as analysis , stay  store  In the project , Set the breakpoint at  mysql-connector-java  Driven  NativeSession.execSQL ()  In the method , and  MySQL Server  All instructions for interaction , Eventually, this method will be called to execute . I found the problem :

  • When transaction rollback fails , The transaction flow is not executed  SET autocommit=0  Instructions .

It means that transaction rollback fails , Transactions have always been in auto commit mode , therefore , The abnormal rollback operation does not rollback the data that has been persisted .

After discovering this problem , Then locate why  Spring  Yes  Set autoCommit=false ,  The problem that has not been implemented in the end , Here again through 【 Turning point  1】 Of  java-project  Compare the single-step commissioning of the project , Found a piece of key code (ConnectionImpl.setAutoCommit ()) The code in the two projects is inconsistent :

java-project,mysql-connector-java:8.0.26( Transaction rollback takes effect )

null
store,mysql-connector-java:8.0.28( Transaction rollback does not take effect )

null
Here is a brief introduction to this parameter

  • useLocalSessionState: Maintain local  sessionState ,  In need of judgment  【 Transaction commit mode 】、【 Isolation level 】 When setting , Get local status , Not every time  MySQL Server  Initiate an inquiry .

This parameter helps reduce and  MySQL  Interaction , It can improve the performance of writing data . So when optimizing parameter performance , Is set to by default  true  了 . here , If  useLocalSessionState=false, Will just cover up this  bug.

Decrypt

Because in  store,mysql-connector-java:8.0.28  The problematic version  isAutocommit ()  Behavioral logic and  isAutoCommit ()  atypism , Should have called to judge  isAutocommit  return  true  when , But back to  false. It finally led to  store  On receiving  Spring Transactional  Set up  autoCommit=false  The request of , because  needsSetOnServer=false ,  Skip the real initiation directly  Set autocommit=0  Execution of instructions . This causes the current transaction mode to be the auto commit mode , So when there is any addition, deletion or modification in the transaction , Will immediately after the implementation  commit  Persistence . In this case, if an exception occurs and a transaction is initiated  rollback , Naturally, transactions that have been automatically committed before will not be rolled back . This well explains that the transaction log posted at the beginning is complete , But transaction is the problem that rollback does not take effect .

The second solution

Check it out here , The second way to solve the problem appears , Just let me judge whether it needs to be executed  Set autocommit=0  At the time of the  needsSetOnServer=true  Just set up . therefore , As long as the  store  The following two parameters are used to adjust any parameter configuration , Then we can solve the problem . This method is more suitable than the first method :

useLocalSessionState=false
auto-commit=false

Explain why  isolation  Set to  Isolation.REPEATABLE_READ  Will take effect

So that's the end of it ? did not , The expectation is that even if  useLocalSessionState=ture , The transaction should also be complete . Then don't forget  isAutoCommit ()  and  isAutocommit ()  The difference of . Let's look at their definition first :

public boolean isAutocommit() {
 return (this.statusFlags & 2) != 0;
}

public boolean isAutoCommit() {
 return this.autoCommit;
}

Originally in  mysql-connector-java:8.0.28  Drive in , Use  statusFlags  Status replaces  autoCommit  The logo of ( Why did you make this change ), This explains

  • Turning point  2: When the isolation level is set to  REPEATABLE_READ  when , Transaction rollback takes effect . Because when the user-defined isolation level  RR  And the default  RC  When not in agreement , Will trigger  session  Set a new isolation level , This will also  statusFlags = 0  Updated to  statusFlags = 2.  Therefore, in the call  isAutocommit ()  return  true , It meets the requirements of execution  SET autocommit=0  The conditions of the instruction .

Although we know the reason here , Also know exactly  isAutoCommit () != isAutocommit () , But it's not clear why this change was made . The specific problems here are not listed for the time being , Let's repeat the question first .

Repetition problem

Now that the problem has been well positioned , Then follow the routine troubleshooting process , Repeat the problem scenario as expected , Define the boundary of the problem . Because there may be other factors that may cause problems together . stay  java-project  In the project , Make the following dependent version adjustments

  • upgrade  spring-boot:2.6.6  Version and  store  bring into correspondence with : The question is repeated
  • keep  spring-boot:2.5.4, adjustment  mysql-connector-java:8.0.28 : The problem is repeated

Come here , Basically excluded  Spring Transactional  We're under suspicion . And then locked the spearhead on  mysql-connector-java:8.0.28  On the body .

confirm  bug

Consider from  mysql-connector-java:8.0.26  Of  isAutoCommit  Changed to  mysql-connector-java:8.0.28  Of  isAutocommit  There must be a reason , With the intention of making it clear that the code author submitted the change , I went to rummage  github.

  • https://github.com/mysql/mysql-connector-j

Look for it  github  Record of submission  commit , Find out , The latest version has been changed back to  isAutoCommit ()  了 , then  Commit Message  It clearly states that this is  8.0.28  Version of  bug, Such as .

null
thus , Finally the truth came out .

Repair

  • 8.0.29 release:https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/news-8-0-29.html
  • A connection did not maintain the correct autocommit state when it was used in a pool with useLocalSessionState=true. (Bug #106435, Bug #33850099)

The final solution

Such as  8.0.29 release  Notice , It has been fixed  8.0.28  Set up  useLocalSessionState=true  Under the circumstances ,autoCommit  Status setting problem . therefore , Application upgrade to  mysql-connector-java:8.0.29  Version can

Conclusion

First, summarize the question table as  Spring Transactional【 Transaction rollback does not take effect , Data submitted before rollback will not be rolled back 】, The root cause is  【mysql-connector-java:8.0.28  A change to version commit  bug , Results in enabling useLocalSessionState=true  Under the circumstances ,autoCommit  There is a problem with the status settings 】.

And then because  spring-boot:2.6.3 ~ 2.6.7 , These five versions default to  MySQL  Drive is  mysql-connector-java:8.0.28 , and  useLocalSessionState=true  Almost  Java JDBC DataSource  Standard configuration in , So this  bug  It is estimated that it will affect a large number of people . Then because it only affects the rollback operation , So this problem will be hidden deeply , It's not easy to detect , The so-called far-reaching impact .
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/173/202206221405182077.html