@@ -113,17 +113,27 @@ func (lr *LockResolver) Close() {
113
113
114
114
// TxnStatus represents a txn's final status. It should be Lock or Commit or Rollback.
115
115
type TxnStatus struct {
116
+ // The ttl is set from the `CheckTxnStatus` kv response, it is read only and do not change it.
116
117
ttl uint64
117
118
commitTS uint64
118
119
action kvrpcpb.Action
119
120
primaryLock * kvrpcpb.LockInfo
120
121
}
121
122
122
123
// IsCommitted returns true if the txn's final status is Commit.
123
- func (s TxnStatus ) IsCommitted () bool { return s .ttl == 0 && s . commitTS > 0 }
124
+ func (s TxnStatus ) IsCommitted () bool { return s .commitTS > 0 }
124
125
125
126
// IsRolledBack returns true if the txn's final status is rolled back.
126
- func (s TxnStatus ) IsRolledBack () bool { return s .ttl == 0 && s .commitTS == 0 }
127
+ func (s TxnStatus ) IsRolledBack () bool {
128
+ return s .ttl == 0 && s .commitTS == 0 && (s .action == kvrpcpb .Action_NoAction ||
129
+ s .action == kvrpcpb .Action_LockNotExistRollback ||
130
+ s .action == kvrpcpb .Action_TTLExpireRollback )
131
+ }
132
+
133
+ // IsStatusDetermined returns true if the txn's final status is determined.
134
+ func (s TxnStatus ) IsStatusDetermined () bool {
135
+ return s .IsRolledBack () || s .IsCommitted ()
136
+ }
127
137
128
138
// CommitTS returns the txn's commitTS. It is valid iff `IsCommitted` is true.
129
139
func (s TxnStatus ) CommitTS () uint64 { return s .commitTS }
@@ -137,26 +147,32 @@ func (s TxnStatus) Action() kvrpcpb.Action { return s.action }
137
147
// StatusCacheable checks whether the transaction status is certain.True will be
138
148
// returned if its status is certain:
139
149
//
140
- // If transaction is already committed, the result could be cached.
141
- // Otherwise:
142
- // If l.LockType is pessimistic lock type:
143
- // - if its primary lock is pessimistic too, the check txn status result should not be cached.
144
- // - if its primary lock is prewrite lock type, the check txn status could be cached.
145
- // If l.lockType is prewrite lock type:
146
- // - always cache the check txn status result.
150
+ // The `CheckTxnStatus` status logic is:
151
+ //
152
+ // If l.LockType is pessimistic lock type:
153
+ // - if its primary lock is pessimistic too, the check txn status result should NOT be cached.
154
+ // - if its primary lock is prewrite lock type, the check txn status could be cached.
155
+ // If l.lockType is prewrite lock type:
156
+ // - always cache the check txn status result.
147
157
//
148
158
// For prewrite locks, their primary keys should ALWAYS be the correct one and will NOT change.
159
+ //
160
+ // The mapping from `CheckTxnStatus` kv result to the tidb status:
161
+ //
162
+ // TxnStatus::RolledBack => resp.set_action(Action::NoAction),
163
+ // TxnStatus::TtlExpire => resp.set_action(Action::TtlExpireRollback),
164
+ // TxnStatus::LockNotExist => resp.set_action(Action::LockNotExistRollback),
165
+ // TxnStatus::Committed { commit_ts } => {
166
+ // resp.set_commit_version(commit_ts.into_inner())
167
+ // }
168
+ //
169
+ // So the transaction is regarded as committed if the commit_ts is not 0, and rollback if the
170
+ // `action` equals `Action::NoAction` or `Action::LockNotExistRollback` or `Action::TtlExpireRollback`.
171
+ // Refer to the tikv `CheckTxnStatus` handling logic for more information.
149
172
func (s TxnStatus ) StatusCacheable () bool {
150
- if s .IsCommitted () {
173
+ if s .IsCommitted () || s . IsRolledBack () {
151
174
return true
152
175
}
153
- if s .ttl == 0 {
154
- if s .action == kvrpcpb .Action_NoAction ||
155
- s .action == kvrpcpb .Action_LockNotExistRollback ||
156
- s .action == kvrpcpb .Action_TTLExpireRollback {
157
- return true
158
- }
159
- }
160
176
return false
161
177
}
162
178
@@ -485,7 +501,9 @@ func (lr *LockResolver) resolveLocks(bo *retry.Backoffer, opts ResolveLocksOptio
485
501
} else if err != nil {
486
502
return TxnStatus {}, err
487
503
}
488
- if status .ttl != 0 {
504
+ expiredAsyncCommitLocks := status .primaryLock != nil && status .primaryLock .UseAsyncCommit && ! forceSyncCommit &&
505
+ lr .store .GetOracle ().IsExpired (l .TxnID , status .ttl , & oracle.Option {TxnScope : oracle .GlobalTxnScope })
506
+ if status .ttl != 0 && ! expiredAsyncCommitLocks {
489
507
return status , nil
490
508
}
491
509
@@ -497,7 +515,7 @@ func (lr *LockResolver) resolveLocks(bo *retry.Backoffer, opts ResolveLocksOptio
497
515
cleanRegions = make (map [locate.RegionVerID ]struct {})
498
516
cleanTxns [l .TxnID ] = cleanRegions
499
517
}
500
- if status . primaryLock != nil && status . primaryLock . UseAsyncCommit && ! forceSyncCommit {
518
+ if expiredAsyncCommitLocks {
501
519
// resolveAsyncCommitLock will resolve all locks of the transaction, so we needn't resolve
502
520
// it again if it has been resolved once.
503
521
if exists {
@@ -815,11 +833,7 @@ func (lr *LockResolver) getTxnStatus(bo *retry.Backoffer, txnID uint64, primary
815
833
status .action = cmdResp .Action
816
834
status .primaryLock = cmdResp .LockInfo
817
835
818
- if status .primaryLock != nil && status .primaryLock .UseAsyncCommit && ! forceSyncCommit {
819
- if ! lr .store .GetOracle ().IsExpired (txnID , cmdResp .LockTtl , & oracle.Option {TxnScope : oracle .GlobalTxnScope }) {
820
- status .ttl = cmdResp .LockTtl
821
- }
822
- } else if cmdResp .LockTtl != 0 {
836
+ if cmdResp .LockTtl != 0 {
823
837
status .ttl = cmdResp .LockTtl
824
838
} else {
825
839
if cmdResp .CommitVersion == 0 {
@@ -873,7 +887,7 @@ func (data *asyncResolveData) addKeys(locks []*kvrpcpb.LockInfo, expected int, s
873
887
874
888
// Check locks to see if any have been committed or rolled back.
875
889
if len (locks ) < expected {
876
- logutil .BgLogger ().Debug ("addKeys: lock has been committed or rolled back" , zap .Uint64 ("commit ts" , commitTS ), zap .Uint64 ("start ts" , startTS ))
890
+ logutil .BgLogger ().Info ("addKeys: lock has been committed or rolled back" , zap .Uint64 ("commit ts" , commitTS ), zap .Uint64 ("start ts" , startTS ))
877
891
// A lock is missing - the transaction must either have been rolled back or committed.
878
892
if ! data .missingLock {
879
893
// commitTS == 0 => lock has been rolled back.
@@ -964,10 +978,10 @@ func (lr *LockResolver) checkSecondaries(bo *retry.Backoffer, txnID uint64, curK
964
978
}
965
979
966
980
// resolveAsyncResolveData resolves all locks in an async-commit transaction according to the status.
967
- func (lr * LockResolver ) resolveAsyncResolveData (bo * retry.Backoffer , l * Lock , status TxnStatus , data * asyncResolveData ) error {
981
+ func (lr * LockResolver ) resolveAsyncResolveData (bo * retry.Backoffer , l * Lock , status TxnStatus , keys [][] byte ) error {
968
982
util .EvalFailpoint ("resolveAsyncResolveData" )
969
983
970
- keysByRegion , _ , err := lr .store .GetRegionCache ().GroupKeysByRegion (bo , data . keys , nil )
984
+ keysByRegion , _ , err := lr .store .GetRegionCache ().GroupKeysByRegion (bo , keys , nil )
971
985
if err != nil {
972
986
return err
973
987
}
@@ -1003,31 +1017,49 @@ func (lr *LockResolver) resolveAsyncResolveData(bo *retry.Backoffer, l *Lock, st
1003
1017
func (lr * LockResolver ) resolveAsyncCommitLock (bo * retry.Backoffer , l * Lock , status TxnStatus , asyncResolveAll bool ) (TxnStatus , error ) {
1004
1018
metrics .LockResolverCountWithResolveAsync .Inc ()
1005
1019
1006
- resolveData , err := lr .checkAllSecondaries (bo , l , & status )
1007
- if err != nil {
1008
- return TxnStatus {}, err
1009
- }
1010
- resolveData .keys = append (resolveData .keys , l .Primary )
1020
+ var toResolveKeys [][]byte
1021
+ if status .IsStatusDetermined () {
1022
+ toResolveKeys = make ([][]byte , 0 , len (status .primaryLock .Secondaries )+ 1 )
1023
+ toResolveKeys = append (toResolveKeys , status .primaryLock .Secondaries ... )
1024
+ toResolveKeys = append (toResolveKeys , l .Primary )
1025
+ } else {
1026
+ // Only do checkAllSecondaries if the transaction status is undetermined.
1027
+ // The async commit transaction is regarded as committed if `resolveData.commitTS` is not 0,
1028
+ // otherwise it is regarded as rolled back. The transaction status should be determined if the
1029
+ // `checkAllSecondaries` finishes with no errors.
1030
+ resolveData , err := lr .checkAllSecondaries (bo , l , & status )
1031
+ if err != nil {
1032
+ return TxnStatus {}, err
1033
+ }
1034
+ resolveData .keys = append (resolveData .keys , l .Primary )
1011
1035
1012
- status .commitTS = resolveData .commitTs
1013
- if status .StatusCacheable () {
1014
- lr .saveResolved (l .TxnID , status )
1036
+ status .commitTS = resolveData .commitTs
1037
+ if status .StatusCacheable () {
1038
+ lr .saveResolved (l .TxnID , status )
1039
+ }
1040
+ toResolveKeys = resolveData .keys
1015
1041
}
1016
1042
1017
- logutil .BgLogger ().Info ("resolve async commit" , zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .commitTS ))
1043
+ if _ , err := util .EvalFailpoint ("resolveAsyncCommitLockReturn" ); err == nil {
1044
+ return status , nil
1045
+ }
1046
+ logutil .BgLogger ().Info ("resolve async commit locks" , zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .commitTS ), zap .Stringer ("TxnStatus" , status ))
1018
1047
if asyncResolveAll {
1019
1048
asyncBo := retry .NewBackoffer (lr .asyncResolveCtx , asyncResolveLockMaxBackoff )
1020
1049
go func () {
1021
- err := lr .resolveAsyncResolveData (asyncBo , l , status , resolveData )
1050
+ err := lr .resolveAsyncResolveData (asyncBo , l , status , toResolveKeys )
1022
1051
if err != nil {
1023
1052
logutil .BgLogger ().Info ("failed to resolve async-commit locks asynchronously" ,
1024
1053
zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .CommitTS ()), zap .Error (err ))
1025
1054
}
1026
1055
}()
1027
1056
} else {
1028
- err = lr .resolveAsyncResolveData (bo , l , status , resolveData )
1057
+ err := lr .resolveAsyncResolveData (bo , l , status , toResolveKeys )
1058
+ if err != nil {
1059
+ return TxnStatus {}, err
1060
+ }
1029
1061
}
1030
- return status , err
1062
+ return status , nil
1031
1063
}
1032
1064
1033
1065
// checkAllSecondaries checks the secondary locks of an async commit transaction to find out the final
0 commit comments