385 lines
13 KiB
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)
|
|
}
|