-
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
executor: support innodb_lock_wait_timeout for pessimistic transaction #13103
Changes from 1 commit
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 |
---|---|---|
|
@@ -567,3 +567,91 @@ func (s *testPessimisticSuite) TestKillStopTTLManager(c *C) { | |
// This query should success rather than returning a ResolveLock error. | ||
tk2.MustExec("update test_kill set c = c + 1 where id = 1") | ||
} | ||
|
||
func (s *testPessimisticSuite) TestInnodbLockWaitTimeout(c *C) { | ||
tk := testkit.NewTestKitWithInit(c, s.store) | ||
tk.MustExec("drop table if exists tk") | ||
tk.MustExec("create table tk (c1 int primary key, c2 int)") | ||
tk.MustExec("insert into tk values(1,1),(2,2),(3,3),(4,4),(5,5)") | ||
// tk set global | ||
tk.MustExec("set global innodb_lock_wait_timeout = 3") | ||
tk.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 50")) | ||
|
||
tk2 := testkit.NewTestKitWithInit(c, s.store) | ||
tk2.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 3")) | ||
tk2.MustExec("set innodb_lock_wait_timeout = 2") | ||
tk2.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 2")) | ||
|
||
tk3 := testkit.NewTestKitWithInit(c, s.store) | ||
tk3.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 3")) | ||
tk3.MustExec("set innodb_lock_wait_timeout = 1") | ||
tk3.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 1")) | ||
|
||
tk2.MustExec("set @@autocommit = 0") | ||
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. why set autocommit here? 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. set this before next |
||
tk3.MustExec("set @@autocommit = 0") | ||
|
||
tk4 := testkit.NewTestKitWithInit(c, s.store) | ||
tk4.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 3")) | ||
tk4.MustExec("set @@autocommit = 0") | ||
|
||
// tk2 lock c1 = 1 | ||
tk2.MustExec("begin pessimistic") | ||
tk2.MustExec("select * from tk where c1 = 1 for update") // lock succ c1 = 1 | ||
|
||
// tk3 try lock c1 = 1 timeout 1sec | ||
tk3.MustExec("begin pessimistic") | ||
start := time.Now() | ||
_, err := tk3.Exec("select * from tk where c1 = 1 for update") | ||
c.Check(time.Since(start), GreaterEqual, time.Duration(1000*time.Millisecond)) | ||
c.Check(time.Since(start), LessEqual, time.Duration(1100*time.Millisecond)) // unit test diff should not be too big | ||
c.Check(err.Error(), Equals, tikv.ErrLockWaitTimeout.Error()) | ||
|
||
tk4.MustExec("begin pessimistic") | ||
tk4.MustExec("update tk set c2 = c2 + 1 where c1 = 2") // lock succ c1 = 2 by update | ||
start = time.Now() | ||
_, err = tk2.Exec("update tk set c2 = c2 - 1 where c1 = 2") | ||
c.Check(time.Since(start), GreaterEqual, time.Duration(2000*time.Millisecond)) | ||
c.Check(time.Since(start), LessEqual, time.Duration(2100*time.Millisecond)) // unit test diff should not be too big | ||
c.Check(err.Error(), Equals, tikv.ErrLockWaitTimeout.Error()) | ||
|
||
tk2.MustExec("set innodb_lock_wait_timeout = 1") | ||
tk2.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 1")) | ||
start = time.Now() | ||
_, err = tk2.Exec("delete from tk where c1 = 2") | ||
c.Check(time.Since(start), GreaterEqual, time.Duration(1000*time.Millisecond)) | ||
c.Check(time.Since(start), LessEqual, time.Duration(1100*time.Millisecond)) // unit test diff should not be too big | ||
c.Check(err.Error(), Equals, tikv.ErrLockWaitTimeout.Error()) | ||
|
||
tk2.MustExec("commit") | ||
tk3.MustExec("commit") | ||
tk4.MustExec("commit") | ||
|
||
tk.MustQuery(`show variables like "innodb_lock_wait_timeout"`).Check(testkit.Rows("innodb_lock_wait_timeout 50")) | ||
tk.MustQuery(`select * from tk where c1 = 2`).Check(testkit.Rows("2 3")) // tk4 update commit work, tk2 delete should be rollbacked | ||
|
||
// test stmtRollBack caused by timeout but not the whole transaction | ||
tk2.MustExec("begin pessimistic") | ||
tk2.MustExec("update tk set c2 = c2 + 2 where c1 = 2") // tk2 lock succ c1 = 2 by update | ||
tk2.MustQuery(`select * from tk where c1 = 2`).Check(testkit.Rows("2 5")) // tk2 update c2 succ | ||
|
||
tk3.MustExec("begin pessimistic") | ||
tk3.MustExec("select * from tk where c1 = 3 for update") // tk3 lock c1 = 3 succ | ||
|
||
start = time.Now() | ||
_, err = tk2.Exec("delete from tk where c1 = 3") // tk2 tries to lock c1 = 3 fail, this delete should be rollback, but previous update should be keeped | ||
c.Check(time.Since(start), GreaterEqual, time.Duration(1000*time.Millisecond)) | ||
c.Check(time.Since(start), LessEqual, time.Duration(1100*time.Millisecond)) // unit test diff should not be too big | ||
c.Check(err.Error(), Equals, tikv.ErrLockWaitTimeout.Error()) | ||
|
||
tk2.MustExec("commit") | ||
tk3.MustExec("commit") | ||
|
||
tk.MustQuery(`select * from tk where c1 = 1`).Check(testkit.Rows("1 1")) | ||
tk.MustQuery(`select * from tk where c1 = 2`).Check(testkit.Rows("2 5")) // tk2 update succ | ||
tk.MustQuery(`select * from tk where c1 = 3`).Check(testkit.Rows("3 3")) // tk2 delete should fail | ||
tk.MustQuery(`select * from tk where c1 = 4`).Check(testkit.Rows("4 4")) | ||
tk.MustQuery(`select * from tk where c1 = 5`).Check(testkit.Rows("5 5")) | ||
|
||
// clean | ||
tk.MustExec("drop table if exists tk") | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -447,6 +447,9 @@ type SessionVars struct { | |||||
isolationReadEngines map[kv.StoreType]struct{} | ||||||
|
||||||
PlannerSelectBlockAsName []ast.HintTable | ||||||
|
||||||
// Lock wait timeout for pessimistic transaction in milliseconds, `innodb_lock_wait_timeout` is in seconds | ||||||
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. put 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.
Suggested change
|
||||||
LockWaitTimeout int64 | ||||||
} | ||||||
|
||||||
// PreparedParams contains the parameters of the current prepared statement when executing it. | ||||||
|
@@ -524,6 +527,7 @@ func NewSessionVars() *SessionVars { | |||||
AllowRemoveAutoInc: DefTiDBAllowRemoveAutoInc, | ||||||
UsePlanBaselines: DefTiDBUsePlanBaselines, | ||||||
isolationReadEngines: map[kv.StoreType]struct{}{kv.TiKV: {}, kv.TiFlash: {}}, | ||||||
LockWaitTimeout: DefInnodbLockWaitTimeout * 1000, | ||||||
} | ||||||
vars.Concurrency = Concurrency{ | ||||||
IndexLookupConcurrency: DefIndexLookupConcurrency, | ||||||
|
@@ -812,6 +816,9 @@ func (s *SessionVars) SetSystemVar(name string, val string) error { | |||||
case MaxExecutionTime: | ||||||
timeoutMS := tidbOptPositiveInt32(val, 0) | ||||||
s.MaxExecutionTime = uint64(timeoutMS) | ||||||
case InnodbLockWaitTimeout: | ||||||
lockWaitSec := tidbOptInt64(val, DefInnodbLockWaitTimeout) | ||||||
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. We need to validate the range. 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 is done in function
|
||||||
s.LockWaitTimeout = int64(lockWaitSec * 1000) | ||||||
case TiDBSkipUTF8Check: | ||||||
s.SkipUTF8Check = TiDBOptOn(val) | ||||||
case TiDBOptAggPushDown: | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -676,6 +676,7 @@ func (action actionPessimisticLock) handleSingleBatch(c *twoPhaseCommitter, bo * | |||||
IsFirstLock: c.isFirstLock, | ||||||
WaitTimeout: action.lockWaitTime, | ||||||
}, pb.Context{Priority: c.priority, SyncLog: c.syncLog}) | ||||||
lockWaitStartTime := time.Now() | ||||||
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. We need to update the 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. roger |
||||||
resp, err := c.store.SendReq(bo, req, batch.region, readTimeoutShort) | ||||||
if err != nil { | ||||||
|
@@ -726,15 +727,28 @@ func (action actionPessimisticLock) handleSingleBatch(c *twoPhaseCommitter, bo * | |||||
// if the lock left behind whose related txn is already committed or rollbacked, | ||||||
// (eg secondary locks not committed or rollbacked yet) | ||||||
// we cant return "nowait conflict" directly | ||||||
if action.lockWaitTime == kv.LockNoWait && lock.LockType == pb.Op_PessimisticLock { | ||||||
// the pessimistic lock found could be lock left behind(timeout but not recycled yet) | ||||||
if !c.store.oracle.IsExpired(lock.TxnID, lock.TTL) { | ||||||
return ErrLockAcquireFailAndNoWaitSet | ||||||
if lock.LockType == pb.Op_PessimisticLock { | ||||||
if action.lockWaitTime == kv.LockNoWait { | ||||||
// the pessimistic lock found could be lock left behind(timeout but not recycled yet) | ||||||
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. It is better to improve the English of this comment. |
||||||
if !c.store.oracle.IsExpired(lock.TxnID, lock.TTL) { | ||||||
return ErrLockAcquireFailAndNoWaitSet | ||||||
} | ||||||
} else if action.lockWaitTime == kv.LockAlwaysWait { | ||||||
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. We don't need to handle 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. @coocood some inner usages which calls 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. How about
Suggested change
then do something. 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. current we have 3 paths(negative, 0, positive value), I think it's more clear to make them like noodle style, and we just need to add/remove one more |
||||||
// do nothing but keep wait | ||||||
} else { | ||||||
// user has set the `InnodbLockWaitTimeout`, check timeout | ||||||
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. It is better to improve the English of this comment. |
||||||
// the pessimistic lock found could be lock left behind(timeout but not recycled yet) | ||||||
if !c.store.oracle.IsExpired(lock.TxnID, lock.TTL) { | ||||||
if time.Since(lockWaitStartTime).Milliseconds() >= action.lockWaitTime { | ||||||
return ErrLockWaitTimeout | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
locks = append(locks, lock) | ||||||
} | ||||||
// Because we already waited on tikv, no need to Backoff here. | ||||||
// tikv default will wait 3s(also the maximum wait value) when lock error occurs | ||||||
_, err = c.store.lockResolver.ResolveLocks(bo, c.startTS, locks) | ||||||
if err != nil { | ||||||
return errors.Trace(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.