1248 lines
49 KiB
Go
1248 lines
49 KiB
Go
// Copyright 2021 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,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package executor_test
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
. "github.com/pingcap/check"
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/failpoint"
|
|
"github.com/pingcap/tidb/config"
|
|
"github.com/pingcap/tidb/ddl/placement"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/testkit"
|
|
"github.com/tikv/client-go/v2/oracle"
|
|
)
|
|
|
|
func (s *testStaleTxnSerialSuite) TestExactStalenessTransaction(c *C) {
|
|
testcases := []struct {
|
|
name string
|
|
preSQL string
|
|
sql string
|
|
IsStaleness bool
|
|
expectPhysicalTS int64
|
|
zone string
|
|
}{
|
|
{
|
|
name: "AsOfTimestamp",
|
|
preSQL: "begin",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP '2020-09-06 00:00:00';`,
|
|
IsStaleness: true,
|
|
expectPhysicalTS: 1599321600000,
|
|
zone: "sh",
|
|
},
|
|
{
|
|
name: "begin after AsOfTimestamp",
|
|
preSQL: `START TRANSACTION READ ONLY AS OF TIMESTAMP '2020-09-06 00:00:00';`,
|
|
sql: "begin",
|
|
IsStaleness: false,
|
|
zone: "",
|
|
},
|
|
{
|
|
name: "AsOfTimestamp with tidb_bounded_staleness",
|
|
preSQL: "begin",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP tidb_bounded_staleness('2015-09-21 00:07:01', NOW());`,
|
|
IsStaleness: true,
|
|
expectPhysicalTS: 1442765221000,
|
|
zone: "bj",
|
|
},
|
|
{
|
|
name: "begin after AsOfTimestamp with tidb_bounded_staleness",
|
|
preSQL: `START TRANSACTION READ ONLY AS OF TIMESTAMP tidb_bounded_staleness('2015-09-21 00:07:01', NOW());`,
|
|
sql: "begin",
|
|
IsStaleness: false,
|
|
zone: "",
|
|
},
|
|
}
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
for _, testcase := range testcases {
|
|
c.Log(testcase.name)
|
|
failpoint.Enable("tikvclient/injectTxnScope",
|
|
fmt.Sprintf(`return("%v")`, testcase.zone))
|
|
tk.MustExec(testcase.preSQL)
|
|
tk.MustExec(testcase.sql)
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.IsStaleness, Equals, testcase.IsStaleness)
|
|
if testcase.expectPhysicalTS > 0 {
|
|
c.Assert(oracle.ExtractPhysical(tk.Se.GetSessionVars().TxnCtx.StartTS), Equals, testcase.expectPhysicalTS)
|
|
} else if !testcase.IsStaleness {
|
|
curTS := oracle.ExtractPhysical(oracle.GoTimeToTS(time.Now()))
|
|
startTS := oracle.ExtractPhysical(tk.Se.GetSessionVars().TxnCtx.StartTS)
|
|
c.Assert(curTS-startTS, Less, time.Second.Milliseconds())
|
|
c.Assert(startTS-curTS, Less, time.Second.Milliseconds())
|
|
}
|
|
tk.MustExec("commit")
|
|
}
|
|
failpoint.Disable("tikvclient/injectTxnScope")
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestSelectAsOf(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec(`drop table if exists b`)
|
|
tk.MustExec("create table t (id int primary key);")
|
|
tk.MustExec("create table b (pid int primary key);")
|
|
defer func() {
|
|
tk.MustExec(`drop table if exists b`)
|
|
tk.MustExec(`drop table if exists t`)
|
|
}()
|
|
time.Sleep(3 * time.Second)
|
|
now := time.Now()
|
|
|
|
// test setSQL with extract timestamp
|
|
testcases1 := []struct {
|
|
setTxnSQL string
|
|
name string
|
|
sql string
|
|
expectPhysicalTS int64
|
|
// IsStaleness is auto cleanup in select stmt.
|
|
errorStr string
|
|
}{
|
|
{
|
|
name: "set transaction as of",
|
|
setTxnSQL: fmt.Sprintf("set transaction read only as of timestamp '%s';", now.Format("2006-1-2 15:04:05")),
|
|
sql: "select * from t;",
|
|
expectPhysicalTS: now.Unix(),
|
|
},
|
|
{
|
|
name: "set transaction as of, expect error",
|
|
setTxnSQL: fmt.Sprintf("set transaction read only as of timestamp '%s';", now.Format("2006-1-2 15:04:05")),
|
|
sql: fmt.Sprintf("select * from t as of timestamp '%s';", now.Format("2006-1-2 15:04:05")),
|
|
errorStr: ".*can't use select as of while already set transaction as of.*",
|
|
},
|
|
{
|
|
name: "TimestampExactRead1",
|
|
sql: fmt.Sprintf("select * from t as of timestamp '%s';", now.Format("2006-1-2 15:04:05")),
|
|
expectPhysicalTS: now.Unix(),
|
|
},
|
|
{
|
|
name: "TimestampExactRead2",
|
|
sql: fmt.Sprintf("select * from t as of timestamp TIMESTAMP('%s');", now.Format("2006-1-2 15:04:05")),
|
|
expectPhysicalTS: now.Unix(),
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases1 {
|
|
c.Log(testcase.name)
|
|
if len(testcase.setTxnSQL) > 0 {
|
|
tk.MustExec(testcase.setTxnSQL)
|
|
}
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/executor/assertStaleTSO", fmt.Sprintf(`return(%d)`, testcase.expectPhysicalTS)), IsNil)
|
|
rs, err := tk.Exec(testcase.sql)
|
|
if len(testcase.errorStr) != 0 {
|
|
c.Assert(err, ErrorMatches, testcase.errorStr)
|
|
continue
|
|
}
|
|
c.Assert(err, IsNil, Commentf("sql:%s, error stack %v", testcase.sql, errors.ErrorStack(err)))
|
|
if rs != nil {
|
|
rs.Close()
|
|
}
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/executor/assertStaleTSO"), IsNil)
|
|
if len(testcase.setTxnSQL) > 0 {
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
}
|
|
}
|
|
|
|
// test stale sql by calculating with NOW() function
|
|
testcases2 := []struct {
|
|
name string
|
|
sql string
|
|
preSec int64
|
|
errorStr string
|
|
}{
|
|
{
|
|
name: "TimestampExactRead3",
|
|
sql: `select * from t as of timestamp NOW() - INTERVAL 2 SECOND;`,
|
|
preSec: 2,
|
|
},
|
|
{
|
|
name: "TimestampExactRead4",
|
|
sql: `select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 2 SECOND);`,
|
|
preSec: 2,
|
|
},
|
|
{
|
|
name: "TimestampExactRead5",
|
|
sql: `select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND), b as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND);`,
|
|
preSec: 1,
|
|
},
|
|
{
|
|
name: "TimestampExactRead6",
|
|
sql: `select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND), b as of timestamp TIMESTAMP('2020-09-06 00:00:00');`,
|
|
errorStr: ".*can not set different time in the as of.*",
|
|
},
|
|
{
|
|
name: "TimestampExactRead7",
|
|
sql: `select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND), b;`,
|
|
errorStr: ".*can not set different time in the as of.*",
|
|
},
|
|
{
|
|
name: "TimestampExactRead8",
|
|
sql: `select * from t, b as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND);`,
|
|
errorStr: ".*can not set different time in the as of.*",
|
|
},
|
|
{
|
|
name: "TimestampExactRead9",
|
|
sql: `select * from (select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND), b as of timestamp TIMESTAMP(NOW() - INTERVAL 1 SECOND)) as c, b;`,
|
|
errorStr: ".*can not set different time in the as of.*",
|
|
},
|
|
{
|
|
name: "TimestampExactRead10",
|
|
sql: `select * from (select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 2 SECOND), b as of timestamp TIMESTAMP(NOW() - INTERVAL 2 SECOND)) as c;`,
|
|
preSec: 2,
|
|
},
|
|
// Cannot be supported the SubSelect
|
|
{
|
|
name: "TimestampExactRead11",
|
|
sql: `select * from (select * from t as of timestamp TIMESTAMP(NOW() - INTERVAL 20 SECOND), b as of timestamp TIMESTAMP(NOW() - INTERVAL 20 SECOND)) as c as of timestamp Now();`,
|
|
errorStr: ".*You have an error in your SQL syntax.*",
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases2 {
|
|
c.Log(testcase.name)
|
|
if testcase.preSec > 0 {
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectNow", fmt.Sprintf(`return(%d)`, now.Unix())), IsNil)
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/executor/assertStaleTSO", fmt.Sprintf(`return(%d)`, now.Unix()-testcase.preSec)), IsNil)
|
|
}
|
|
rs, err := tk.Exec(testcase.sql)
|
|
if len(testcase.errorStr) != 0 {
|
|
c.Assert(err, ErrorMatches, testcase.errorStr)
|
|
continue
|
|
}
|
|
c.Assert(err, IsNil, Commentf("sql:%s, error stack %v", testcase.sql, errors.ErrorStack(err)))
|
|
if rs != nil {
|
|
rs.Close()
|
|
}
|
|
if testcase.preSec > 0 {
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/expression/injectNow"), IsNil)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/executor/assertStaleTSO"), IsNil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleReadKVRequest(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec(`drop table if exists t1`)
|
|
tk.MustExec(`drop table if exists t2`)
|
|
tk.MustExec("create table t (id int primary key);")
|
|
tk.MustExec(`create table t1 (c int primary key, d int,e int,index idx_d(d),index idx_e(e))`)
|
|
defer tk.MustExec(`drop table if exists t`)
|
|
defer tk.MustExec(`drop table if exists t1`)
|
|
conf := *config.GetGlobalConfig()
|
|
oldConf := conf
|
|
defer config.StoreGlobalConfig(&oldConf)
|
|
conf.Labels = map[string]string{
|
|
placement.DCLabelKey: "sh",
|
|
}
|
|
config.StoreGlobalConfig(&conf)
|
|
testcases := []struct {
|
|
name string
|
|
sql string
|
|
assert string
|
|
}{
|
|
{
|
|
name: "coprocessor read",
|
|
sql: "select * from t",
|
|
assert: "github.com/pingcap/tidb/distsql/assertRequestBuilderReplicaOption",
|
|
},
|
|
{
|
|
name: "point get read",
|
|
sql: "select * from t where id = 1",
|
|
assert: "github.com/pingcap/tidb/executor/assertPointReplicaOption",
|
|
},
|
|
{
|
|
name: "batch point get read",
|
|
sql: "select * from t where id in (1,2,3)",
|
|
assert: "github.com/pingcap/tidb/executor/assertBatchPointReplicaOption",
|
|
},
|
|
}
|
|
tk.MustExec("set @@tidb_replica_read='closest-replicas'")
|
|
for _, testcase := range testcases {
|
|
failpoint.Enable(testcase.assert, `return("sh")`)
|
|
tk.MustExec(`START TRANSACTION READ ONLY AS OF TIMESTAMP NOW(3);`)
|
|
tk.MustQuery(testcase.sql)
|
|
tk.MustExec(`commit`)
|
|
failpoint.Disable(testcase.assert)
|
|
}
|
|
for _, testcase := range testcases {
|
|
failpoint.Enable(testcase.assert, `return("sh")`)
|
|
tk.MustExec(`SET TRANSACTION READ ONLY AS OF TIMESTAMP NOW(3)`)
|
|
tk.MustExec(`begin;`)
|
|
tk.MustQuery(testcase.sql)
|
|
tk.MustExec(`commit`)
|
|
failpoint.Disable(testcase.assert)
|
|
}
|
|
// assert follower read closest read
|
|
for _, testcase := range testcases {
|
|
failpoint.Enable(testcase.assert, `return("sh")`)
|
|
tk.MustQuery(testcase.sql)
|
|
failpoint.Disable(testcase.assert)
|
|
}
|
|
tk.MustExec(`insert into t1 (c,d,e) values (1,1,1);`)
|
|
tk.MustExec(`insert into t1 (c,d,e) values (2,3,5);`)
|
|
time.Sleep(2 * time.Second)
|
|
tsv := time.Now().Format("2006-1-2 15:04:05.000")
|
|
tk.MustExec(`insert into t1 (c,d,e) values (3,3,7);`)
|
|
tk.MustExec(`insert into t1 (c,d,e) values (4,0,5);`)
|
|
tk.MustExec(`insert into t1 (c,d,e) values (5,0,5);`)
|
|
// IndexLookUp Reader Executor
|
|
rows1 := tk.MustQuery(fmt.Sprintf("select * from t1 AS OF TIMESTAMP '%v' use index (idx_d) where c < 5 and d < 5", tsv)).Rows()
|
|
c.Assert(rows1, HasLen, 2)
|
|
// IndexMerge Reader Executor
|
|
rows2 := tk.MustQuery(fmt.Sprintf("select /*+ USE_INDEX_MERGE(t1, idx_d, idx_e) */ * from t1 AS OF TIMESTAMP '%v' where c <5 and (d =5 or e=5)", tsv)).Rows()
|
|
c.Assert(rows2, HasLen, 1)
|
|
// TableReader Executor
|
|
rows3 := tk.MustQuery(fmt.Sprintf("select * from t1 AS OF TIMESTAMP '%v' where c < 6", tsv)).Rows()
|
|
c.Assert(rows3, HasLen, 2)
|
|
// IndexReader Executor
|
|
rows4 := tk.MustQuery(fmt.Sprintf("select /*+ USE_INDEX(t1, idx_d) */ d from t1 AS OF TIMESTAMP '%v' where c < 5 and d < 1;", tsv)).Rows()
|
|
c.Assert(rows4, HasLen, 0)
|
|
// point get executor
|
|
rows5 := tk.MustQuery(fmt.Sprintf("select * from t1 AS OF TIMESTAMP '%v' where c = 3;", tsv)).Rows()
|
|
c.Assert(rows5, HasLen, 0)
|
|
rows6 := tk.MustQuery(fmt.Sprintf("select * from t1 AS OF TIMESTAMP '%v' where c in (3,4,5);", tsv)).Rows()
|
|
c.Assert(rows6, HasLen, 0)
|
|
}
|
|
|
|
func (s *testStaleTxnSuite) TestStalenessAndHistoryRead(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
|
|
time1 := time.Now()
|
|
time1TS := oracle.GoTimeToTS(time1)
|
|
schemaVer1 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int primary key);")
|
|
tk.MustExec(`drop table if exists t`)
|
|
time.Sleep(50 * time.Millisecond)
|
|
time2 := time.Now()
|
|
time2TS := oracle.GoTimeToTS(time2)
|
|
schemaVer2 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
|
|
// test set txn as of will flush/mutex tidb_snapshot
|
|
tk.MustExec(fmt.Sprintf(`set @@tidb_snapshot="%s"`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, time1TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, NotNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
tk.MustExec(fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time2.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, NotNil)
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, time2TS)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
|
|
// test tidb_snapshot will flush/mutex set txn as of
|
|
tk.MustExec(fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, time1TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, NotNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
tk.MustExec(fmt.Sprintf(`set @@tidb_snapshot="%s"`, time2.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, time2TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, NotNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
|
|
// test start txn will flush/mutex tidb_snapshot
|
|
tk.MustExec(fmt.Sprintf(`set @@tidb_snapshot="%s"`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, time1TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, NotNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
|
|
tk.MustExec(fmt.Sprintf(`START TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time2.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Equals, time2TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, IsNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
tk.MustExec("commit")
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, IsNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
|
|
// test snapshot mutex with txn
|
|
tk.MustExec(fmt.Sprintf(`START TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time2.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Equals, time2TS)
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, IsNil)
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
err := tk.ExecToErr(`set @@tidb_snapshot="2020-10-08 16:45:26";`)
|
|
c.Assert(err, ErrorMatches, ".*Transaction characteristics can't be changed while a transaction is in progress")
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotTS, Equals, uint64(0))
|
|
c.Assert(tk.Se.GetSessionVars().SnapshotInfoschema, IsNil)
|
|
tk.MustExec("commit")
|
|
|
|
// test set txn as of txn mutex with txn
|
|
tk.MustExec("START TRANSACTION")
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
err = tk.ExecToErr("SET TRANSACTION READ ONLY AS OF TIMESTAMP '2020-10-08 16:46:26'")
|
|
c.Assert(err, ErrorMatches, ".*Transaction characteristics can't be changed while a transaction is in progress")
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
tk.MustExec("commit")
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestTimeBoundedStalenessTxn(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int primary key);")
|
|
defer tk.MustExec(`drop table if exists t`)
|
|
testcases := []struct {
|
|
name string
|
|
sql string
|
|
injectSafeTS uint64
|
|
// compareWithSafeTS will be 0 if StartTS==SafeTS, -1 if StartTS < SafeTS, and +1 if StartTS > SafeTS.
|
|
compareWithSafeTS int
|
|
}{
|
|
{
|
|
name: "20 seconds ago to now, safeTS 10 secs ago",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP tidb_bounded_staleness(NOW() - INTERVAL 20 SECOND, NOW())`,
|
|
injectSafeTS: oracle.GoTimeToTS(time.Now().Add(-10 * time.Second)),
|
|
compareWithSafeTS: 0,
|
|
},
|
|
{
|
|
name: "10 seconds ago to now, safeTS 20 secs ago",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP tidb_bounded_staleness(NOW() - INTERVAL 10 SECOND, NOW())`,
|
|
injectSafeTS: oracle.GoTimeToTS(time.Now().Add(-20 * time.Second)),
|
|
compareWithSafeTS: 1,
|
|
},
|
|
{
|
|
name: "20 seconds ago to 10 seconds ago, safeTS 5 secs ago",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP tidb_bounded_staleness(NOW() - INTERVAL 20 SECOND, NOW() - INTERVAL 10 SECOND)`,
|
|
injectSafeTS: oracle.GoTimeToTS(time.Now().Add(-5 * time.Second)),
|
|
compareWithSafeTS: -1,
|
|
},
|
|
{
|
|
name: "exact timestamp 5 seconds ago, safeTS 10 secs ago",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP NOW() - INTERVAL 5 SECOND`,
|
|
injectSafeTS: oracle.GoTimeToTS(time.Now().Add(-10 * time.Second)),
|
|
compareWithSafeTS: 1,
|
|
},
|
|
{
|
|
name: "exact timestamp 10 seconds ago, safeTS 5 secs ago",
|
|
sql: `START TRANSACTION READ ONLY AS OF TIMESTAMP NOW() - INTERVAL 10 SECOND`,
|
|
injectSafeTS: oracle.GoTimeToTS(time.Now().Add(-5 * time.Second)),
|
|
compareWithSafeTS: -1,
|
|
},
|
|
}
|
|
for _, testcase := range testcases {
|
|
c.Log(testcase.name)
|
|
c.Assert(failpoint.Enable("tikvclient/injectSafeTS",
|
|
fmt.Sprintf("return(%v)", testcase.injectSafeTS)), IsNil)
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS",
|
|
fmt.Sprintf("return(%v)", testcase.injectSafeTS)), IsNil)
|
|
tk.MustExec(testcase.sql)
|
|
if testcase.compareWithSafeTS == 1 {
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Greater, testcase.injectSafeTS)
|
|
} else if testcase.compareWithSafeTS == 0 {
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Equals, testcase.injectSafeTS)
|
|
} else {
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Less, testcase.injectSafeTS)
|
|
}
|
|
tk.MustExec("commit")
|
|
}
|
|
failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")
|
|
failpoint.Disable("tikvclient/injectSafeTS")
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStalenessTransactionSchemaVer(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int primary key);")
|
|
|
|
schemaVer1 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
time1 := time.Now()
|
|
tk.MustExec("alter table t add c int")
|
|
|
|
// confirm schema changed
|
|
schemaVer2 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
c.Assert(schemaVer1, Less, schemaVer2)
|
|
|
|
// get the specific old schema
|
|
tk.MustExec(fmt.Sprintf(`START TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
|
|
// schema changed back to the newest
|
|
tk.MustExec("commit")
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
|
|
// select does not affect the infoschema
|
|
tk.MustExec(fmt.Sprintf(`SELECT * from t AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestSetTransactionReadOnlyAsOf(c *C) {
|
|
t1, err := time.Parse(types.TimeFormat, "2016-09-21 09:53:04")
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
testcases := []struct {
|
|
sql string
|
|
expectedTS uint64
|
|
injectSafeTS uint64
|
|
}{
|
|
{
|
|
sql: `SET TRANSACTION READ ONLY as of timestamp '2021-04-21 00:42:12'`,
|
|
expectedTS: 424394603102208000,
|
|
injectSafeTS: 0,
|
|
},
|
|
{
|
|
sql: `SET TRANSACTION READ ONLY as of timestamp tidb_bounded_staleness('2015-09-21 00:07:01', '2021-04-27 11:26:13')`,
|
|
expectedTS: oracle.GoTimeToTS(t1),
|
|
injectSafeTS: oracle.GoTimeToTS(t1),
|
|
},
|
|
}
|
|
for _, testcase := range testcases {
|
|
if testcase.injectSafeTS > 0 {
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS",
|
|
fmt.Sprintf("return(%v)", testcase.injectSafeTS)), IsNil)
|
|
}
|
|
tk.MustExec(testcase.sql)
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, testcase.expectedTS)
|
|
tk.MustExec("begin")
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Equals, testcase.expectedTS)
|
|
tk.MustExec("commit")
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
tk.MustExec("begin")
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.StartTS, Not(Equals), testcase.expectedTS)
|
|
tk.MustExec("commit")
|
|
|
|
failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")
|
|
}
|
|
|
|
err = tk.ExecToErr(`SET TRANSACTION READ ONLY as of timestamp tidb_bounded_staleness(invalid1, invalid2')`)
|
|
c.Assert(err, NotNil)
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
|
|
tk.MustExec(`SET TRANSACTION READ ONLY as of timestamp '2021-04-21 00:42:12'`)
|
|
err = tk.ExecToErr(`START TRANSACTION READ ONLY AS OF TIMESTAMP '2020-09-06 00:00:00'`)
|
|
c.Assert(err, NotNil)
|
|
c.Assert(err.Error(), Equals, "start transaction read only as of is forbidden after set transaction read only as of")
|
|
tk.MustExec(`SET TRANSACTION READ ONLY as of timestamp '2021-04-21 00:42:12'`)
|
|
err = tk.ExecToErr(`START TRANSACTION READ ONLY AS OF TIMESTAMP '2020-09-06 00:00:00'`)
|
|
c.Assert(err, NotNil)
|
|
c.Assert(err.Error(), Equals, "start transaction read only as of is forbidden after set transaction read only as of")
|
|
|
|
tk.MustExec("begin")
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(424394603102208000))
|
|
tk.MustExec("commit")
|
|
tk.MustExec(`START TRANSACTION READ ONLY AS OF TIMESTAMP '2020-09-06 00:00:00'`)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestValidateReadOnlyInStalenessTransaction(c *C) {
|
|
errMsg1 := ".*only support read-only statement during read-only staleness transactions.*"
|
|
errMsg2 := ".*select lock hasn't been supported in stale read yet.*"
|
|
testcases := []struct {
|
|
name string
|
|
sql string
|
|
isValidate bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "select statement",
|
|
sql: `select * from t;`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "explain statement",
|
|
sql: `explain insert into t (id) values (1);`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "explain analyze insert statement",
|
|
sql: `explain analyze insert into t (id) values (1);`,
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "explain analyze select statement",
|
|
sql: `explain analyze select * from t `,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "execute insert statement",
|
|
sql: `EXECUTE stmt1;`,
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "execute select statement",
|
|
sql: `EXECUTE stmt2;`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "show statement",
|
|
sql: `show tables;`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "set union",
|
|
sql: `SELECT 1, 2 UNION SELECT 'a', 'b';`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "insert",
|
|
sql: `insert into t (id) values (1);`,
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "delete",
|
|
sql: `delete from t where id =1`,
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "update",
|
|
sql: "update t set id =2 where id =1",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "point get",
|
|
sql: `select * from t where id = 1`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "batch point get",
|
|
sql: `select * from t where id in (1,2,3);`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "split table",
|
|
sql: `SPLIT TABLE t BETWEEN (0) AND (1000000000) REGIONS 16;`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "do statement",
|
|
sql: `DO SLEEP(1);`,
|
|
isValidate: true,
|
|
},
|
|
{
|
|
name: "select for update",
|
|
sql: "select * from t where id = 1 for update",
|
|
isValidate: false,
|
|
errMsg: errMsg2,
|
|
},
|
|
{
|
|
name: "select lock in share mode",
|
|
sql: "select * from t where id = 1 lock in share mode",
|
|
isValidate: false,
|
|
errMsg: errMsg2,
|
|
},
|
|
{
|
|
name: "select for update union statement",
|
|
sql: "select * from t for update union select * from t;",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "replace statement",
|
|
sql: "replace into t(id) values (1)",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "load data statement",
|
|
sql: "LOAD DATA LOCAL INFILE '/mn/asa.csv' INTO TABLE t FIELDS TERMINATED BY x'2c' ENCLOSED BY b'100010' LINES TERMINATED BY '\r\n' IGNORE 1 LINES (id);",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "update multi tables",
|
|
sql: "update t,t1 set t.id = 1,t1.id = 2 where t.1 = 2 and t1.id = 3;",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "delete multi tables",
|
|
sql: "delete t from t1 where t.id = t1.id",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
{
|
|
name: "insert select",
|
|
sql: "insert into t select * from t1;",
|
|
isValidate: false,
|
|
errMsg: errMsg1,
|
|
},
|
|
}
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t (id int);")
|
|
tk.MustExec("create table t1 (id int);")
|
|
tk.MustExec(`PREPARE stmt1 FROM 'insert into t(id) values (5);';`)
|
|
tk.MustExec(`PREPARE stmt2 FROM 'select * from t';`)
|
|
tk.MustExec(`set @@tidb_enable_noop_functions=1;`)
|
|
for _, testcase := range testcases {
|
|
c.Log(testcase.name)
|
|
tk.MustExec(`START TRANSACTION READ ONLY AS OF TIMESTAMP NOW(3);`)
|
|
if testcase.isValidate {
|
|
tk.MustExec(testcase.sql)
|
|
} else {
|
|
err := tk.ExecToErr(testcase.sql)
|
|
c.Assert(err, NotNil)
|
|
c.Assert(err.Error(), Matches, testcase.errMsg)
|
|
}
|
|
tk.MustExec("commit")
|
|
tk.MustExec("set transaction read only as of timestamp NOW(3);")
|
|
if testcase.isValidate {
|
|
tk.MustExec(testcase.sql)
|
|
} else {
|
|
err := tk.ExecToErr(testcase.sql)
|
|
c.Assert(err, NotNil)
|
|
c.Assert(err.Error(), Matches, testcase.errMsg)
|
|
}
|
|
// clean the status
|
|
tk.MustExec("set transaction read only as of timestamp ''")
|
|
}
|
|
}
|
|
|
|
func (s *testStaleTxnSuite) TestSpecialSQLInStalenessTxn(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
testcases := []struct {
|
|
name string
|
|
sql string
|
|
sameSession bool
|
|
}{
|
|
{
|
|
name: "ddl",
|
|
sql: "create table t (id int, b int,INDEX(b));",
|
|
sameSession: false,
|
|
},
|
|
{
|
|
name: "set global session",
|
|
sql: `SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER';`,
|
|
sameSession: true,
|
|
},
|
|
{
|
|
name: "analyze table",
|
|
sql: "analyze table t",
|
|
sameSession: true,
|
|
},
|
|
{
|
|
name: "session binding",
|
|
sql: "CREATE SESSION BINDING FOR SELECT * FROM t WHERE b = 123 USING SELECT * FROM t IGNORE INDEX (b) WHERE b = 123;",
|
|
sameSession: true,
|
|
},
|
|
{
|
|
name: "global binding",
|
|
sql: "CREATE GLOBAL BINDING FOR SELECT * FROM t WHERE b = 123 USING SELECT * FROM t IGNORE INDEX (b) WHERE b = 123;",
|
|
sameSession: true,
|
|
},
|
|
{
|
|
name: "grant statements",
|
|
sql: "GRANT ALL ON test.* TO 'newuser';",
|
|
sameSession: false,
|
|
},
|
|
{
|
|
name: "revoke statements",
|
|
sql: "REVOKE ALL ON test.* FROM 'newuser';",
|
|
sameSession: false,
|
|
},
|
|
}
|
|
tk.MustExec("CREATE USER 'newuser' IDENTIFIED BY 'mypassword';")
|
|
for _, testcase := range testcases {
|
|
comment := Commentf(testcase.name)
|
|
tk.MustExec(`START TRANSACTION READ ONLY AS OF TIMESTAMP NOW(3);`)
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.IsStaleness, Equals, true, comment)
|
|
tk.MustExec(testcase.sql)
|
|
c.Assert(tk.Se.GetSessionVars().TxnCtx.IsStaleness, Equals, testcase.sameSession, comment)
|
|
}
|
|
}
|
|
|
|
func (s *testStaleTxnSuite) TestAsOfTimestampCompatibility(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t5(id int);")
|
|
defer tk.MustExec("drop table if exists t5;")
|
|
time1 := time.Now()
|
|
testcases := []struct {
|
|
beginSQL string
|
|
sql string
|
|
}{
|
|
{
|
|
beginSQL: fmt.Sprintf("START TRANSACTION READ ONLY AS OF TIMESTAMP '%s'", time1.Format("2006-1-2 15:04:05.000")),
|
|
sql: fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
{
|
|
beginSQL: "begin",
|
|
sql: fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
{
|
|
beginSQL: "start transaction",
|
|
sql: fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
{
|
|
beginSQL: fmt.Sprintf("START TRANSACTION READ ONLY AS OF TIMESTAMP '%s'", time1.Format("2006-1-2 15:04:05.000")),
|
|
sql: fmt.Sprintf("select * from t5 as of timestamp '%s'", time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
{
|
|
beginSQL: "begin",
|
|
sql: fmt.Sprintf("select * from t5 as of timestamp '%s'", time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
{
|
|
beginSQL: "start transaction",
|
|
sql: fmt.Sprintf("select * from t5 as of timestamp '%s'", time1.Format("2006-1-2 15:04:05.000")),
|
|
},
|
|
}
|
|
for _, testcase := range testcases {
|
|
tk.MustExec(testcase.beginSQL)
|
|
err := tk.ExecToErr(testcase.sql)
|
|
c.Assert(err, ErrorMatches, ".*as of timestamp can't be set in transaction.*|.*Transaction characteristics can't be changed while a transaction is in progress")
|
|
tk.MustExec("commit")
|
|
}
|
|
tk.MustExec(`create table test.table1 (id int primary key, a int);`)
|
|
defer tk.MustExec("drop table if exists test.table1;")
|
|
time1 = time.Now()
|
|
tk.MustExec(fmt.Sprintf("explain analyze select * from test.table1 as of timestamp '%s' where id = 1;", time1.Format("2006-1-2 15:04:05.000")))
|
|
}
|
|
|
|
func (s *testStaleTxnSuite) TestSetTransactionInfoSchema(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int primary key);")
|
|
|
|
schemaVer1 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
time1 := time.Now()
|
|
tk.MustExec("alter table t add c int")
|
|
|
|
// confirm schema changed
|
|
schemaVer2 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
time2 := time.Now()
|
|
c.Assert(schemaVer1, Less, schemaVer2)
|
|
tk.MustExec(fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
tk.MustExec("select * from t;")
|
|
tk.MustExec("alter table t add d int")
|
|
schemaVer3 := tk.Se.GetInfoSchema().SchemaMetaVersion()
|
|
tk.MustExec(fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
tk.MustExec("begin;")
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer1)
|
|
tk.MustExec("commit")
|
|
tk.MustExec(fmt.Sprintf(`SET TRANSACTION READ ONLY AS OF TIMESTAMP '%s'`, time2.Format("2006-1-2 15:04:05.000")))
|
|
tk.MustExec("begin;")
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer2)
|
|
tk.MustExec("commit")
|
|
c.Assert(tk.Se.GetInfoSchema().SchemaMetaVersion(), Equals, schemaVer3)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleSelect(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int)")
|
|
|
|
tolerance := 50 * time.Millisecond
|
|
|
|
tk.MustExec("insert into t values (1)")
|
|
time.Sleep(tolerance)
|
|
time1 := time.Now()
|
|
|
|
tk.MustExec("insert into t values (2)")
|
|
time.Sleep(tolerance)
|
|
time2 := time.Now()
|
|
|
|
tk.MustExec("insert into t values (3)")
|
|
time.Sleep(tolerance)
|
|
|
|
staleRows := testkit.Rows("1")
|
|
staleSQL := fmt.Sprintf(`select * from t as of timestamp '%s'`, time1.Format("2006-1-2 15:04:05.000"))
|
|
|
|
// test normal stale select
|
|
tk.MustQuery(staleSQL).Check(staleRows)
|
|
|
|
// test stale select in txn
|
|
tk.MustExec("begin")
|
|
c.Assert(tk.ExecToErr(staleSQL), NotNil)
|
|
tk.MustExec("commit")
|
|
|
|
// test prepared stale select
|
|
tk.MustExec(fmt.Sprintf(`prepare s from "%s"`, staleSQL))
|
|
tk.MustQuery("execute s")
|
|
|
|
// test prepared stale select in txn
|
|
tk.MustExec("begin")
|
|
c.Assert(tk.ExecToErr(staleSQL), NotNil)
|
|
tk.MustExec("commit")
|
|
|
|
// test stale select in stale txn
|
|
tk.MustExec(fmt.Sprintf(`start transaction read only as of timestamp '%s'`, time2.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert(tk.ExecToErr(staleSQL), NotNil)
|
|
tk.MustExec("commit")
|
|
|
|
// test prepared stale select with schema change
|
|
tk.MustExec("alter table t add column c int")
|
|
tk.MustExec("insert into t values (4, 5)")
|
|
time.Sleep(10 * time.Millisecond)
|
|
tk.MustQuery("execute s").Check(staleRows)
|
|
tk.MustExec("alter table t add column d int")
|
|
tk.MustExec("insert into t values (4, 4, 4)")
|
|
time.Sleep(tolerance)
|
|
|
|
// test point get
|
|
time6 := time.Now()
|
|
tk.MustExec("insert into t values (5, 5, 5)")
|
|
time.Sleep(tolerance)
|
|
tk.MustQuery(fmt.Sprintf("select * from t as of timestamp '%s' where c=5", time6.Format("2006-1-2 15:04:05.000"))).Check(testkit.Rows("4 5 <nil>"))
|
|
}
|
|
|
|
func (s *testStaleTxnSuite) TestStaleReadFutureTime(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int)")
|
|
// Setting tx_read_ts to a time in the future will fail. (One day before the 2038 problem)
|
|
_, err := tk.Exec("start transaction read only as of timestamp '2038-01-18 03:14:07'")
|
|
c.Assert(err, ErrorMatches, "cannot set read timestamp to a future time")
|
|
// Transaction should not be started and read ts should not be set if check fails
|
|
c.Assert(tk.Se.GetSessionVars().InTxn(), IsFalse)
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
|
|
_, err = tk.Exec("set transaction read only as of timestamp '2038-01-18 03:14:07'")
|
|
c.Assert(err, ErrorMatches, "cannot set read timestamp to a future time")
|
|
c.Assert(tk.Se.GetSessionVars().TxnReadTS.PeakTxnReadTS(), Equals, uint64(0))
|
|
|
|
_, err = tk.Exec("select * from t as of timestamp '2038-01-18 03:14:07'")
|
|
c.Assert(err, ErrorMatches, "cannot set read timestamp to a future time")
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleReadPrepare(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int)")
|
|
time.Sleep(2 * time.Second)
|
|
conf := *config.GetGlobalConfig()
|
|
oldConf := conf
|
|
defer config.StoreGlobalConfig(&oldConf)
|
|
conf.Labels = map[string]string{
|
|
placement.DCLabelKey: "sh",
|
|
}
|
|
config.StoreGlobalConfig(&conf)
|
|
time1 := time.Now()
|
|
tso := oracle.ComposeTS(time1.Unix()*1000, 0)
|
|
time.Sleep(200 * time.Millisecond)
|
|
failpoint.Enable("github.com/pingcap/tidb/executor/assertExecutePrepareStatementStalenessOption",
|
|
fmt.Sprintf(`return("%v_%v")`, tso, "sh"))
|
|
tk.MustExec(fmt.Sprintf(`prepare p1 from "select * from t as of timestamp '%v'"`, time1.Format("2006-1-2 15:04:05")))
|
|
tk.MustExec("execute p1")
|
|
// assert execute prepared statement in stale read txn
|
|
tk.MustExec(`prepare p2 from "select * from t"`)
|
|
tk.MustExec(fmt.Sprintf("start transaction read only as of timestamp '%v'", time1.Format("2006-1-2 15:04:05")))
|
|
tk.MustExec("execute p2")
|
|
tk.MustExec("commit")
|
|
|
|
// assert execute prepared statement in stale read txn
|
|
tk.MustExec(fmt.Sprintf("set transaction read only as of timestamp '%v'", time1.Format("2006-1-2 15:04:05")))
|
|
tk.MustExec("execute p2")
|
|
failpoint.Disable("github.com/pingcap/tidb/executor/assertExecutePrepareStatementStalenessOption")
|
|
|
|
// test prepared stale select in stale txn
|
|
tk.MustExec(fmt.Sprintf(`start transaction read only as of timestamp '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert("execute p1", NotNil)
|
|
tk.MustExec("commit")
|
|
|
|
// assert execute prepared statement should be error after set transaction read only as of
|
|
tk.MustExec(fmt.Sprintf(`set transaction read only as of timestamp '%s'`, time1.Format("2006-1-2 15:04:05.000")))
|
|
c.Assert("execute p1", NotNil)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStmtCtxStaleFlag(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int)")
|
|
time.Sleep(2 * time.Second)
|
|
time1 := time.Now().Format("2006-1-2 15:04:05")
|
|
testcases := []struct {
|
|
sql string
|
|
hasStaleFlag bool
|
|
}{
|
|
// assert select as of statement
|
|
{
|
|
sql: fmt.Sprintf("select * from t as of timestamp '%v'", time1),
|
|
hasStaleFlag: true,
|
|
},
|
|
// assert select statement
|
|
{
|
|
sql: "select * from t",
|
|
hasStaleFlag: false,
|
|
},
|
|
// assert select statement in stale transaction
|
|
{
|
|
sql: fmt.Sprintf("start transaction read only as of timestamp '%v'", time1),
|
|
hasStaleFlag: false,
|
|
},
|
|
{
|
|
sql: "select * from t",
|
|
hasStaleFlag: true,
|
|
},
|
|
{
|
|
sql: "commit",
|
|
hasStaleFlag: false,
|
|
},
|
|
// assert select statement after set transaction
|
|
{
|
|
sql: fmt.Sprintf("set transaction read only as of timestamp '%v'", time1),
|
|
hasStaleFlag: false,
|
|
},
|
|
{
|
|
sql: "select * from t",
|
|
hasStaleFlag: true,
|
|
},
|
|
// assert select statement after consumed set transaction
|
|
{
|
|
sql: "select * from t",
|
|
hasStaleFlag: false,
|
|
},
|
|
// assert prepare statement with select as of statement
|
|
{
|
|
sql: fmt.Sprintf(`prepare p from 'select * from t as of timestamp "%v"'`, time1),
|
|
hasStaleFlag: false,
|
|
},
|
|
// assert execute statement with select as of statement
|
|
{
|
|
sql: "execute p",
|
|
hasStaleFlag: true,
|
|
},
|
|
// assert prepare common select statement
|
|
{
|
|
sql: "prepare p1 from 'select * from t'",
|
|
hasStaleFlag: false,
|
|
},
|
|
{
|
|
sql: "execute p1",
|
|
hasStaleFlag: false,
|
|
},
|
|
// assert execute select statement in stale transaction
|
|
{
|
|
sql: fmt.Sprintf("start transaction read only as of timestamp '%v'", time1),
|
|
hasStaleFlag: false,
|
|
},
|
|
{
|
|
sql: "execute p1",
|
|
hasStaleFlag: true,
|
|
},
|
|
{
|
|
sql: "commit",
|
|
hasStaleFlag: false,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
failpoint.Enable("github.com/pingcap/tidb/exector/assertStmtCtxIsStaleness",
|
|
fmt.Sprintf("return(%v)", testcase.hasStaleFlag))
|
|
tk.MustExec(testcase.sql)
|
|
failpoint.Disable("github.com/pingcap/tidb/exector/assertStmtCtxIsStaleness")
|
|
// assert stale read flag should be false after each statement execution
|
|
c.Assert(tk.Se.GetSessionVars().StmtCtx.IsStaleness, IsFalse)
|
|
}
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleSessionQuery(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t10 (id int);")
|
|
tk.MustExec("insert into t10 (id) values (1)")
|
|
time.Sleep(2 * time.Second)
|
|
now := time.Now()
|
|
tk.MustExec(`set @@tidb_read_staleness="-1"`)
|
|
// query will use stale read
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectNow", fmt.Sprintf(`return(%d)`, now.Unix())), IsNil)
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/executor/assertStaleTSO", fmt.Sprintf(`return(%d)`, now.Unix()-1)), IsNil)
|
|
c.Assert(tk.MustQuery("select * from t10;").Rows(), HasLen, 1)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/executor/assertStaleTSO"), IsNil)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/expression/injectNow"), IsNil)
|
|
// begin transaction won't be affected by read staleness
|
|
tk.MustExec("begin")
|
|
tk.MustExec("insert into t10(id) values (2);")
|
|
tk.MustExec("commit")
|
|
tk.MustExec("insert into t10(id) values (3);")
|
|
// query will still use staleness read
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectNow", fmt.Sprintf(`return(%d)`, now.Unix())), IsNil)
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/executor/assertStaleTSO", fmt.Sprintf(`return(%d)`, now.Unix()-1)), IsNil)
|
|
c.Assert(tk.MustQuery("select * from t10").Rows(), HasLen, 1)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/executor/assertStaleTSO"), IsNil)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/expression/injectNow"), IsNil)
|
|
// assert stale read is not exist after empty the variable
|
|
tk.MustExec(`set @@tidb_read_staleness=""`)
|
|
c.Assert(tk.MustQuery("select * from t10").Rows(), HasLen, 3)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleReadCompatibility(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
defer tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int)")
|
|
tk.MustExec("insert into t(id) values (1)")
|
|
time.Sleep(2 * time.Second)
|
|
t1 := time.Now()
|
|
tk.MustExec("insert into t(id) values (2)")
|
|
time.Sleep(2 * time.Second)
|
|
t2 := time.Now()
|
|
tk.MustExec("insert into t(id) values (3)")
|
|
// assert select as of timestamp won't work after set transaction read only as of timestamp
|
|
tk.MustExec(fmt.Sprintf("set transaction read only as of timestamp '%s';", t1.Format("2006-1-2 15:04:05")))
|
|
err := tk.ExecToErr(fmt.Sprintf("select * from t as of timestamp '%s';", t1.Format("2006-1-2 15:04:05")))
|
|
c.Assert(err, NotNil)
|
|
c.Assert(err.Error(), Matches, ".*invalid as of timestamp: can't use select as of while already set transaction as of.*")
|
|
// assert set transaction read only as of timestamp is consumed
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 3)
|
|
// enable tidb_read_staleness
|
|
tk.MustExec("set @@tidb_read_staleness='-1'")
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/expression/injectNow", fmt.Sprintf(`return(%d)`, t1.Unix())), IsNil)
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 1)
|
|
// assert select as of timestamp during tidb_read_staleness
|
|
c.Assert(tk.MustQuery(fmt.Sprintf("select * from t as of timestamp '%s'", t2.Format("2006-1-2 15:04:05"))).Rows(), HasLen, 2)
|
|
// assert set transaction as of timestamp during tidb_read_staleness
|
|
tk.MustExec(fmt.Sprintf("set transaction read only as of timestamp '%s';", t2.Format("2006-1-2 15:04:05")))
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 2)
|
|
|
|
// assert begin stale transaction during tidb_read_staleness
|
|
tk.MustExec(fmt.Sprintf("start transaction read only as of timestamp '%v'", t2.Format("2006-1-2 15:04:05")))
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 2)
|
|
tk.MustExec("commit")
|
|
|
|
// assert session query still is affected by tidb_read_staleness
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 1)
|
|
|
|
// disable tidb_read_staleness
|
|
tk.MustExec("set @@tidb_read_staleness=''")
|
|
c.Assert(tk.MustQuery("select * from t;").Rows(), HasLen, 3)
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/expression/injectNow"), IsNil)
|
|
}
|
|
|
|
func (s *testStaleTxnSerialSuite) TestStaleReadNoExtraTSORequest(c *C) {
|
|
tk := testkit.NewTestKit(c, s.store)
|
|
// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
|
|
safePointName := "tikv_gc_safe_point"
|
|
safePointValue := "20160102-15:04:05 -0700"
|
|
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
|
|
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
|
|
ON DUPLICATE KEY
|
|
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
|
|
tk.MustExec(updateSafePoint)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t (id int);")
|
|
time.Sleep(3 * time.Second)
|
|
// statement stale read
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/assertTSONotRequest", `return(true)`), IsNil)
|
|
tk.MustQuery("select * from t as of timestamp NOW() - INTERVAL 2 SECOND")
|
|
failpoint.Disable("github.com/pingcap/tidb/session/assertTSONotRequest")
|
|
|
|
// set and statement stale read
|
|
tk.MustExec("set transaction read only as of timestamp NOW() - INTERVAL 2 SECOND")
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/assertTSONotRequest", `return(true)`), IsNil)
|
|
tk.MustQuery("select * from t")
|
|
failpoint.Disable("github.com/pingcap/tidb/session/assertTSONotRequest")
|
|
|
|
// stale read transaction
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/assertTSONotRequest", `return(true)`), IsNil)
|
|
tk.MustExec("start transaction read only as of timestamp NOW() - INTERVAL 2 SECOND")
|
|
tk.MustQuery("select * from t")
|
|
tk.MustExec("commit")
|
|
failpoint.Disable("github.com/pingcap/tidb/session/assertTSONotRequest")
|
|
|
|
// set and stale read transaction
|
|
tk.MustExec("set transaction read only as of timestamp NOW() - INTERVAL 2 SECOND")
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/assertTSONotRequest", `return(true)`), IsNil)
|
|
tk.MustExec("begin")
|
|
tk.MustQuery("select * from t")
|
|
tk.MustExec("commit")
|
|
failpoint.Disable("github.com/pingcap/tidb/session/assertTSONotRequest")
|
|
|
|
// use tidb_read_staleness
|
|
tk.MustExec(`set @@tidb_read_staleness='-1'`)
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/assertTSONotRequest", `return(true)`), IsNil)
|
|
tk.MustQuery("select * from t")
|
|
failpoint.Disable("github.com/pingcap/tidb/session/assertTSONotRequest")
|
|
}
|