apache / apache/incubator-seata
Read Committed bug in SelectForUpdateExecutor.java
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26k
- Forks
- 8.8k
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 4
Description
- [ ] I have searched the [issues](https://github.com/seata/seata/issues) of this repository and believe that this is not a duplicate.
### Ⅰ. Issue Description
Normally, Seata supports the isolation level of Read Uncommited, but it does support Read Committed level by forupdate sql which is dealed in SelectForUpdateExecutor.java.
But in the method 'doExecute' of SelectForUpdateExecutor.java, the code
`rs = this.statementCallback.execute(this.statementProxy.getTargetStatement(), args);`
is firstly executed, then span checking the global lock. In extremly condition, it will cause Dirty Read in two Global Transactional.
For example (Global Transactional-1 as GT1 and Global Transactional-1 as GT2), a record is already updated in a finished Branch Transactional of GT1, then GT2 query this record before GT1 release global lock, then GT2 rollback because of a RuntimeException, then GT1 break span checking and
`return rs;` Because the record in GT2 is queried between GT1's update sql and rollback action, it is a instant dirty data;
Time axis like this:
GT1: --update Record A--------------------Rollback--------------------------------------->
GT2:-------------------- query Record A--------------Break span and return Record A----->
### Ⅱ. Describe what happened
So, in my test code, i construct a case through adding some codes in GT1:
`sleep(10000);`
`throw RuntimeException`
after a finished Branch Transactional which update a record, then running GT2 to query the same record in GT1's sleeping duration. GT2 get a dirty data.
### Ⅲ. Describe what you expected to happen
In my point, i think that a global forupade should acquire the global row lock, then executes query sql, then release global lock. Like this, we can avoid a dirty reading.
this solution can resolve this problem.
wish seata global forupdate perform as same as single Transactional in Read Committed Isolation level.
best wish!!
### Ⅳ. How to reproduce it (as minimally and precisely as possible)
as example as Ⅱ
### Ⅴ. Anything else we need to know?
### Ⅵ. Environment:
- JDK version :1.8
- OS : mac OS
- Others:
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read SelectForUpdateExecutor.java, focusing on doExecute and the ordering of statementCallback.execute versus global-lock checking. Reproduce the race with GT1 sleeping and then throwing after an update while GT2 queries the same record. Done means the Read Committed for-update path no longer returns dirty data in this scenario.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- databases, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100