Files
tidb/session/pessimistic_test.go

385 lines
13 KiB
Go

// 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)
}