-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
store: upgrade the CheckTxnStatus API #13123
Changes from 7 commits
6181920
576c051
c0285ba
e7aad0d
8da07bc
8106b33
386ca77
bba8d04
fbc600f
5fb661a
bbd42e3
e77e17b
64721b2
c01bc0a
d4e192a
0f3ae0f
913e29d
1aa8f9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -882,7 +882,7 @@ func rollbackKey(db *leveldb.DB, batch *leveldb.Batch, key []byte, startTS uint6 | |
} | ||
// If current transaction's lock exist. | ||
if ok && dec.lock.startTS == startTS { | ||
if err = rollbackLock(batch, dec.lock, key, startTS); err != nil { | ||
if err = rollbackLock(batch, key, startTS); err != nil { | ||
return errors.Trace(err) | ||
} | ||
return nil | ||
|
@@ -919,7 +919,7 @@ func rollbackKey(db *leveldb.DB, batch *leveldb.Batch, key []byte, startTS uint6 | |
return nil | ||
} | ||
|
||
func rollbackLock(batch *leveldb.Batch, lock mvccLock, key []byte, startTS uint64) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This parameter is not used in the function, so I remove it |
||
func rollbackLock(batch *leveldb.Batch, key []byte, startTS uint64) error { | ||
tomb := mvccValue{ | ||
valueType: typeRollback, | ||
startTS: startTS, | ||
|
@@ -980,7 +980,7 @@ func (mvcc *MVCCLevelDB) Cleanup(key []byte, startTS, currentTS uint64) error { | |
if ok && dec.lock.startTS == startTS { | ||
// If the lock has already outdated, clean up it. | ||
if currentTS == 0 || uint64(oracle.ExtractPhysical(dec.lock.startTS))+dec.lock.ttl < uint64(oracle.ExtractPhysical(currentTS)) { | ||
if err = rollbackLock(batch, dec.lock, key, startTS); err != nil { | ||
if err = rollbackLock(batch, key, startTS); err != nil { | ||
return err | ||
} | ||
return mvcc.db.Write(batch, nil) | ||
|
@@ -1032,7 +1032,7 @@ func (mvcc *MVCCLevelDB) Cleanup(key []byte, startTS, currentTS uint64) error { | |
// primaryKey + lockTS together could locate the primary lock. | ||
// callerStartTS is the start ts of reader transaction. | ||
// currentTS is the current ts, but it may be inaccurate. Just use it to check TTL. | ||
func (mvcc *MVCCLevelDB) CheckTxnStatus(primaryKey []byte, lockTS, callerStartTS, currentTS uint64) (uint64, uint64, error) { | ||
func (mvcc *MVCCLevelDB) CheckTxnStatus(primaryKey []byte, lockTS, callerStartTS, currentTS uint64, rollbackIfNotExist bool) (uint64, uint64, error) { | ||
mvcc.mu.Lock() | ||
defer mvcc.mu.Unlock() | ||
|
||
|
@@ -1057,7 +1057,7 @@ func (mvcc *MVCCLevelDB) CheckTxnStatus(primaryKey []byte, lockTS, callerStartTS | |
|
||
// If the lock has already outdated, clean up it. | ||
if uint64(oracle.ExtractPhysical(lock.startTS))+lock.ttl < uint64(oracle.ExtractPhysical(currentTS)) { | ||
if err = rollbackLock(batch, lock, primaryKey, lockTS); err != nil { | ||
if err = rollbackLock(batch, primaryKey, lockTS); err != nil { | ||
return 0, 0, errors.Trace(err) | ||
} | ||
if err = mvcc.db.Write(batch, nil); err != nil { | ||
|
@@ -1112,9 +1112,27 @@ func (mvcc *MVCCLevelDB) CheckTxnStatus(primaryKey []byte, lockTS, callerStartTS | |
} | ||
|
||
// If current transaction is not prewritted before, it may be pessimistic lock. | ||
// When pessimistic lock rollback, it may not leave a 'rollbacked' tombstone. | ||
logutil.BgLogger().Debug("CheckTxnStatus can't find the primary lock, pessimistic rollback?") | ||
return 0, 0, nil | ||
// When pessimistic txn rollback statement, it may not leave a 'rollbacked' tombstone. | ||
|
||
// Or maybe caused by concurrent prewrite operation. | ||
// Especially in the non-block reading case, the secondary lock is likely to be | ||
// written before the primary lock. | ||
|
||
if rollbackIfNotExist { | ||
batch := &leveldb.Batch{} | ||
if err := rollbackLock(batch, primaryKey, lockTS); err != nil { | ||
return 0, 0, errors.Trace(err) | ||
} | ||
if err := mvcc.db.Write(batch, nil); err != nil { | ||
return 0, 0, errors.Trace(err) | ||
} | ||
return 0, 0, nil | ||
} | ||
|
||
return 0, 0, &ErrTxnNotFound{kvrpcpb.TxnNotFound{ | ||
StartTs: lockTS, | ||
PrimaryKey: primaryKey, | ||
}} | ||
} | ||
|
||
// TxnHeartBeat implements the MVCCStore interface. | ||
|
@@ -1220,7 +1238,7 @@ func (mvcc *MVCCLevelDB) ResolveLock(startKey, endKey []byte, startTS, commitTS | |
if commitTS > 0 { | ||
err = commitLock(batch, dec.lock, currKey, startTS, commitTS) | ||
} else { | ||
err = rollbackLock(batch, dec.lock, currKey, startTS) | ||
err = rollbackLock(batch, currKey, startTS) | ||
} | ||
if err != nil { | ||
return errors.Trace(err) | ||
|
@@ -1260,7 +1278,7 @@ func (mvcc *MVCCLevelDB) BatchResolveLock(startKey, endKey []byte, txnInfos map[ | |
if commitTS > 0 { | ||
err = commitLock(batch, dec.lock, currKey, dec.lock.startTS, commitTS) | ||
} else { | ||
err = rollbackLock(batch, dec.lock, currKey, dec.lock.startTS) | ||
err = rollbackLock(batch, currKey, dec.lock.startTS) | ||
} | ||
if err != nil { | ||
return errors.Trace(err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -204,7 +204,7 @@ func (lr *LockResolver) BatchResolveLocks(bo *Backoffer, locks []*Lock, loc Regi | |
tikvLockResolverCountWithExpired.Inc() | ||
|
||
// Use currentTS = math.MaxUint64 means rollback the txn, no matter the lock is expired or not! | ||
status, err := lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, math.MaxUint64) | ||
status, err := lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, math.MaxUint64, true) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
@@ -279,20 +279,10 @@ func (lr *LockResolver) ResolveLocks(bo *Backoffer, callerStartTS uint64, locks | |
|
||
tikvLockResolverCountWithResolve.Inc() | ||
|
||
var expiredLocks []*Lock | ||
for _, l := range locks { | ||
msBeforeLockExpired := lr.store.GetOracle().UntilExpired(l.TxnID, l.TTL) | ||
if msBeforeLockExpired <= 0 { | ||
expiredLocks = append(expiredLocks, l) | ||
} else { | ||
msBeforeTxnExpired.update(int64(l.TTL)) | ||
tikvLockResolverCountWithNotExpired.Inc() | ||
} | ||
} | ||
// TxnID -> []Region, record resolved Regions. | ||
// TODO: Maybe put it in LockResolver and share by all txns. | ||
cleanTxns := make(map[uint64]map[RegionVerID]struct{}) | ||
for _, l := range expiredLocks { | ||
for _, l := range locks { | ||
status, err := lr.getTxnStatusFromLock(bo, l, callerStartTS) | ||
if err != nil { | ||
msBeforeTxnExpired.update(0) | ||
|
@@ -368,40 +358,76 @@ func (lr *LockResolver) GetTxnStatus(txnID uint64, callerStartTS uint64, primary | |
if err != nil { | ||
return status, err | ||
} | ||
return lr.getTxnStatus(bo, txnID, primary, callerStartTS, currentTS) | ||
return lr.getTxnStatus(bo, txnID, primary, callerStartTS, currentTS, true) | ||
MyonKeminta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func (lr *LockResolver) getTxnStatusFromLock(bo *Backoffer, l *Lock, callerStartTS uint64) (TxnStatus, error) { | ||
var currentTS uint64 | ||
var err error | ||
var status TxnStatus | ||
if l.TTL == 0 { | ||
// NOTE: l.TTL = 0 is a special protocol!!! | ||
// When the pessimistic txn prewrite meets locks of a txn, it should resolve the lock **unconditionally**. | ||
// In this case, TiKV use lock TTL = 0 to notify TiDB, and TiDB should resolve the lock! | ||
// Set currentTS to max uint64 to make the lock expired. | ||
currentTS = math.MaxUint64 | ||
} else { | ||
var err error | ||
currentTS, err = lr.store.GetOracle().GetLowResolutionTimestamp(bo.ctx) | ||
if err != nil { | ||
return TxnStatus{}, err | ||
} | ||
} | ||
return lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, currentTS) | ||
|
||
rollbackIfNotExist := false | ||
for { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a for loop in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the unit test easier. |
||
status, err = lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, currentTS, rollbackIfNotExist) | ||
if err == nil { | ||
return status, err | ||
} | ||
if _, ok := errors.Cause(err).(txnNotFoundErr); !ok { | ||
return status, err | ||
} | ||
|
||
// Handle txnNotFound error. | ||
time.Sleep(5 * time.Millisecond) | ||
if lr.store.GetOracle().UntilExpired(l.TxnID, l.TTL) <= 0 { | ||
rollbackIfNotExist = true | ||
} | ||
} | ||
} | ||
|
||
type txnNotFoundErr struct { | ||
*kvrpcpb.TxnNotFound | ||
} | ||
|
||
func (e txnNotFoundErr) Error() string { | ||
return e.TxnNotFound.String() | ||
} | ||
|
||
func (lr *LockResolver) getTxnStatus(bo *Backoffer, txnID uint64, primary []byte, callerStartTS, currentTS uint64) (TxnStatus, error) { | ||
// If nonBlockRead is true, the caller should handle the txnNotFoundErr. | ||
func (lr *LockResolver) getTxnStatus(bo *Backoffer, txnID uint64, primary []byte, callerStartTS, currentTS uint64, rollbackIfNotExist bool) (TxnStatus, error) { | ||
if s, ok := lr.getResolved(txnID); ok { | ||
return s, nil | ||
} | ||
|
||
tikvLockResolverCountWithQueryTxnStatus.Inc() | ||
|
||
// CheckTxnStatus would meet the following cases: | ||
// 1. LOCK | ||
// 1.1 Lock expired -- orphan lock, fail to update TTL, crash recovery etc. | ||
// 1.2 Lock TTL -- active transaction holding the lock. | ||
// 2. NO LOCK | ||
// 2.1 Txn Committed | ||
// 2.2 Txn Rollbacked -- rollback itself, rollback by others, GC tomb etc. | ||
// 2.3 No lock -- pessimistic lock rollback, concurrence prewrite. | ||
|
||
var status TxnStatus | ||
req := tikvrpc.NewRequest(tikvrpc.CmdCheckTxnStatus, &kvrpcpb.CheckTxnStatusRequest{ | ||
PrimaryKey: primary, | ||
LockTs: txnID, | ||
CallerStartTs: callerStartTS, | ||
CurrentTs: currentTS, | ||
PrimaryKey: primary, | ||
LockTs: txnID, | ||
CallerStartTs: callerStartTS, | ||
CurrentTs: currentTS, | ||
RollbackIfNotExist: rollbackIfNotExist, | ||
}) | ||
for { | ||
loc, err := lr.store.GetRegionCache().LocateKey(bo, primary) | ||
|
@@ -428,6 +454,11 @@ func (lr *LockResolver) getTxnStatus(bo *Backoffer, txnID uint64, primary []byte | |
} | ||
cmdResp := resp.Resp.(*kvrpcpb.CheckTxnStatusResponse) | ||
if keyErr := cmdResp.GetError(); keyErr != nil { | ||
txnNotFound := keyErr.GetTxnNotFound() | ||
if txnNotFound != nil { | ||
return status, txnNotFoundErr{txnNotFound} | ||
} | ||
|
||
err = errors.Errorf("unexpected err: %s, tid: %v", keyErr, txnID) | ||
logutil.BgLogger().Error("getTxnStatus error", zap.Error(err)) | ||
return status, err | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to cover the case where the last parameter is
true
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done