From b1adc61ce5c9a905a9b6a1fba52a145c2a30e955 Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Wed, 21 Dec 2022 20:40:55 +0800 Subject: [PATCH] This is an automated cherry-pick of #40080 Signed-off-by: ti-chi-bot --- executor/seqtest/prepared_test.go | 10 +- planner/core/cacheable_checker.go | 15 + planner/core/cacheable_checker_test.go | 4 +- planner/core/plan_cache_test.go | 304 +++++++++ sessiontxn/txn_rc_tso_optimize_test.go | 821 +++++++++++++++++++++++++ 5 files changed, 1148 insertions(+), 6 deletions(-) create mode 100644 planner/core/plan_cache_test.go create mode 100644 sessiontxn/txn_rc_tso_optimize_test.go diff --git a/executor/seqtest/prepared_test.go b/executor/seqtest/prepared_test.go index b8d81a5f5206c..4e57af83e9b33 100644 --- a/executor/seqtest/prepared_test.go +++ b/executor/seqtest/prepared_test.go @@ -495,14 +495,14 @@ func TestPreparedInsert(t *testing.T) { err = counter.Write(pb) require.NoError(t, err) hit := pb.GetCounter().GetValue() - require.Equal(t, float64(1), hit) + require.Equal(t, float64(0), hit) // insert-values-stmt cannot use the plan cache } tk.MustExec(`set @a=3,@b=3; execute stmt_insert using @a, @b;`) if flag { err = counter.Write(pb) require.NoError(t, err) hit := pb.GetCounter().GetValue() - require.Equal(t, float64(2), hit) + require.Equal(t, float64(0), hit) } result := tk.MustQuery("select id, c1 from prepare_test where id = ?", 1) @@ -518,21 +518,21 @@ func TestPreparedInsert(t *testing.T) { err = counter.Write(pb) require.NoError(t, err) hit := pb.GetCounter().GetValue() - require.Equal(t, float64(2), hit) + require.Equal(t, float64(0), hit) } tk.MustExec(`set @a=2; execute stmt_insert_select using @a;`) if flag { err = counter.Write(pb) require.NoError(t, err) hit := pb.GetCounter().GetValue() - require.Equal(t, float64(3), hit) + require.Equal(t, float64(1), hit) } tk.MustExec(`set @a=3; execute stmt_insert_select using @a;`) if flag { err = counter.Write(pb) require.NoError(t, err) hit := pb.GetCounter().GetValue() - require.Equal(t, float64(4), hit) + require.Equal(t, float64(2), hit) } result = tk.MustQuery("select id, c1 from prepare_test where id = ?", 101) diff --git a/planner/core/cacheable_checker.go b/planner/core/cacheable_checker.go index 90c0fcc3bbd83..f8c26e445d75c 100644 --- a/planner/core/cacheable_checker.go +++ b/planner/core/cacheable_checker.go @@ -87,6 +87,21 @@ func (checker *cacheableChecker) Enter(in ast.Node) (out ast.Node, skipChildren return in, true } } + case *ast.InsertStmt: + if node.Select == nil { + // do not cache insert-values-stmt like 'insert into t values (...)' since + // no performance benefit and to save memory. + checker.cacheable = false + checker.reason = "ignore insert-values-stmt" + return in, true + } + for _, hints := range node.TableHints { + if hints.HintName.L == HintIgnorePlanCache { + checker.cacheable = false + checker.reason = "ignore plan cache by hint" + return in, true + } + } case *ast.VariableExpr, *ast.ExistsSubqueryExpr, *ast.SubqueryExpr: checker.cacheable = false return in, true diff --git a/planner/core/cacheable_checker_test.go b/planner/core/cacheable_checker_test.go index d4802db30f183..075cfc69ce6e9 100644 --- a/planner/core/cacheable_checker_test.go +++ b/planner/core/cacheable_checker_test.go @@ -52,7 +52,9 @@ func TestCacheable(t *testing.T) { tableRefsClause := &ast.TableRefsClause{TableRefs: &ast.Join{Left: &ast.TableSource{Source: tbl}}} // test InsertStmt - stmt = &ast.InsertStmt{Table: tableRefsClause} + stmt = &ast.InsertStmt{Table: tableRefsClause} // insert-values-stmt + require.False(t, core.Cacheable(stmt, is)) + stmt = &ast.InsertStmt{Table: tableRefsClause, Select: &ast.SelectStmt{}} // insert-select-stmt require.True(t, core.Cacheable(stmt, is)) // test DeleteStmt diff --git a/planner/core/plan_cache_test.go b/planner/core/plan_cache_test.go new file mode 100644 index 0000000000000..a480c583d0434 --- /dev/null +++ b/planner/core/plan_cache_test.go @@ -0,0 +1,304 @@ +// Copyright 2022 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 core_test + +import ( + "context" + "errors" + "fmt" + "math/rand" + "strconv" + "strings" + "testing" + + "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/parser/mysql" + plannercore "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/testkit" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util" + "github.com/stretchr/testify/require" +) + +type mockParameterizer struct { + action string +} + +func (mp *mockParameterizer) Parameterize(originSQL string) (paramSQL string, params []expression.Expression, ok bool, err error) { + switch mp.action { + case "error": + return "", nil, false, errors.New("error") + case "not_support": + return "", nil, false, nil + } + // only support SQL like 'select * from t where col {op} {int} and ...' + prefix := "select * from t where " + if !strings.HasPrefix(originSQL, prefix) { + return "", nil, false, nil + } + buf := make([]byte, 0, 32) + buf = append(buf, prefix...) + for i, condStr := range strings.Split(originSQL[len(prefix):], "and") { + if i > 0 { + buf = append(buf, " and "...) + } + tmp := strings.Split(strings.TrimSpace(condStr), " ") + if len(tmp) != 3 { // col {op} {val} + return "", nil, false, nil + } + buf = append(buf, tmp[0]...) + buf = append(buf, tmp[1]...) + buf = append(buf, '?') + + intParam, err := strconv.Atoi(tmp[2]) + if err != nil { + return "", nil, false, nil + } + params = append(params, &expression.Constant{Value: types.NewDatum(intParam), RetType: types.NewFieldType(mysql.TypeLong)}) + } + return string(buf), params, true, nil +} + +func TestInitLRUWithSystemVar(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("set @@session.tidb_prepared_plan_cache_size = 0") // MinValue: 1 + tk.MustQuery("select @@session.tidb_prepared_plan_cache_size").Check(testkit.Rows("1")) + sessionVar := tk.Session().GetSessionVars() + + lru := plannercore.NewLRUPlanCache(uint(sessionVar.PreparedPlanCacheSize), 0, 0, plannercore.PickPlanFromBucket, tk.Session()) + require.NotNil(t, lru) +} + +func TestNonPreparedPlanCacheWithExplain(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec("create table t(a int)") + tk.MustExec("set tidb_enable_non_prepared_plan_cache=1") + tk.MustExec("select * from t where a=1") // cache this plan + + tk.MustQuery("explain select * from t where a=2").Check(testkit.Rows( + `Selection_8 10.00 root eq(test.t.a, 2)`, + `└─TableReader_7 10.00 root data:Selection_6`, + ` └─Selection_6 10.00 cop[tikv] eq(test.t.a, 2)`, + ` └─TableFullScan_5 10000.00 cop[tikv] table:t keep order:false, stats:pseudo`)) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + + tk.MustQuery("explain format=verbose select * from t where a=2").Check(testkit.Rows( + `Selection_8 10.00 169474.57 root eq(test.t.a, 2)`, + `└─TableReader_7 10.00 168975.57 root data:Selection_6`, + ` └─Selection_6 10.00 2534000.00 cop[tikv] eq(test.t.a, 2)`, + ` └─TableFullScan_5 10000.00 2035000.00 cop[tikv] table:t keep order:false, stats:pseudo`)) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + + tk.MustQuery("explain analyze select * from t where a=2").CheckAt([]int{0, 1, 2, 3}, [][]interface{}{ + {"Selection_8", "10.00", "0", "root"}, + {"└─TableReader_7", "10.00", "0", "root"}, + {" └─Selection_6", "10.00", "0", "cop[tikv]"}, + {" └─TableFullScan_5", "10000.00", "0", "cop[tikv]"}, + }) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) +} + +func TestNonPreparedPlanCacheFallback(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`create table t (a int)`) + for i := 0; i < 5; i++ { + tk.MustExec(fmt.Sprintf("insert into t values (%v)", i)) + } + tk.MustExec("set tidb_enable_non_prepared_plan_cache=1") + + // inject a fault to GeneratePlanCacheStmtWithAST + ctx := context.WithValue(context.Background(), "____GeneratePlanCacheStmtWithASTErr", struct{}{}) + tk.MustQueryWithContext(ctx, "select * from t where a in (1, 2)").Sort().Check(testkit.Rows("1", "2")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // cannot generate PlanCacheStmt + tk.MustQueryWithContext(ctx, "select * from t where a in (1, 3)").Sort().Check(testkit.Rows("1", "3")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // cannot generate PlanCacheStmt + tk.MustQuery("select * from t where a in (1, 2)").Sort().Check(testkit.Rows("1", "2")) + tk.MustQuery("select * from t where a in (1, 3)").Sort().Check(testkit.Rows("1", "3")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // no error + + // inject a fault to GetPlanFromSessionPlanCache + tk.MustQuery("select * from t where a=1").Check(testkit.Rows("1")) // cache this plan + tk.MustQuery("select * from t where a=2").Check(testkit.Rows("2")) // plan from cache + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + ctx = context.WithValue(context.Background(), "____GetPlanFromSessionPlanCacheErr", struct{}{}) + tk.MustQueryWithContext(ctx, "select * from t where a=3").Check(testkit.Rows("3")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // fallback to the normal opt-path + tk.MustQueryWithContext(ctx, "select * from t where a=4").Check(testkit.Rows("4")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // fallback to the normal opt-path + tk.MustQueryWithContext(context.Background(), "select * from t where a=0").Check(testkit.Rows("0")) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // use the cached plan if no error + + // inject a fault to RestoreASTWithParams + ctx = context.WithValue(context.Background(), "____GetPlanFromSessionPlanCacheErr", struct{}{}) + ctx = context.WithValue(ctx, "____RestoreASTWithParamsErr", struct{}{}) + _, err := tk.ExecWithContext(ctx, "select * from t where a=1") + require.NotNil(t, err) +} + +func TestNonPreparedPlanCacheBasically(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`create table t (a int, b int, c int, d int, primary key(a), key(b), key(c, d))`) + for i := 0; i < 20; i++ { + tk.MustExec(fmt.Sprintf("insert into t values (%v, %v, %v, %v)", i, rand.Intn(20), rand.Intn(20), rand.Intn(20))) + } + + queries := []string{ + "select * from t where a<10", + "select * from t where a<13 and b<15", + "select * from t where b=13", + "select * from t where c<8", + "select * from t where d>8", + "select * from t where c=8 and d>10", + "select * from t where a<12 and b<13 and c<12 and d>2", + } + + for _, query := range queries { + tk.MustExec(`set tidb_enable_non_prepared_plan_cache=0`) + resultNormal := tk.MustQuery(query).Sort() + tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0")) + + tk.MustExec(`set tidb_enable_non_prepared_plan_cache=1`) + tk.MustQuery(query) // first process + tk.MustQuery(query).Sort().Check(resultNormal.Rows()) // equal to the result without plan-cache + tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) // this plan is from plan-cache + } +} + +func TestIssue38269(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`set @@tidb_enable_prepared_plan_cache=1`) + tk.MustExec("set @@tidb_enable_collect_execution_info=0") + tk.MustExec("use test") + tk.MustExec("create table t1(a int)") + tk.MustExec("create table t2(a int, b int, c int, index idx(a, b))") + tk.MustExec("prepare stmt1 from 'select /*+ inl_join(t2) */ * from t1 join t2 on t1.a = t2.a where t2.b in (?, ?, ?)'") + tk.MustExec("set @a = 10, @b = 20, @c = 30, @d = 40, @e = 50, @f = 60") + tk.MustExec("execute stmt1 using @a, @b, @c") + tk.MustExec("execute stmt1 using @d, @e, @f") + tkProcess := tk.Session().ShowProcess() + ps := []*util.ProcessInfo{tkProcess} + tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) + rows := tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Rows() + require.Contains(t, rows[6][4], "range: decided by [eq(test.t2.a, test.t1.a) in(test.t2.b, 40, 50, 60)]") +} + +func TestIssue38533(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t (a int, key (a))") + tk.MustExec(`prepare st from "select /*+ use_index(t, a) */ a from t where a=? and a=?"`) + tk.MustExec(`set @a=1`) + tk.MustExec(`execute st using @a, @a`) + tkProcess := tk.Session().ShowProcess() + ps := []*util.ProcessInfo{tkProcess} + tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) + plan := tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Rows() + require.True(t, strings.Contains(plan[1][0].(string), "RangeScan")) // range-scan instead of full-scan + + tk.MustExec(`execute st using @a, @a`) + tk.MustExec(`execute st using @a, @a`) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) +} + +func TestIgnoreInsertStmt(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t (a int)") + + // do not cache native insert-stmt + tk.MustExec("prepare st from 'insert into t values (1)'") + tk.MustExec("execute st") + tk.MustExec("execute st") + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) + + // ignore-hint in insert-stmt can work + tk.MustExec("prepare st from 'insert into t select * from t'") + tk.MustExec("execute st") + tk.MustExec("execute st") + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + tk.MustExec("prepare st from 'insert /*+ ignore_plan_cache() */ into t select * from t'") + tk.MustExec("execute st") + tk.MustExec("execute st") + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) +} + +func TestIssue38710(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists UK_NO_PRECISION_19392;") + tk.MustExec("CREATE TABLE `UK_NO_PRECISION_19392` (\n `COL1` bit(1) DEFAULT NULL,\n `COL2` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,\n `COL4` datetime DEFAULT NULL,\n `COL3` bigint DEFAULT NULL,\n `COL5` float DEFAULT NULL,\n UNIQUE KEY `UK_COL1` (`COL1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;") + tk.MustExec("INSERT INTO `UK_NO_PRECISION_19392` VALUES (0x00,'缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈','9294-12-26 06:50:40',-3088380202191555887,-3.33294e38),(NULL,'仲膩蕦圓猴洠飌镂喵疎偌嫺荂踖Ƕ藨蜿諪軁笞','1746-08-30 18:04:04',-4016793239832666288,-2.52633e38),(0x01,'冑溜畁脊乤纊繳蟥哅稐奺躁悼貘飗昹槐速玃沮','1272-01-19 23:03:27',-8014797887128775012,1.48868e38);\n") + tk.MustExec(`prepare stmt from 'select * from UK_NO_PRECISION_19392 where col1 between ? and ? or col3 = ? or col2 in (?, ?, ?);';`) + tk.MustExec("set @a=0x01, @b=0x01, @c=-3088380202191555887, @d=\"缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈\", @e=\"缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈\", @f=\"缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈\";") + rows := tk.MustQuery(`execute stmt using @a,@b,@c,@d,@e,@f;`) // can not be cached because @a = @b + require.Equal(t, 2, len(rows.Rows())) + + tk.MustExec(`set @a=NULL, @b=NULL, @c=-4016793239832666288, @d="缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈", @e="仲膩蕦圓猴洠飌镂喵疎偌嫺荂踖Ƕ藨蜿諪軁笞", @f="缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈";`) + rows = tk.MustQuery(`execute stmt using @a,@b,@c,@d,@e,@f;`) + require.Equal(t, 2, len(rows.Rows())) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) + + rows = tk.MustQuery(`execute stmt using @a,@b,@c,@d,@e,@f;`) + require.Equal(t, 2, len(rows.Rows())) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + + tk.MustExec(`set @a=0x01, @b=0x01, @c=-3088380202191555887, @d="缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈", @e="缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈", @f="缎馗惫砲兣肬憵急鳸嫅稩邏蠧鄂艘腯灩專妴粈";`) + rows = tk.MustQuery(`execute stmt using @a,@b,@c,@d,@e,@f;`) + require.Equal(t, 2, len(rows.Rows())) + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // can not use the cache because the types for @a and @b are not equal to the cached plan +} + +func TestPlanCacheDiagInfo(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t (a int, b int, key(a), key(b))") + + tk.MustExec("prepare stmt from 'select * from t where a in (select a from t)'") + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: query has sub-queries is un-cacheable")) + + tk.MustExec("prepare stmt from 'select /*+ ignore_plan_cache() */ * from t'") + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: ignore plan cache by hint")) + + tk.MustExec("prepare stmt from 'select * from t limit ?'") + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: query has 'limit ?' is un-cacheable")) + + tk.MustExec("prepare stmt from 'select * from t limit ?, 1'") + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: query has 'limit ?, 10' is un-cacheable")) + + tk.MustExec("prepare stmt from 'select * from t order by ?'") + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: query has 'order by ?' is un-cacheable")) + + tk.MustExec("prepare stmt from 'select * from t where a=?'") + tk.MustExec("set @a='123'") + tk.MustExec("execute stmt using @a") // '123' -> 123 + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: '123' may be converted to INT")) + + tk.MustExec("prepare stmt from 'select * from t where a=? and a=?'") + tk.MustExec("set @a=1, @b=1") + tk.MustExec("execute stmt using @a, @b") // a=1 and a=1 -> a=1 + tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: some parameters may be overwritten")) +} diff --git a/sessiontxn/txn_rc_tso_optimize_test.go b/sessiontxn/txn_rc_tso_optimize_test.go new file mode 100644 index 0000000000000..3e945778ca107 --- /dev/null +++ b/sessiontxn/txn_rc_tso_optimize_test.go @@ -0,0 +1,821 @@ +// 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 sessiontxn_test + +import ( + "context" + "fmt" + "testing" + + "github.com/pingcap/failpoint" + "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessiontxn" + "github.com/pingcap/tidb/sessiontxn/isolation" + "github.com/pingcap/tidb/testkit" + "github.com/stretchr/testify/require" +) + +func TestRcTSOCmdCountForPrepareExecuteNormal(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture", "return")) + + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture")) + }() + store := testkit.CreateMockStore(t) + + ctx := context.Background() + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global transaction_isolation = 'READ-COMMITTED'") + tk.MustExec("set global tx_isolation = 'READ-COMMITTED'") + tk.RefreshSession() + sctx := tk.Session() + tk.MustExec("set session tidb_rc_write_check_ts = on") + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("create table t2(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t2 values (1, 1, 1)") + + // PointPlanQueries always don't send tso request, the others send tso request at first exeuction + sqlSelectID, _, _, _ := tk.Session().PrepareStmt("select * from t1 where id1 = ? for update") + sqlUpdateID, _, _, _ := tk.Session().PrepareStmt("update t1 set id3 = id3 + 10 where id1 = ?") + sqlUpdateID2, _, _, _ := tk.Session().PrepareStmt("update t2 set id3 = id3 + 10 where id1 = ?") + sqlSelectID2, _, _, _ := tk.Session().PrepareStmt("select id1+id2 as x from t1 where id1 = ? for update") + sqlSelectID3, _, _, _ := tk.Session().PrepareStmt("select * from t1 where id1 = ?") + sqlInsertID, _, _, _ := tk.Session().PrepareStmt("insert into t1 values(?, ?, ?)") + sqlDeleteID, _, _, _ := tk.Session().PrepareStmt("delete from t1 where id1 = ?") + sqlSelectID4, _, _, _ := tk.Session().PrepareStmt("select * from t1 where id1 > ?") + + res := tk.MustQuery("show variables like 'transaction_isolation'") + require.Equal(t, "READ-COMMITTED", res.Rows()[0][1]) + resetAllTsoCounter(sctx) + + for i := 1; i < 100; i++ { + tk.MustExec("begin pessimistic") + + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID2, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID2, expression.Args2Expressions4Test(9)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID3, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + val := i * 10 + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlInsertID, expression.Args2Expressions4Test(val, val, val)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlDeleteID, expression.Args2Expressions4Test(val)) + require.NoError(t, err) + require.Nil(t, stmt) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID4, expression.Args2Expressions4Test(9)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle := getAllTsoCounter(sctx) + require.Equal(t, uint64(496), countTsoRequest.(uint64)) + require.Equal(t, uint64(594), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(198), countWaitTsoOracle.(uint64)) + + tk.MustExec("set session tidb_rc_write_check_ts = false") + tk.MustExec("delete from t1") + tk.MustExec("delete from t2") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t2 values (1, 1, 1)") + tk.MustExec("insert into t2 values (5, 5, 5)") + sctx.SetValue(sessiontxn.TsoRequestCount, 0) + for i := 1; i < 100; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID2, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + val := i * 10 + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlInsertID, expression.Args2Expressions4Test(val, val, val)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlDeleteID, expression.Args2Expressions4Test(val)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + count := sctx.Value(sessiontxn.TsoRequestCount) + require.Equal(t, uint64(693), count) +} + +func TestRcTSOCmdCountForPrepareExecuteExtra(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture", "return")) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture")) + }() + store := testkit.CreateMockStore(t) + + ctx := context.Background() + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global transaction_isolation = 'READ-COMMITTED'") + tk.MustExec("set global tx_isolation = 'READ-COMMITTED'") + tk.RefreshSession() + sctx := tk.Session() + tk.MustExec("set session tidb_rc_write_check_ts = on") + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("create table t2(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t1 values (10, 10, 10)") + tk.MustExec("insert into t2 values (1, 1, 1)") + tk.MustExec("insert into t2 values (10, 10, 10)") + tk.MustExec("insert into t2 values (20, 20, 20)") + + res := tk.MustQuery("show variables like 'transaction_isolation'") + require.Equal(t, "READ-COMMITTED", res.Rows()[0][1]) + + // union statements with two point-lock-read. + sqlSelectID1, _, _, _ := tk.Session().PrepareStmt("select * from t1 where id1 = ? for update union select * from t2 where id1 = ? for update") + sqlSelectID2, _, _, _ := tk.Session().PrepareStmt("select id1*2 from t1 where id1 = ? for update union select id1*2 from t2 where id1 = ? for update") + sqlSelectID3, _, _, _ := tk.Session().PrepareStmt("select * from t1 where id1 = ? for update union select * from t2 where id1 = ?") + resetAllTsoCounter(sctx) + for i := 0; i < 10; i++ { + tk.MustExec("begin pessimistic") + + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID1, expression.Args2Expressions4Test(1, 2)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID2, expression.Args2Expressions4Test(1, 2)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID3, expression.Args2Expressions4Test(1, 2)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle := getAllTsoCounter(sctx) + require.Equal(t, uint64(32), countTsoRequest.(uint64)) + require.Equal(t, uint64(20), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(10), countWaitTsoOracle.(uint64)) + + // Join->SelectLock->PoinGet + sqlSelectID4, _, _, _ := tk.Session().PrepareStmt("SELECT * FROM t1 JOIN t2 ON t1.id1 = t2.id1 WHERE t1.id1 = ? FOR UPDATE") + resetAllTsoCounter(sctx) + for i := 0; i < 10; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID4, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(21), countTsoRequest.(uint64)) + require.Equal(t, uint64(10), countTsoUseConstant.(uint64)) + require.Equal(t, 0, countWaitTsoOracle.(int)) + + // SelectLock_7->UnionScan_8->TableReader_10->TableRangeScan_9 + sqlInsertID1, _, _, _ := tk.Session().PrepareStmt("insert into t2 values(?, ?, ?)") + sqlSelectID5, _, _, _ := tk.Session().PrepareStmt("SELECT * FROM t1 WHERE id1 = ? or id1 < 2 for update") + resetAllTsoCounter(sctx) + for i := 1; i < 6; i++ { + tk.MustExec("begin pessimistic") + val := i * 11 + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlInsertID1, expression.Args2Expressions4Test(val, val, val)) + require.NoError(t, err) + require.Nil(t, stmt) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID5, expression.Args2Expressions4Test(val)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(20), countTsoRequest.(uint64)) + require.Equal(t, uint64(5), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // BatchPointGet + sqlSelectID6, _, _, _ := tk.Session().PrepareStmt("SELECT * FROM t1 WHERE id1 = ? OR id1 = ? FOR UPDATE") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID6, expression.Args2Expressions4Test(1, 2)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(15), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // Subquery has SelectLock + PointGet + sqlSelectID7, _, _, _ := tk.Session().PrepareStmt("SELECT * FROM t1 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = ? FOR UPDATE)") + sqlSelectID8, _, _, _ := tk.Session().PrepareStmt("SELECT * FROM t1 JOIN (SELECT * FROM t2 WHERE id1 = ? FOR UPDATE ) tt2 ON t1.id1 = tt2.id1") + sqlSelectID9, _, _, _ := tk.Session().PrepareStmt("SELECT (SELECT id1 * 2 FROM t1 WHERE id1 = ? FOR UPDATE)+id1 FROM t2") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlSelectID7, expression.Args2Expressions4Test(10)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID8, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlSelectID9, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.NoError(t, stmt.Close()) + + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(25), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(15), countWaitTsoOracle.(uint64)) + + // PointUpdate Index and Non-index + sqlUpdateID1, _, _, _ := tk.Session().PrepareStmt("UPDATE t1 set id2 = id2 + 100 WHERE id1 = ?") + sqlUpdateID2, _, _, _ := tk.Session().PrepareStmt("UPDATE t2 SET id1 = id1 + 100 WHERE id1 = ?") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID1, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID2, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(10), countTsoRequest.(uint64)) + require.Equal(t, uint64(10), countTsoUseConstant.(uint64)) + require.Equal(t, 0, countWaitTsoOracle.(int)) + + // SelectLock has PointGet and other plans + sqlUpdateID3, _, _, _ := tk.Session().PrepareStmt("UPDATE t1 set id2 = id2 + 100 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = ?)") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID3, expression.Args2Expressions4Test(1)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(15), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // PointUpdate with singlerow subquery. singlerow subquery makes tso wait + // PointUpdate doesn't make tso request + sqlUpdateID4, _, _, _ := tk.Session().PrepareStmt("UPDATE t1 set id2 = id2 + 100 WHERE id1 = (SELECT id1 FROM t2 WHERE id1 = ?)") + resetAllTsoCounter(sctx) + for i := 0; i < 20; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlUpdateID4, expression.Args2Expressions4Test(11)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(60), countTsoRequest.(uint64)) + require.Equal(t, uint64(20), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(20), countWaitTsoOracle.(uint64)) + + // delete + sqlDeleteID1, _, _, _ := tk.Session().PrepareStmt("DELETE FROM t1 WHERE id1 = ?") + sqlDeleteID2, _, _, _ := tk.Session().PrepareStmt("DELETE FROM t1 WHERE id1 > ?") + sqlDeleteID3, _, _, _ := tk.Session().PrepareStmt("DELETE FROM t1 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = ?)") + resetAllTsoCounter(sctx) + for i := 0; i < 1; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlDeleteID1, expression.Args2Expressions4Test(3)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlDeleteID2, expression.Args2Expressions4Test(4)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlDeleteID3, expression.Args2Expressions4Test(20)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(4), countTsoRequest.(uint64)) + require.Equal(t, uint64(1), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(2), countWaitTsoOracle.(uint64)) + + // insert on duplicate + sqlInsertID2, _, _, _ := tk.Session().PrepareStmt("INSERT INTO t1 VALUES(?,5,5) ON DUPLICATE KEY UPDATE id3 = id3 + 100") + sqlInsertID3, _, _, _ := tk.Session().PrepareStmt("INSERT INTO t1 VALUES(?,5,5) ON DUPLICATE KEY UPDATE id2 = id2 + 100") + sqlInsertID4, _, _, _ := tk.Session().PrepareStmt("INSERT INTO t1 VALUES(8,?,5) ON DUPLICATE KEY UPDATE id3 = id3 + 100") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlInsertID2, expression.Args2Expressions4Test(10)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlInsertID3, expression.Args2Expressions4Test(10)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlInsertID4, expression.Args2Expressions4Test(10)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(25), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(15), countWaitTsoOracle.(uint64)) + + // replace into + tk.MustExec("truncate table t1") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t1 values (10, 10, 10)") + sqlReplaceIntot1, _, _, _ := tk.Session().PrepareStmt("REPLACE INTO t1 VALUES(1, ?, ?)") + sqlReplaceIntot2, _, _, _ := tk.Session().PrepareStmt("REPLACE INTO t1 VALUES(?, ?, 20)") + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + val := i * 11 + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlReplaceIntot1, expression.Args2Expressions4Test(val, val)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlReplaceIntot2, expression.Args2Expressions4Test(val, val)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(20), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(10), countWaitTsoOracle.(uint64)) + + // insert ignore + tk.MustExec("truncate table t1") + tk.MustExec("insert into t1 values (1, 1, 1)") + sqlInsertIgnore, _, _, _ := tk.Session().PrepareStmt("INSERT IGNORE INTO t1 VALUES(?, ?, ?)") + resetAllTsoCounter(sctx) + tk.MustExec("begin pessimistic") + stmt, err := tk.Session().ExecutePreparedStmt(ctx, sqlInsertIgnore, expression.Args2Expressions4Test(1, 2, 2)) + require.NoError(t, err) + require.Nil(t, stmt) + stmt, err = tk.Session().ExecutePreparedStmt(ctx, sqlInsertIgnore, expression.Args2Expressions4Test(5, 5, 5)) + require.NoError(t, err) + require.Nil(t, stmt) + tk.MustExec("commit") + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(4), countTsoRequest.(uint64)) + require.Equal(t, uint64(2), countTsoUseConstant.(uint64)) + require.Equal(t, 0, countWaitTsoOracle.(int)) + tk.MustQuery("SELECT * FROM t1 WHERE id1 = 1").Check(testkit.Rows("1 1 1")) + tk.MustQuery("SELECT * FROM t1 WHERE id1 = 5").Check(testkit.Rows("5 5 5")) +} + +func TestRcTSOCmdCountForTextSQLExecuteNormal(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD", "return")) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD")) + }() + store := testkit.CreateMockStore(t) + + // ctx := context.Background() + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global transaction_isolation = 'READ-COMMITTED'") + tk.MustExec("set global tx_isolation = 'READ-COMMITTED'") + tk.RefreshSession() + sctx := tk.Session() + tk.MustExec("set session tidb_rc_write_check_ts = on") + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("create table t2(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t2 values (1, 1, 1)") + tk.MustExec("insert into t2 values (5, 5, 5)") + tk.MustExec("insert into t2 values (8, 8, 8)") + + res := tk.MustQuery("show variables like 'transaction_isolation'") + require.Equal(t, "READ-COMMITTED", res.Rows()[0][1]) + sctx.SetValue(sessiontxn.TsoRequestCount, 0) + + for i := 1; i < 100; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("select * from t1 where id1 = 1 for update") + tk.MustExec("update t1 set id3 = id3 + 10 where id1 = 1") + tk.MustExec("update t2 set id3 = id3 + 10 where id1 = 1") + tk.MustExec("update t2 set id3 = id3 + 10 where id1 > 3 and id1 < 6") + tk.MustExec("select id1+id2 as x from t1 where id1 = 9 for update") + val := i * 10 + tk.MustExec(fmt.Sprintf("insert into t2 values(%v, %v, %v)", val, val, val)) + tk.MustExec(fmt.Sprintf("delete from t2 where id1 = %v", val)) + tk.MustExec("commit") + } + count := sctx.Value(sessiontxn.TsoRequestCount) + require.Equal(t, uint64(495), count) +} + +func resetAllTsoCounter(sctx sessionctx.Context) { + sctx.SetValue(sessiontxn.TsoRequestCount, 0) + sctx.SetValue(sessiontxn.TsoUseConstantCount, 0) + sctx.SetValue(sessiontxn.TsoWaitCount, 0) +} + +func getAllTsoCounter(sctx sessionctx.Context) (interface{}, interface{}, interface{}) { + countTsoRequest := sctx.Value(sessiontxn.TsoRequestCount) + countTsoUseConstant := sctx.Value(sessiontxn.TsoUseConstantCount) + countWaitTsoOracle := sctx.Value(sessiontxn.TsoWaitCount) + return countTsoRequest, countTsoUseConstant, countWaitTsoOracle +} + +func assertAllTsoCounter(t *testing.T, + assertPair []uint64) { + for i := 0; i < len(assertPair); i += 2 { + require.Equal(t, assertPair[i], assertPair[i+1]) + } +} + +func TestRcTSOCmdCountForTextSQLExecuteExtra(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture", "return")) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture", "return")) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/requestTsoFromPD")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/tsoUseConstantFuture")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/sessiontxn/isolation/waitTsoOfOracleFuture")) + }() + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global transaction_isolation = 'READ-COMMITTED'") + tk.MustExec("set global tx_isolation = 'READ-COMMITTED'") + tk.RefreshSession() + sctx := tk.Session() + tk.MustExec("set session tidb_rc_write_check_ts = on") + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("create table t2(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t1 values (10, 10, 10)") + tk.MustExec("insert into t2 values (1, 1, 1)") + tk.MustExec("insert into t2 values (10, 10, 10)") + tk.MustExec("insert into t2 values (20, 20, 20)") + + res := tk.MustQuery("show variables like 'transaction_isolation'") + require.Equal(t, "READ-COMMITTED", res.Rows()[0][1]) + + // union statements makes disableAdviseWarmup false, + // use constant tso when all sub queries of unions are point-lock-read. + resetAllTsoCounter(sctx) + for i := 0; i < 10; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("select * from t1 where id1 = 1 for update union select * from t2 where id1 = 2 for update") + tk.MustExec("select id1*2 from t1 where id1 = 1 for update union select id1*2 from t2 where id1 = 2 for update") + tk.MustExec("select * from t1 where id1 = 1 for update union select * from t2 where id1 = 2") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle := getAllTsoCounter(sctx) + require.Equal(t, uint64(50), countTsoRequest) + require.Equal(t, uint64(20), countTsoUseConstant) + require.Equal(t, uint64(10), countWaitTsoOracle) + + // Join->SelectLock->PoinGet + resetAllTsoCounter(sctx) + for i := 0; i < 10; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("SELECT * FROM t1 JOIN t2 ON t1.id1 = t2.id1 WHERE t1.id1 = 1 FOR UPDATE") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(30), countTsoRequest.(uint64)) + require.Equal(t, uint64(10), countTsoUseConstant.(uint64)) + require.Equal(t, 0, countWaitTsoOracle.(int)) + + // SelectLock_7->UnionScan_8->TableReader_10->TableRangeScan_9 + resetAllTsoCounter(sctx) + for i := 1; i < 6; i++ { + tk.MustExec("begin pessimistic") + val := i * 11 + tk.MustExec(fmt.Sprintf("insert into t2 values(%v, %v, %v)", val, val, val)) + tk.MustExec(fmt.Sprintf("SELECT * FROM t1 WHERE id1 = %v or id1 < 2 for update", val)) + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(20), countTsoRequest.(uint64)) + require.Equal(t, uint64(5), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // BatchPointGet + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("SELECT * FROM t1 WHERE id1 = 1 OR id1 = 2 FOR UPDATE") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(15), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // Subquery has SelectLock + PointGet + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("SELECT * FROM t1 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = 1 FOR UPDATE)") + tk.MustExec("SELECT * FROM t1 JOIN (SELECT * FROM t2 WHERE id1 = 1 FOR UPDATE ) tt2 ON t1.id1 = tt2.id1") + tk.MustExec("SELECT (SELECT id1 * 2 FROM t1 WHERE id1 = 1 FOR UPDATE)+id1 FROM t2") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(25), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(15), countWaitTsoOracle.(uint64)) + + // PointUpdate Index and Non-index + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("UPDATE t1 set id2 = id2 + 100 WHERE id1 = 1") + tk.MustExec("UPDATE t2 SET id1 = id1 + 100 WHERE id1 = 1") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(10), countTsoRequest.(uint64)) + require.Equal(t, uint64(10), countTsoUseConstant.(uint64)) + require.Equal(t, 0, countWaitTsoOracle.(int)) + + // SelectLock has PointGet and other plans + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("UPDATE t1 set id2 = id2 + 100 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = 1)") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(15), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(5), countWaitTsoOracle.(uint64)) + + // PointUpdate with singlerow subquery. singlerow subquery makes tso wait + // PointUpdate doesn't make tso request + resetAllTsoCounter(sctx) + for i := 0; i < 20; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("UPDATE t1 set id2 = id2 + 100 WHERE id1 = (SELECT id1 FROM t2 WHERE id1 = 10)") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(60), countTsoRequest.(uint64)) + require.Equal(t, uint64(20), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(20), countWaitTsoOracle.(uint64)) + + // insert with select + resetAllTsoCounter(sctx) + for i := 0; i < 1; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("INSERT INTO t1 VALUES(4,4,4)") + tk.MustExec("INSERT INTO t1 SELECT * FROM t2 WHERE id1 = 11") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(4), countTsoRequest.(uint64)) + require.Equal(t, uint64(1), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(1), countWaitTsoOracle.(uint64)) + + // delete + resetAllTsoCounter(sctx) + for i := 0; i < 1; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("DELETE FROM t1 WHERE id1 = 3") + tk.MustExec("DELETE FROM t1 WHERE id1 > 4") + tk.MustExec("DELETE FROM t1 WHERE id1 IN (SELECT id1 FROM t2 WHERE id1 = 20)") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(4), countTsoRequest.(uint64)) + require.Equal(t, uint64(1), countTsoUseConstant.(uint64)) + require.Equal(t, uint64(2), countWaitTsoOracle.(uint64)) + + // insert on duplicate key + resetAllTsoCounter(sctx) + for i := 0; i < 5; i++ { + tk.MustExec("begin pessimistic") + tk.MustExec("INSERT INTO t1 VALUES(10,5,5) ON DUPLICATE KEY UPDATE id3 = id3 + 100") + tk.MustExec("INSERT INTO t1 VALUES(10,5,5) ON DUPLICATE KEY UPDATE id2 = id2 + 100") + tk.MustExec("INSERT INTO t1 VALUES(8,10,5) ON DUPLICATE KEY UPDATE id3 = id3 + 100") + tk.MustExec("commit") + } + countTsoRequest, countTsoUseConstant, countWaitTsoOracle = getAllTsoCounter(sctx) + require.Equal(t, uint64(25), countTsoRequest.(uint64)) + require.Equal(t, 0, countTsoUseConstant.(int)) + require.Equal(t, uint64(15), countWaitTsoOracle.(uint64)) +} + +func TestConflictErrorsUseRcWriteCheckTs(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/executor/assertPessimisticLockErr", "return")) + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + defer tk.MustExec("rollback") + + se := tk.Session() + + tk2 := testkit.NewTestKit(t, store) + defer tk2.MustExec("rollback") + + tk.MustExec("use test") + tk2.MustExec("use test") + tk.MustExec("set transaction_isolation = 'READ-COMMITTED'") + tk.MustExec("set tx_isolation = 'READ-COMMITTED'") + tk.MustExec("set session tidb_rc_write_check_ts = on") + tk2.MustExec("set session tidb_rc_write_check_ts = on") + + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1)") + tk.MustExec("insert into t1 values (10, 10, 10)") + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("update t1 set id3 = id3 + 1 where id1 = 1") + tk.MustExec("select * from t1 where id1 = 1 for update") + tk.MustExec("commit") + records, ok := se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.Equal(t, true, ok) + require.Equal(t, records["errWriteConflict"], 1) + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("update t1 set id1 = 5 where id1 = 1") + tk.MustExec("delete from t1 where id1 = 5") + tk.MustExec("commit") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.True(t, ok) + tk.MustQuery("select * from t1 where id1 = 5 for update").Check(testkit.Rows()) + require.Equal(t, records["errWriteConflict"], 1) + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("insert into t1 values(20,20,20)") + tk.MustExec("delete from t1 where id1 = 20") + tk.MustQuery("select * from t1 where id1 = 20").Check(testkit.Rows()) + tk.MustExec("commit") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.True(t, ok) + require.Equal(t, records["errWriteConflict"], 1) + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("insert into t1 values(30,30,30)") + tk.MustExec("update t1 set id3 = 300 where id1 = 30") + tk.MustQuery("select * from t1 where id1 = 30").Check(testkit.Rows("30 30 300")) + tk.MustExec("rollback") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.True(t, ok) + require.Equal(t, records["errWriteConflict"], 1) + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("insert into t1 values(40,40,40)") + _, err := tk.Exec("insert into t1 values(40,400,400)") + require.Error(t, err) + tk.MustExec("rollback") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.True(t, ok) + require.Equal(t, records["errDuplicateKey"], 1) + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("insert into t1 values(50,50,50)") + _, err = tk.Exec("insert ignore into t1 values(50,400,400)") + require.NoError(t, err) + tk.MustExec("rollback") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.True(t, ok) + require.Equal(t, records["errDuplicateKey"], 0) + require.Equal(t, records["errWriteConflict"], 1) + + tk.MustExec("begin pessimistic") + mgr := sessiontxn.GetTxnManager(tk.Session()) + tk.MustExec("update t1 set id3 = id3 + 1 where id1 = 1") + p := mgr.GetContextProvider().(*isolation.PessimisticRCTxnContextProvider) + require.Equal(t, p.IsCheckTSInWriteStmtMode(), true) + tk.MustExec("select * from t1 where id1 = 1") + require.Equal(t, p.IsCheckTSInWriteStmtMode(), false) + tk.MustExec("rollback") + + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("update t1 set id3 = id3 + 1 where id1 = 1") + tk.MustExec("select * from t1 where id1 = 1 for update") + tk.MustExec("select * from t1 where id1 = 10") + tk2.MustExec("insert into t1 values(60, 60, 60)") + tk.MustQuery("select * from t1 where id1 = 60 for update").Check(testkit.Rows("60 60 60")) + tk.MustExec("commit") + records, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.Equal(t, true, ok) + require.Equal(t, records["errWriteConflict"], 1) + + tk.MustExec("set session tidb_rc_write_check_ts = false") + tk2.MustExec("set session tidb_rc_write_check_ts = false") + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("update t1 set id3 = id3 + 1 where id1 = 1") + tk.MustExec("select * from t1 where id1 = 1 for update") + tk.MustExec("commit") + _, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.Equal(t, false, ok) + + tk.MustExec("insert into t1 values(20, 20, 20)") + se.SetValue(sessiontxn.AssertLockErr, nil) + tk.MustExec("begin pessimistic") + tk2.MustExec("update t1 set id1 = 200 where id1 = 20") + tk.MustQuery("select * from t1 where id1 = 200 for update").Check(testkit.Rows("200 20 20")) + tk.MustExec("commit") + _, ok = se.Value(sessiontxn.AssertLockErr).(map[string]int) + require.Equal(t, false, ok) + + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/executor/assertPessimisticLockErr")) +} + +func TestRcWaitTSInSlowLog(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global transaction_isolation = 'READ-COMMITTED'") + tk.RefreshSession() + sctx := tk.Session() + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(id1 int, id2 int, id3 int, PRIMARY KEY(id1), UNIQUE KEY udx_id2 (id2))") + tk.MustExec("insert into t1 values (1, 1, 1), (2, 2, 2), (3, 3, 3)") + + res := tk.MustQuery("show variables like 'transaction_isolation'") + require.Equal(t, "READ-COMMITTED", res.Rows()[0][1]) + sctx.SetValue(sessiontxn.TsoRequestCount, 0) + + tk.MustExec("begin pessimistic") + waitTs1 := sctx.GetSessionVars().DurationWaitTS + tk.MustExec("update t1 set id3 = id3 + 10 where id1 = 1") + waitTs2 := sctx.GetSessionVars().DurationWaitTS + tk.MustExec("update t1 set id3 = id3 + 10 where id1 > 3 and id1 < 6") + waitTs3 := sctx.GetSessionVars().DurationWaitTS + tk.MustExec("commit") + require.NotEqual(t, waitTs1, waitTs2) + require.NotEqual(t, waitTs1, waitTs2) + require.NotEqual(t, waitTs2, waitTs3) +}