@@ -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,27 +147,37 @@ 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 rolled back 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 () {
151
- return true
152
- }
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
- return false
173
+ return s .IsStatusDetermined ()
174
+ }
175
+
176
+ // HasSameDeterminedStatus checks whether the current status is equal to another status if the
177
+ // transaction status is determined.
178
+ func (s TxnStatus ) HasSameDeterminedStatus (other TxnStatus ) bool {
179
+ return (s .IsCommitted () && other .IsCommitted () && s .CommitTS () == other .CommitTS ()) ||
180
+ (s .IsRolledBack () && other .IsRolledBack ())
161
181
}
162
182
163
183
func (s TxnStatus ) String () string {
@@ -204,10 +224,23 @@ func NewLock(l *kvrpcpb.LockInfo) *Lock {
204
224
}
205
225
206
226
func (lr * LockResolver ) saveResolved (txnID uint64 , status TxnStatus ) {
227
+ if ! status .IsStatusDetermined () {
228
+ logutil .BgLogger ().Error ("unexpected undetermined status saved to cache" ,
229
+ zap .Uint64 ("txnID" , txnID ), zap .Stringer ("status" , status ), zap .Stack ("stack" ))
230
+ panic ("unexpected undetermined status saved to cache" )
231
+ }
207
232
lr .mu .Lock ()
208
233
defer lr .mu .Unlock ()
209
234
210
- if _ , ok := lr .mu .resolved [txnID ]; ok {
235
+ if savedStatus , ok := lr .mu .resolved [txnID ]; ok {
236
+ // The saved determined status should always equal to the new one.
237
+ if ! (savedStatus .HasSameDeterminedStatus (status )) {
238
+ logutil .BgLogger ().Error ("unexpected txn status saving to the cache, the existing status is not equal to the new one" ,
239
+ zap .Uint64 ("txnID" , txnID ),
240
+ zap .String ("existing status" , savedStatus .String ()),
241
+ zap .String ("new status" , status .String ()))
242
+ panic ("unexpected txn status saved to cache with existing different entry" )
243
+ }
211
244
return
212
245
}
213
246
lr .mu .resolved [txnID ] = status
@@ -491,19 +524,28 @@ func (lr *LockResolver) resolveLocks(bo *retry.Backoffer, opts ResolveLocksOptio
491
524
} else if err != nil {
492
525
return TxnStatus {}, err
493
526
}
494
- if status .ttl != 0 {
527
+ ttlExpired := (lr .store == nil ) || (lr .store .GetOracle ().IsExpired (l .TxnID , status .ttl , & oracle.Option {TxnScope : oracle .GlobalTxnScope }))
528
+ expiredAsyncCommitLocks := status .primaryLock != nil && status .primaryLock .UseAsyncCommit && ! forceSyncCommit && ttlExpired
529
+ if status .ttl != 0 && ! expiredAsyncCommitLocks {
495
530
return status , nil
496
531
}
497
532
498
- // If the lock is committed or rolled back, resolve lock.
499
- // If the lock is regarded as an expired pessimistic lock, pessimistic rollback it.
533
+ // If the lock is non-async-commit type:
534
+ // - It is committed or rolled back, resolve lock accordingly.
535
+ // - It does not expire, return TTL and backoff wait.
536
+ // - It is regarded as an expired pessimistic lock, pessimistic rollback it.
537
+ //
538
+ // Else if the lock is an async-commit lock:
539
+ // - It does not expire, return TTL and backoff wait.
540
+ // - Otherwise, try to trigger the `resolveAsyncCommitLock` process to determine the status of
541
+ // corresponding async commit transaction.
500
542
metrics .LockResolverCountWithExpired .Inc ()
501
543
cleanRegions , exists := cleanTxns [l .TxnID ]
502
544
if ! exists {
503
545
cleanRegions = make (map [locate.RegionVerID ]struct {})
504
546
cleanTxns [l .TxnID ] = cleanRegions
505
547
}
506
- if status . primaryLock != nil && status . primaryLock . UseAsyncCommit && ! forceSyncCommit {
548
+ if expiredAsyncCommitLocks {
507
549
// resolveAsyncCommitLock will resolve all locks of the transaction, so we needn't resolve
508
550
// it again if it has been resolved once.
509
551
if exists {
@@ -821,11 +863,7 @@ func (lr *LockResolver) getTxnStatus(bo *retry.Backoffer, txnID uint64, primary
821
863
status .action = cmdResp .Action
822
864
status .primaryLock = cmdResp .LockInfo
823
865
824
- if status .primaryLock != nil && status .primaryLock .UseAsyncCommit && ! forceSyncCommit {
825
- if ! lr .store .GetOracle ().IsExpired (txnID , cmdResp .LockTtl , & oracle.Option {TxnScope : oracle .GlobalTxnScope }) {
826
- status .ttl = cmdResp .LockTtl
827
- }
828
- } else if cmdResp .LockTtl != 0 {
866
+ if cmdResp .LockTtl != 0 {
829
867
status .ttl = cmdResp .LockTtl
830
868
} else {
831
869
if cmdResp .CommitVersion == 0 {
@@ -879,7 +917,7 @@ func (data *asyncResolveData) addKeys(locks []*kvrpcpb.LockInfo, expected int, s
879
917
880
918
// Check locks to see if any have been committed or rolled back.
881
919
if len (locks ) < expected {
882
- logutil .BgLogger ().Debug ("addKeys: lock has been committed or rolled back" , zap .Uint64 ("commit ts" , commitTS ), zap .Uint64 ("start ts" , startTS ))
920
+ logutil .BgLogger ().Info ("addKeys: lock has been committed or rolled back" , zap .Uint64 ("commit ts" , commitTS ), zap .Uint64 ("start ts" , startTS ))
883
921
// A lock is missing - the transaction must either have been rolled back or committed.
884
922
if ! data .missingLock {
885
923
// commitTS == 0 => lock has been rolled back.
@@ -970,10 +1008,10 @@ func (lr *LockResolver) checkSecondaries(bo *retry.Backoffer, txnID uint64, curK
970
1008
}
971
1009
972
1010
// resolveAsyncResolveData resolves all locks in an async-commit transaction according to the status.
973
- func (lr * LockResolver ) resolveAsyncResolveData (bo * retry.Backoffer , l * Lock , status TxnStatus , data * asyncResolveData ) error {
1011
+ func (lr * LockResolver ) resolveAsyncResolveData (bo * retry.Backoffer , l * Lock , status TxnStatus , keys [][] byte ) error {
974
1012
util .EvalFailpoint ("resolveAsyncResolveData" )
975
1013
976
- keysByRegion , _ , err := lr .store .GetRegionCache ().GroupKeysByRegion (bo , data . keys , nil )
1014
+ keysByRegion , _ , err := lr .store .GetRegionCache ().GroupKeysByRegion (bo , keys , nil )
977
1015
if err != nil {
978
1016
return err
979
1017
}
@@ -1012,31 +1050,49 @@ func (lr *LockResolver) resolveAsyncResolveData(bo *retry.Backoffer, l *Lock, st
1012
1050
func (lr * LockResolver ) resolveAsyncCommitLock (bo * retry.Backoffer , l * Lock , status TxnStatus , asyncResolveAll bool ) (TxnStatus , error ) {
1013
1051
metrics .LockResolverCountWithResolveAsync .Inc ()
1014
1052
1015
- resolveData , err := lr .checkAllSecondaries (bo , l , & status )
1016
- if err != nil {
1017
- return TxnStatus {}, err
1018
- }
1019
- resolveData .keys = append (resolveData .keys , l .Primary )
1053
+ var toResolveKeys [][]byte
1054
+ if status .IsStatusDetermined () {
1055
+ toResolveKeys = make ([][]byte , 0 , len (status .primaryLock .Secondaries )+ 1 )
1056
+ toResolveKeys = append (toResolveKeys , status .primaryLock .Secondaries ... )
1057
+ toResolveKeys = append (toResolveKeys , l .Primary )
1058
+ } else {
1059
+ // Only do checkAllSecondaries if the transaction status is undetermined.
1060
+ // The async commit transaction is regarded as committed if `resolveData.commitTS` is not 0,
1061
+ // otherwise it is regarded as rolled back. The transaction status should be determined if the
1062
+ // `checkAllSecondaries` finishes with no errors.
1063
+ resolveData , err := lr .checkAllSecondaries (bo , l , & status )
1064
+ if err != nil {
1065
+ return TxnStatus {}, err
1066
+ }
1067
+ resolveData .keys = append (resolveData .keys , l .Primary )
1020
1068
1021
- status .commitTS = resolveData .commitTs
1022
- if status .StatusCacheable () {
1023
- lr .saveResolved (l .TxnID , status )
1069
+ status .commitTS = resolveData .commitTs
1070
+ if status .StatusCacheable () {
1071
+ lr .saveResolved (l .TxnID , status )
1072
+ }
1073
+ toResolveKeys = resolveData .keys
1024
1074
}
1025
1075
1026
- logutil .BgLogger ().Info ("resolve async commit" , zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .commitTS ))
1076
+ if _ , err := util .EvalFailpoint ("resolveAsyncCommitLockReturn" ); err == nil {
1077
+ return status , nil
1078
+ }
1079
+ logutil .BgLogger ().Info ("resolve async commit locks" , zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .commitTS ), zap .Stringer ("TxnStatus" , status ))
1027
1080
if asyncResolveAll {
1028
1081
asyncBo := retry .NewBackoffer (lr .asyncResolveCtx , asyncResolveLockMaxBackoff )
1029
1082
go func () {
1030
- err := lr .resolveAsyncResolveData (asyncBo , l , status , resolveData )
1083
+ err := lr .resolveAsyncResolveData (asyncBo , l , status , toResolveKeys )
1031
1084
if err != nil {
1032
1085
logutil .BgLogger ().Info ("failed to resolve async-commit locks asynchronously" ,
1033
1086
zap .Uint64 ("startTS" , l .TxnID ), zap .Uint64 ("commitTS" , status .CommitTS ()), zap .Error (err ))
1034
1087
}
1035
1088
}()
1036
1089
} else {
1037
- err = lr .resolveAsyncResolveData (bo , l , status , resolveData )
1090
+ err := lr .resolveAsyncResolveData (bo , l , status , toResolveKeys )
1091
+ if err != nil {
1092
+ return TxnStatus {}, err
1093
+ }
1038
1094
}
1039
- return status , err
1095
+ return status , nil
1040
1096
}
1041
1097
1042
1098
// checkAllSecondaries checks the secondary locks of an async commit transaction to find out the final
0 commit comments