The basic idea of the optimization is: when a pessimistic lock request is woken up after being blocked by another lock, try to grant it the lock immediately, instead of returning to the client and let the client retry. By this way, we avoids a transaction being woken up failed to get the lock due to the lock being preempted by another transaction.
When a lock is released and a queueing pessimistic lock request is woken up, allow the latter request to resume and continue acquiring the lock (it’s very likely to succeed since we allow locking with conflict).
//
// Aggressive locking refers to the behavior that when a DML in a pessimistic transaction encounters write conflict,
// do not pessimistic-rollback them immediately; instead, keep the already-acquired locks and retry the statement.
// In this way, during retry, if it needs to acquire the same locks that was acquired in the previous execution, the
// lock RPC can be skipped. After finishing the execution, if some of the locks that were acquired in the previous
// execution but not needed in the current retried execution, they will be released.
//
// In aggressive locking state, keys locked by LockKeys will be recorded to a separated buffer. For LockKeys
// invocations that involves only one key, the pessimistic lock request will be performed in ForceLock mode
// (kvrpcpb.PessimisticLockWakeUpMode_WakeUpModeForceLock).
func (txn *KVTxn) StartAggressiveLocking() {
if txn.aggressiveLockingContext != nil {
panic(“Trying to start aggressive locking while it’s already started”)
}
txn.aggressiveLockingContext = &aggressiveLockingContext{
lastRetryUnnecessaryLocks: nil,
currentLockedKeys: make(map[string]tempLockBufferEntry),
startTime: time.Now(),
}
}
When a key is locked with conflict, the current statement becomes executing at a different snapshot. However, the statement may have already read data in the expired snapshot (as specified by the for_update_ts of the statement). In this case, we have no choice but retry the current statement with a new for_update_ts .
对于 delete 语句、update 语句,例如 delete from t where k=2; update t set a=3 where k=2;
可能由于阻塞过程中,其他事务提交了 k=2 的新数据,所以 tidb 需要重试,重新查询新的 snapshot 下所有 k=2 的 rowid,然后一一加锁。
对于存在唯一索引的表数据,
select * from t where indexKey=1 for update; 或
select * from t where id=1 for update;
不太清楚是先 seek 行数据再加 index 锁和 rowid 锁,还是先加 index 锁和 rowid 锁再 seek 行数据。
如果是前者的话,那的确需要再重试 seek 一下
如果是后者的话,好像也没必要重试
// StartFairLocking adapts the method signature of KVTxn to satisfy kv.FairLockingController.
// TODO: Update the methods’ signatures in client-go to avoid this adaptor functions.
// TODO: Rename aggressive locking in client-go to fair locking.