// Copyright 2019 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. package session_test import ( "fmt" "time" . "github.com/pingcap/check" "github.com/pingcap/errors" "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/store/mockstore" "github.com/pingcap/tidb/store/mockstore/mocktikv" "github.com/pingcap/tidb/store/tikv" "github.com/pingcap/tidb/tablecodec" "github.com/pingcap/tidb/util/codec" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" ) var _ = Suite(&testPessimisticSuite{}) type testPessimisticSuite struct { cluster *mocktikv.Cluster mvccStore mocktikv.MVCCStore store kv.Storage dom *domain.Domain } func (s *testPessimisticSuite) SetUpSuite(c *C) { testleak.BeforeTest() config.GetGlobalConfig().PessimisticTxn.Enable = true // Set it to 300ms for testing lock resolve. tikv.PessimisticLockTTL = 300 s.cluster = mocktikv.NewCluster() mocktikv.BootstrapWithSingleStore(s.cluster) s.mvccStore = mocktikv.MustNewMVCCStore() store, err := mockstore.NewMockTikvStore( mockstore.WithCluster(s.cluster), mockstore.WithMVCCStore(s.mvccStore), ) c.Assert(err, IsNil) s.store = store session.SetSchemaLease(0) session.DisableStats4Test() s.dom, err = session.BootstrapSession(s.store) c.Assert(err, IsNil) } func (s *testPessimisticSuite) TearDownSuite(c *C) { s.dom.Close() s.store.Close() config.GetGlobalConfig().PessimisticTxn.Enable = false testleak.AfterTest(c)() } func (s *testPessimisticSuite) TestPessimisticTxn(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) // Make the name has different indent for easier read. tk1 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists pessimistic") tk.MustExec("create table pessimistic (k int, v int)") tk.MustExec("insert into pessimistic values (1, 1)") // t1 lock, t2 update, t1 update and retry statement. tk1.MustExec("begin pessimistic") tk.MustExec("update pessimistic set v = 2 where v = 1") // Update can see the change, so this statement affects 0 roews. tk1.MustExec("update pessimistic set v = 3 where v = 1") c.Assert(tk1.Se.AffectedRows(), Equals, uint64(0)) c.Assert(session.GetHistory(tk1.Se).Count(), Equals, 0) // select for update can see the change of another transaction. tk1.MustQuery("select * from pessimistic for update").Check(testkit.Rows("1 2")) // plain select can not see the change of another transaction. tk1.MustQuery("select * from pessimistic").Check(testkit.Rows("1 1")) tk1.MustExec("update pessimistic set v = 3 where v = 2") c.Assert(tk1.Se.AffectedRows(), Equals, uint64(1)) // pessimistic lock doesn't block read operation of other transactions. tk.MustQuery("select * from pessimistic").Check(testkit.Rows("1 2")) tk1.MustExec("commit") tk1.MustQuery("select * from pessimistic").Check(testkit.Rows("1 3")) // t1 lock, t1 select for update, t2 wait t1. tk1.MustExec("begin pessimistic") tk1.MustExec("select * from pessimistic where k = 1 for update") finishCh := make(chan struct{}) go func() { tk.MustExec("update pessimistic set v = 5 where k = 1") finishCh <- struct{}{} }() time.Sleep(time.Millisecond * 10) tk1.MustExec("update pessimistic set v = 3 where k = 1") tk1.MustExec("commit") <-finishCh tk.MustQuery("select * from pessimistic").Check(testkit.Rows("1 5")) } func (s *testPessimisticSuite) TestTxnMode(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tests := []struct { beginStmt string txnMode string configDefault bool isPessimistic bool }{ {"pessimistic", "pessimistic", false, true}, {"pessimistic", "pessimistic", true, true}, {"pessimistic", "optimistic", false, true}, {"pessimistic", "optimistic", true, true}, {"pessimistic", "", false, true}, {"pessimistic", "", true, true}, {"optimistic", "pessimistic", false, false}, {"optimistic", "pessimistic", true, false}, {"optimistic", "optimistic", false, false}, {"optimistic", "optimistic", true, false}, {"optimistic", "", false, false}, {"optimistic", "", true, false}, {"", "pessimistic", false, true}, {"", "pessimistic", true, true}, {"", "optimistic", false, false}, {"", "optimistic", true, false}, {"", "", false, false}, {"", "", true, true}, } for _, tt := range tests { config.GetGlobalConfig().PessimisticTxn.Default = tt.configDefault tk.MustExec(fmt.Sprintf("set @@tidb_txn_mode = '%s'", tt.txnMode)) tk.MustExec("begin " + tt.beginStmt) c.Check(tk.Se.GetSessionVars().TxnCtx.IsPessimistic, Equals, tt.isPessimistic) tk.MustExec("rollback") } tk.MustExec("set @@autocommit = 0") tk.MustExec("create table if not exists txn_mode (a int)") tests2 := []struct { txnMode string configDefault bool isPessimistic bool }{ {"pessimistic", false, true}, {"pessimistic", true, true}, {"optimistic", false, false}, {"optimistic", true, false}, {"", false, false}, {"", true, true}, } for _, tt := range tests2 { config.GetGlobalConfig().PessimisticTxn.Default = tt.configDefault tk.MustExec(fmt.Sprintf("set @@tidb_txn_mode = '%s'", tt.txnMode)) tk.MustExec("rollback") tk.MustExec("insert txn_mode values (1)") c.Check(tk.Se.GetSessionVars().TxnCtx.IsPessimistic, Equals, tt.isPessimistic) tk.MustExec("rollback") } } func (s *testPessimisticSuite) TestDeadlock(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists deadlock") tk.MustExec("create table deadlock (k int primary key, v int)") tk.MustExec("insert into deadlock values (1, 1), (2, 1)") syncCh := make(chan struct{}) go func() { tk1 := testkit.NewTestKitWithInit(c, s.store) tk1.MustExec("begin pessimistic") tk1.MustExec("update deadlock set v = v + 1 where k = 2") <-syncCh tk1.MustExec("update deadlock set v = v + 1 where k = 1") <-syncCh }() tk.MustExec("begin pessimistic") tk.MustExec("update deadlock set v = v + 1 where k = 1") syncCh <- struct{}{} time.Sleep(time.Millisecond * 10) _, err := tk.Exec("update deadlock set v = v + 1 where k = 2") e, ok := errors.Cause(err).(*terror.Error) c.Assert(ok, IsTrue) c.Assert(int(e.Code()), Equals, mysql.ErrLockDeadlock) syncCh <- struct{}{} } func (s *testPessimisticSuite) TestSingleStatementRollback(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk2 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists pessimistic") tk.MustExec("create table single_statement (id int primary key, v int)") tk.MustExec("insert into single_statement values (1, 1), (2, 1), (3, 1), (4, 1)") tblID := tk.GetTableID("single_statement") s.cluster.SplitTable(s.mvccStore, tblID, 2) region1Key := codec.EncodeBytes(nil, tablecodec.EncodeRowKeyWithHandle(tblID, 1)) region1, _ := s.cluster.GetRegionByKey(region1Key) region1ID := region1.Id region2Key := codec.EncodeBytes(nil, tablecodec.EncodeRowKeyWithHandle(tblID, 3)) region2, _ := s.cluster.GetRegionByKey(region2Key) region2ID := region2.Id syncCh := make(chan bool) go func() { tk2.MustExec("begin pessimistic") <-syncCh s.cluster.ScheduleDelay(tk2.Se.GetSessionVars().TxnCtx.StartTS, region2ID, time.Millisecond*3) tk2.MustExec("update single_statement set v = v + 1") tk2.MustExec("commit") <-syncCh }() tk.MustExec("begin pessimistic") syncCh <- true s.cluster.ScheduleDelay(tk.Se.GetSessionVars().TxnCtx.StartTS, region1ID, time.Millisecond*3) tk.MustExec("update single_statement set v = v + 1") tk.MustExec("commit") syncCh <- true } func (s *testPessimisticSuite) TestFirstStatementFail(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists first") tk.MustExec("create table first (k int unique)") tk.MustExec("insert first values (1)") tk.MustExec("begin pessimistic") _, err := tk.Exec("insert first values (1)") c.Assert(err, NotNil) tk.MustExec("insert first values (2)") tk.MustExec("commit") } func (s *testPessimisticSuite) TestKeyExistsCheck(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists chk") tk.MustExec("create table chk (k int primary key)") tk.MustExec("insert chk values (1)") tk.MustExec("delete from chk where k = 1") tk.MustExec("begin pessimistic") tk.MustExec("insert chk values (1)") tk.MustExec("commit") tk1 := testkit.NewTestKitWithInit(c, s.store) tk1.MustExec("begin optimistic") tk1.MustExec("insert chk values (1), (2), (3)") _, err := tk1.Exec("commit") c.Assert(err, NotNil) tk.MustExec("begin pessimistic") tk.MustExec("insert chk values (2)") tk.MustExec("commit") } func (s *testPessimisticSuite) TestInsertOnDup(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk2 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists dup") tk.MustExec("create table dup (id int primary key, c int)") tk.MustExec("begin pessimistic") tk2.MustExec("insert dup values (1, 1)") tk.MustExec("insert dup values (1, 1) on duplicate key update c = c + 1") tk.MustExec("commit") tk.MustQuery("select * from dup").Check(testkit.Rows("1 2")) } func (s *testPessimisticSuite) TestPointGetKeyLock(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk2 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists point") tk.MustExec("create table point (id int primary key, u int unique, c int)") syncCh := make(chan struct{}) tk.MustExec("begin pessimistic") tk.MustExec("update point set c = c + 1 where id = 1") tk.MustExec("delete from point where u = 2") go func() { tk2.MustExec("begin pessimistic") _, err1 := tk2.Exec("insert point values (1, 1, 1)") c.Check(kv.ErrKeyExists.Equal(err1), IsTrue) _, err1 = tk2.Exec("insert point values (2, 2, 2)") c.Check(kv.ErrKeyExists.Equal(err1), IsTrue) tk2.MustExec("rollback") <-syncCh }() time.Sleep(time.Millisecond * 10) tk.MustExec("insert point values (1, 1, 1)") tk.MustExec("insert point values (2, 2, 2)") tk.MustExec("commit") syncCh <- struct{}{} tk.MustExec("begin pessimistic") tk.MustExec("select * from point where id = 3 for update") tk.MustExec("select * from point where u = 4 for update") go func() { tk2.MustExec("begin pessimistic") _, err1 := tk2.Exec("insert point values (3, 3, 3)") c.Check(kv.ErrKeyExists.Equal(err1), IsTrue) _, err1 = tk2.Exec("insert point values (4, 4, 4)") c.Check(kv.ErrKeyExists.Equal(err1), IsTrue) tk2.MustExec("rollback") <-syncCh }() time.Sleep(time.Millisecond * 10) tk.MustExec("insert point values (3, 3, 3)") tk.MustExec("insert point values (4, 4, 4)") tk.MustExec("commit") syncCh <- struct{}{} } func (s *testPessimisticSuite) TestBankTransfer(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk2 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists accounts") tk.MustExec("create table accounts (id int primary key, c int)") tk.MustExec("insert accounts values (1, 100), (2, 100), (3, 100)") syncCh := make(chan struct{}) tk.MustExec("begin pessimistic") tk.MustQuery("select * from accounts where id = 1 for update").Check(testkit.Rows("1 100")) go func() { tk2.MustExec("begin pessimistic") tk2.MustExec("select * from accounts where id = 2 for update") <-syncCh tk2.MustExec("select * from accounts where id = 3 for update") tk2.MustExec("update accounts set c = 50 where id = 2") tk2.MustExec("update accounts set c = 150 where id = 3") tk2.MustExec("commit") <-syncCh }() syncCh <- struct{}{} tk.MustQuery("select * from accounts where id = 2 for update").Check(testkit.Rows("2 50")) tk.MustExec("update accounts set c = 50 where id = 1") tk.MustExec("update accounts set c = 100 where id = 2") tk.MustExec("commit") syncCh <- struct{}{} tk.MustQuery("select sum(c) from accounts").Check(testkit.Rows("300")) } func (s *testPessimisticSuite) TestOptimisticConflicts(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk2 := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists conflict") tk.MustExec("create table conflict (id int primary key, c int)") tk.MustExec("insert conflict values (1, 1)") tk.MustExec("begin pessimistic") tk.MustQuery("select * from conflict where id = 1 for update") syncCh := make(chan struct{}) go func() { tk2.MustExec("update conflict set c = 3 where id = 1") <-syncCh }() time.Sleep(time.Millisecond * 10) tk.MustExec("update conflict set c = 2 where id = 1") tk.MustExec("commit") syncCh <- struct{}{} tk.MustQuery("select c from conflict where id = 1").Check(testkit.Rows("3")) // Check outdated pessimistic lock is resolved. tk.MustExec("begin pessimistic") tk.MustExec("update conflict set c = 4 where id = 1") time.Sleep(300 * time.Millisecond) tk2.MustExec("begin optimistic") tk2.MustExec("update conflict set c = 5 where id = 1") tk2.MustExec("commit") _, err := tk.Exec("commit") c.Check(err, NotNil) }