From 4e10fec132f182f67e7f8a77ef9a7065d34780a4 Mon Sep 17 00:00:00 2001 From: crazycs Date: Thu, 9 Nov 2023 15:29:12 +0800 Subject: [PATCH 1/3] This is an automated cherry-pick of #48412 Signed-off-by: ti-chi-bot --- .../internal/testserverclient/BUILD.bazel | 24 + pkg/session/test/txn/txn_test.go | 631 ++++++++++++++++++ server/server_test.go | 88 +++ server/tidb_test.go | 5 + session/session.go | 8 + session/tidb.go | 22 +- 6 files changed, 773 insertions(+), 5 deletions(-) create mode 100644 pkg/server/internal/testserverclient/BUILD.bazel create mode 100644 pkg/session/test/txn/txn_test.go diff --git a/pkg/server/internal/testserverclient/BUILD.bazel b/pkg/server/internal/testserverclient/BUILD.bazel new file mode 100644 index 0000000000000..b29f420f7e1f5 --- /dev/null +++ b/pkg/server/internal/testserverclient/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "testserverclient", + srcs = ["server_client.go"], + importpath = "github.com/pingcap/tidb/pkg/server/internal/testserverclient", + visibility = ["//pkg/server:__subpackages__"], + deps = [ + "//pkg/config", + "//pkg/errno", + "//pkg/kv", + "//pkg/parser/mysql", + "//pkg/server", + "//pkg/testkit", + "//pkg/testkit/testenv", + "//pkg/util/versioninfo", + "@com_github_go_sql_driver_mysql//:mysql", + "@com_github_pingcap_errors//:errors", + "@com_github_pingcap_failpoint//:failpoint", + "@com_github_pingcap_log//:log", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/pkg/session/test/txn/txn_test.go b/pkg/session/test/txn/txn_test.go new file mode 100644 index 0000000000000..3f5893157ea34 --- /dev/null +++ b/pkg/session/test/txn/txn_test.go @@ -0,0 +1,631 @@ +// Copyright 2023 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 txn + +import ( + "context" + "fmt" + "strings" + "sync" + "testing" + "time" + + "github.com/pingcap/failpoint" + "github.com/pingcap/tidb/pkg/config" + "github.com/pingcap/tidb/pkg/kv" + "github.com/pingcap/tidb/pkg/parser/auth" + "github.com/pingcap/tidb/pkg/parser/mysql" + "github.com/pingcap/tidb/pkg/parser/terror" + plannercore "github.com/pingcap/tidb/pkg/planner/core" + "github.com/pingcap/tidb/pkg/testkit" + "github.com/stretchr/testify/require" +) + +// TestAutocommit . See https://dev.mysql.com/doc/internals/en/status-flags.html +func TestAutocommit(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + tk.MustExec("drop table if exists t;") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("insert t values ()") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("begin") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("insert t values ()") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("drop table if exists t") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + + tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + tk.MustExec("set autocommit=0") + require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) + tk.MustExec("insert t values ()") + require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) + tk.MustExec("commit") + require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) + tk.MustExec("drop table if exists t") + require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) + tk.MustExec("set autocommit='On'") + require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) + + // When autocommit is 0, transaction start ts should be the first *valid* + // statement, rather than *any* statement. + tk.MustExec("create table t (id int key)") + tk.MustExec("set @@autocommit = 0") + tk.MustExec("rollback") + tk.MustExec("set @@autocommit = 0") + + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + tk1.MustExec("insert into t select 1") + //nolint:all_revive,revive + tk.MustQuery("select * from t").Check(testkit.Rows("1")) + tk.MustExec("delete from t") + + // When the transaction is rolled back, the global set statement would succeed. + tk.MustExec("set @@global.autocommit = 0") + tk.MustExec("begin") + tk.MustExec("insert into t values (1)") + tk.MustExec("set @@global.autocommit = 1") + tk.MustExec("rollback") + tk.MustQuery("select count(*) from t where id = 1").Check(testkit.Rows("0")) + tk.MustQuery("select @@global.autocommit").Check(testkit.Rows("1")) + + // When the transaction is committed because of switching mode, the session set statement shold succeed. + tk.MustExec("set autocommit = 0") + tk.MustExec("begin") + tk.MustExec("insert into t values (1)") + tk.MustExec("set autocommit = 1") + tk.MustExec("rollback") + tk.MustQuery("select count(*) from t where id = 1").Check(testkit.Rows("1")) + tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) + + tk.MustExec("set autocommit = 0") + tk.MustExec("insert into t values (2)") + tk.MustExec("set autocommit = 1") + tk.MustExec("rollback") + tk.MustQuery("select count(*) from t where id = 2").Check(testkit.Rows("1")) + tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) + + // Set should not take effect if the mode is not changed. + tk.MustExec("set autocommit = 0") + tk.MustExec("begin") + tk.MustExec("insert into t values (3)") + tk.MustExec("set autocommit = 0") + tk.MustExec("rollback") + tk.MustQuery("select count(*) from t where id = 3").Check(testkit.Rows("0")) + tk.MustQuery("select @@autocommit").Check(testkit.Rows("0")) + + tk.MustExec("set autocommit = 1") + tk.MustExec("begin") + tk.MustExec("insert into t values (4)") + tk.MustExec("set autocommit = 1") + tk.MustExec("rollback") + tk.MustQuery("select count(*) from t where id = 4").Check(testkit.Rows("0")) + tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) +} + +// TestTxnLazyInitialize tests that when autocommit = 0, not all statement starts +// a new transaction. +func TestTxnLazyInitialize(t *testing.T) { + testTxnLazyInitialize(t, false) + testTxnLazyInitialize(t, true) +} + +func testTxnLazyInitialize(t *testing.T, isPessimistic bool) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (id int)") + if isPessimistic { + tk.MustExec("set tidb_txn_mode = 'pessimistic'") + } + + tk.MustExec("set @@autocommit = 0") + _, err := tk.Session().Txn(true) + require.True(t, kv.ErrInvalidTxn.Equal(err)) + txn, err := tk.Session().Txn(false) + require.NoError(t, err) + require.False(t, txn.Valid()) + tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) + tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) + + // Those statements should not start a new transaction automatically. + tk.MustQuery("select 1") + tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) + + tk.MustExec("set @@tidb_general_log = 0") + tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) + + tk.MustQuery("explain select * from t") + tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) + + // Begin statement should start a new transaction. + tk.MustExec("begin") + txn, err = tk.Session().Txn(false) + require.NoError(t, err) + require.True(t, txn.Valid()) + tk.MustExec("rollback") + + tk.MustExec("select * from t") + txn, err = tk.Session().Txn(false) + require.NoError(t, err) + require.True(t, txn.Valid()) + tk.MustExec("rollback") + + tk.MustExec("insert into t values (1)") + txn, err = tk.Session().Txn(false) + require.NoError(t, err) + require.True(t, txn.Valid()) + tk.MustExec("rollback") +} + +func TestDisableTxnAutoRetry(t *testing.T) { + store := testkit.CreateMockStoreWithSchemaLease(t, 1*time.Second) + + setTxnTk := testkit.NewTestKit(t, store) + setTxnTk.MustExec("set global tidb_txn_mode=''") + tk1 := testkit.NewTestKit(t, store) + tk2 := testkit.NewTestKit(t, store) + + tk1.MustExec("use test") + tk2.MustExec("use test") + + tk1.MustExec("create table no_retry (id int)") + tk1.MustExec("insert into no_retry values (1)") + tk1.MustExec("set @@tidb_disable_txn_auto_retry = 1") + + tk1.MustExec("begin") + tk1.MustExec("update no_retry set id = 2") + + tk2.MustExec("begin") + tk2.MustExec("update no_retry set id = 3") + tk2.MustExec("commit") + + // No auto retry because tidb_disable_txn_auto_retry is set to 1. + _, err := tk1.Session().Execute(context.Background(), "commit") + require.Error(t, err) + + // session 1 starts a transaction early. + // execute a select statement to clear retry history. + tk1.MustExec("select 1") + err = tk1.Session().PrepareTxnCtx(context.Background()) + require.NoError(t, err) + // session 2 update the value. + tk2.MustExec("update no_retry set id = 4") + // AutoCommit update will retry, so it would not fail. + tk1.MustExec("update no_retry set id = 5") + + // RestrictedSQL should retry. + ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnOthers) + tk1.Session().ExecuteInternal(ctx, "begin") + + tk2.MustExec("update no_retry set id = 6") + + tk1.Session().ExecuteInternal(ctx, "update no_retry set id = 7") + tk1.Session().ExecuteInternal(ctx, "commit") + + // test for disable transaction local latch + defer config.RestoreFunc()() + config.UpdateGlobal(func(conf *config.Config) { + conf.TxnLocalLatches.Enabled = false + }) + tk1.MustExec("begin") + tk1.MustExec("update no_retry set id = 9") + + tk2.MustExec("update no_retry set id = 8") + + _, err = tk1.Session().Execute(context.Background(), "commit") + require.Error(t, err) + require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) + require.Contains(t, err.Error(), kv.TxnRetryableMark) + tk1.MustExec("rollback") + + config.UpdateGlobal(func(conf *config.Config) { + conf.TxnLocalLatches.Enabled = true + }) + tk1.MustExec("begin") + tk2.MustExec("alter table no_retry add index idx(id)") + tk2.MustQuery("select * from no_retry").Check(testkit.Rows("8")) + tk1.MustExec("update no_retry set id = 10") + _, err = tk1.Session().Execute(context.Background(), "commit") + require.Error(t, err) + + // set autocommit to begin and commit + tk1.MustExec("set autocommit = 0") + tk1.MustQuery("select * from no_retry").Check(testkit.Rows("8")) + tk2.MustExec("update no_retry set id = 11") + tk1.MustExec("update no_retry set id = 12") + _, err = tk1.Session().Execute(context.Background(), "set autocommit = 1") + require.Error(t, err) + require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) + require.Contains(t, err.Error(), kv.TxnRetryableMark) + tk1.MustExec("rollback") + tk2.MustQuery("select * from no_retry").Check(testkit.Rows("11")) + + tk1.MustExec("set autocommit = 0") + tk1.MustQuery("select * from no_retry").Check(testkit.Rows("11")) + tk2.MustExec("update no_retry set id = 13") + tk1.MustExec("update no_retry set id = 14") + _, err = tk1.Session().Execute(context.Background(), "commit") + require.Error(t, err) + require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) + require.Contains(t, err.Error(), kv.TxnRetryableMark) + tk1.MustExec("rollback") + tk2.MustQuery("select * from no_retry").Check(testkit.Rows("13")) +} + +// The Read-only flags are checked in the planning stage of queries, +// but this test checks we check them again at commit time. +// The main use case for this is a long-running auto-commit statement. +func TestAutoCommitRespectsReadOnly(t *testing.T) { + store := testkit.CreateMockStore(t) + var wg sync.WaitGroup + tk1 := testkit.NewTestKit(t, store) + tk2 := testkit.NewTestKit(t, store) + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil)) + require.NoError(t, tk2.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil)) + + tk1.MustExec("create table test.auto_commit_test (a int)") + wg.Add(1) + go func() { + err := tk1.ExecToErr("INSERT INTO test.auto_commit_test VALUES (SLEEP(1))") + require.True(t, terror.ErrorEqual(err, plannercore.ErrSQLInReadOnlyMode), fmt.Sprintf("err %v", err)) + wg.Done() + }() + tk2.MustExec("SET GLOBAL tidb_restricted_read_only = 1") + err := tk2.ExecToErr("INSERT INTO test.auto_commit_test VALUES (0)") // should also be an error + require.True(t, terror.ErrorEqual(err, plannercore.ErrSQLInReadOnlyMode), fmt.Sprintf("err %v", err)) + // Reset and check with the privilege to ignore the readonly flag and continue to insert. + wg.Wait() + tk1.MustExec("SET GLOBAL tidb_restricted_read_only = 0") + tk1.MustExec("SET GLOBAL tidb_super_read_only = 0") + tk1.MustExec("GRANT RESTRICTED_REPLICA_WRITER_ADMIN on *.* to 'root'") + + wg.Add(1) + go func() { + tk1.MustExec("INSERT INTO test.auto_commit_test VALUES (SLEEP(1))") + wg.Done() + }() + tk2.MustExec("SET GLOBAL tidb_restricted_read_only = 1") + tk2.MustExec("INSERT INTO test.auto_commit_test VALUES (0)") + + // wait for go routines + wg.Wait() + tk1.MustExec("SET GLOBAL tidb_restricted_read_only = 0") + tk1.MustExec("SET GLOBAL tidb_super_read_only = 0") +} + +func TestRetryForCurrentTxn(t *testing.T) { + store := testkit.CreateMockStore(t) + + setTxnTk := testkit.NewTestKit(t, store) + setTxnTk.MustExec("set global tidb_txn_mode=''") + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + tk.MustExec("create table history (a int)") + tk.MustExec("insert history values (1)") + + // Firstly, enable retry. + tk.MustExec("set tidb_disable_txn_auto_retry = 0") + tk.MustExec("begin") + tk.MustExec("update history set a = 2") + // Disable retry now. + tk.MustExec("set tidb_disable_txn_auto_retry = 1") + + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + tk1.MustExec("update history set a = 3") + + tk.MustExec("commit") + tk.MustQuery("select * from history").Check(testkit.Rows("2")) +} + +func TestBatchCommit(t *testing.T) { + store := testkit.CreateMockStore(t) + setTxnTk := testkit.NewTestKit(t, store) + setTxnTk.MustExec("set global tidb_txn_mode=''") + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set tidb_batch_commit = 1") + tk.MustExec("set tidb_disable_txn_auto_retry = 0") + tk.MustExec("create table t (id int)") + defer config.RestoreFunc()() + config.UpdateGlobal(func(conf *config.Config) { + conf.Performance.StmtCountLimit = 3 + }) + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + tk.MustExec("SET SESSION autocommit = 1") + tk.MustExec("begin") + tk.MustExec("insert into t values (1)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("insert into t values (2)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("rollback") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + + // The above rollback will not make the session in transaction. + tk.MustExec("insert into t values (1)") + tk1.MustQuery("select * from t").Check(testkit.Rows("1")) + tk.MustExec("delete from t") + + tk.MustExec("begin") + tk.MustExec("insert into t values (5)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("insert into t values (6)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("insert into t values (7)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + + tk.MustExec("delete from t") + tk.MustExec("commit") + tk.MustExec("begin") + tk.MustExec("explain analyze insert into t values (5)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("explain analyze insert into t values (6)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("explain analyze insert into t values (7)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + + // The session is still in transaction. + tk.MustExec("insert into t values (8)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + tk.MustExec("insert into t values (9)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + tk.MustExec("insert into t values (10)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + tk.MustExec("commit") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7", "8", "9", "10")) + + // The above commit will not make the session in transaction. + tk.MustExec("insert into t values (11)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7", "8", "9", "10", "11")) + + tk.MustExec("delete from t") + tk.MustExec("SET SESSION autocommit = 0") + tk.MustExec("insert into t values (1)") + tk.MustExec("insert into t values (2)") + tk.MustExec("insert into t values (3)") + tk.MustExec("rollback") + tk1.MustExec("insert into t values (4)") + tk1.MustExec("insert into t values (5)") + tk.MustQuery("select * from t").Check(testkit.Rows("4", "5")) +} + +func TestTxnRetryErrMsg(t *testing.T) { + store := testkit.CreateMockStore(t) + setTxnTk := testkit.NewTestKit(t, store) + setTxnTk.MustExec("set global tidb_txn_mode=''") + tk1 := testkit.NewTestKit(t, store) + tk2 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + tk1.MustExec("create table no_retry (id int)") + tk1.MustExec("insert into no_retry values (1)") + tk1.MustExec("begin") + tk2.MustExec("use test") + tk2.MustExec("update no_retry set id = id + 1") + tk1.MustExec("update no_retry set id = id + 1") + require.NoError(t, failpoint.Enable("tikvclient/mockRetryableErrorResp", `return(true)`)) + _, err := tk1.Session().Execute(context.Background(), "commit") + require.NoError(t, failpoint.Disable("tikvclient/mockRetryableErrorResp")) + require.Error(t, err) + require.True(t, kv.ErrTxnRetryable.Equal(err), "error: %s", err) + require.True(t, strings.Contains(err.Error(), "mock retryable error"), "error: %s", err) + require.True(t, strings.Contains(err.Error(), kv.TxnRetryableMark), "error: %s", err) +} + +func TestSetTxnScope(t *testing.T) { + // Check the default value of @@tidb_enable_local_txn and @@txn_scope whitout configuring the zone label. + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Check the default value of @@tidb_enable_local_txn and @@txn_scope with configuring the zone label. + require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) + tk = testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) + + // @@tidb_enable_local_txn is off without configuring the zone label. + tk = testkit.NewTestKit(t, store) + tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to local. + err := tk.ExecToErr("set @@txn_scope = 'local';") + require.Error(t, err) + require.Regexp(t, `.*txn_scope can not be set to local when tidb_enable_local_txn is off.*`, err) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to global. + tk.MustExec("set @@txn_scope = 'global';") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + + // @@tidb_enable_local_txn is off with configuring the zone label. + require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) + tk = testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to local. + err = tk.ExecToErr("set @@txn_scope = 'local';") + require.Error(t, err) + require.Regexp(t, `.*txn_scope can not be set to local when tidb_enable_local_txn is off.*`, err) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to global. + tk.MustExec("set @@txn_scope = 'global';") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) + + // @@tidb_enable_local_txn is on without configuring the zone label. + tk = testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set global tidb_enable_local_txn = on;") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to local. + err = tk.ExecToErr("set @@txn_scope = 'local';") + require.Error(t, err) + require.Regexp(t, `.*txn_scope can not be set to local when zone label is empty or "global".*`, err) + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to global. + tk.MustExec("set @@txn_scope = 'global';") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + + // @@tidb_enable_local_txn is on with configuring the zone label. + require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) + tk = testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set global tidb_enable_local_txn = on;") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.LocalTxnScope)) + require.Equal(t, "bj", tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to global. + tk.MustExec("set @@txn_scope = 'global';") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) + require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Set @@txn_scope to local. + tk.MustExec("set @@txn_scope = 'local';") + tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.LocalTxnScope)) + require.Equal(t, "bj", tk.Session().GetSessionVars().CheckAndGetTxnScope()) + // Try to set @@txn_scope to an invalid value. + err = tk.ExecToErr("set @@txn_scope='foo'") + require.Error(t, err) + require.Regexp(t, `.*txn_scope value should be global or local.*`, err) + require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) +} + +func TestErrorRollback(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t_rollback") + tk.MustExec("create table t_rollback (c1 int, c2 int, primary key(c1))") + tk.MustExec("insert into t_rollback values (0, 0)") + + var wg sync.WaitGroup + cnt := 4 + wg.Add(cnt) + num := 20 + + for i := 0; i < cnt; i++ { + go func() { + defer wg.Done() + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set @@session.tidb_retry_limit = 100") + for j := 0; j < num; j++ { + _, _ = tk.Exec("insert into t_rollback values (1, 1)") + tk.MustExec("update t_rollback set c2 = c2 + 1 where c1 = 0") + } + }() + } + + wg.Wait() + tk.MustQuery("select c2 from t_rollback where c1 = 0").Check(testkit.Rows(fmt.Sprint(cnt * num))) +} + +// TestInTrans . See https://dev.mysql.com/doc/internals/en/status-flags.html +func TestInTrans(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") + tk.MustExec("insert t values ()") + tk.MustExec("begin") + txn, err := tk.Session().Txn(true) + require.NoError(t, err) + require.True(t, txn.Valid()) + tk.MustExec("insert t values ()") + require.True(t, txn.Valid()) + tk.MustExec("drop table if exists t;") + require.False(t, txn.Valid()) + tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") + require.False(t, txn.Valid()) + tk.MustExec("insert t values ()") + require.False(t, txn.Valid()) + tk.MustExec("commit") + tk.MustExec("insert t values ()") + + tk.MustExec("set autocommit=0") + tk.MustExec("begin") + require.True(t, txn.Valid()) + tk.MustExec("insert t values ()") + require.True(t, txn.Valid()) + tk.MustExec("commit") + require.False(t, txn.Valid()) + tk.MustExec("insert t values ()") + require.True(t, txn.Valid()) + tk.MustExec("commit") + require.False(t, txn.Valid()) + + tk.MustExec("set autocommit=1") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") + tk.MustExec("begin") + require.True(t, txn.Valid()) + tk.MustExec("insert t values ()") + require.True(t, txn.Valid()) + tk.MustExec("rollback") + require.False(t, txn.Valid()) +} + +func TestCommitRetryCount(t *testing.T) { + store := testkit.CreateMockStore(t) + + setTxnTk := testkit.NewTestKit(t, store) + setTxnTk.MustExec("set global tidb_txn_mode=''") + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + tk2 := testkit.NewTestKit(t, store) + tk2.MustExec("use test") + + tk1.MustExec("create table no_retry (id int)") + tk1.MustExec("insert into no_retry values (1)") + tk1.MustExec("set @@tidb_retry_limit = 0") + + tk1.MustExec("begin") + tk1.MustExec("update no_retry set id = 2") + + tk2.MustExec("begin") + tk2.MustExec("update no_retry set id = 3") + tk2.MustExec("commit") + + // No auto retry because retry limit is set to 0. + require.Error(t, tk1.ExecToErr("commit")) +} diff --git a/server/server_test.go b/server/server_test.go index 2e6477d00ff56..a5aeb878a4bba 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -37,6 +37,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/log" +<<<<<<< HEAD:server/server_test.go "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/kv" tmysql "github.com/pingcap/tidb/parser/mysql" @@ -47,6 +48,16 @@ import ( "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/replayer" "github.com/pingcap/tidb/util/versioninfo" +======= + "github.com/pingcap/tidb/pkg/config" + "github.com/pingcap/tidb/pkg/errno" + "github.com/pingcap/tidb/pkg/kv" + tmysql "github.com/pingcap/tidb/pkg/parser/mysql" + "github.com/pingcap/tidb/pkg/server" + "github.com/pingcap/tidb/pkg/testkit" + "github.com/pingcap/tidb/pkg/testkit/testenv" + "github.com/pingcap/tidb/pkg/util/versioninfo" +>>>>>>> 9d6d6fd3da1 (session: fix select for update statement can't get stmt-count-limit error (#48412)):pkg/server/internal/testserverclient/server_client.go "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -2525,6 +2536,7 @@ func (cli *testServerClient) runTestInfoschemaClientErrors(t *testing.T) { }) } +<<<<<<< HEAD:server/server_test.go func TestIssue46197(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) @@ -2569,3 +2581,79 @@ func TestIssue46197(t *testing.T) { path := testdata.ConvertRowsToStrings(tk.MustQuery("select @@tidb_last_plan_replayer_token").Rows()) require.NoError(t, os.Remove(filepath.Join(replayer.GetPlanReplayerDirName(), path[0]))) } +======= +func (cli *TestServerClient) RunTestStmtCountLimit(t *testing.T) { + originalStmtCountLimit := config.GetGlobalConfig().Performance.StmtCountLimit + config.UpdateGlobal(func(conf *config.Config) { + conf.Performance.StmtCountLimit = 3 + }) + defer func() { + config.UpdateGlobal(func(conf *config.Config) { + conf.Performance.StmtCountLimit = originalStmtCountLimit + }) + }() + + cli.RunTests(t, nil, func(dbt *testkit.DBTestKit) { + dbt.MustExec("create table t (id int key);") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + _, err := dbt.GetDB().Query("select * from t for update;") + require.Error(t, err) + require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) + dbt.MustExec("insert into t values (3);") + dbt.MustExec("commit;") + rows := dbt.MustQuery("select * from t;") + var id int + count := 0 + for rows.Next() { + rows.Scan(&id) + count++ + } + require.NoError(t, rows.Close()) + require.Equal(t, 3, id) + require.Equal(t, 1, count) + + dbt.MustExec("delete from t;") + dbt.MustExec("commit;") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + _, err = dbt.GetDB().Exec("insert into t values (3);") + require.Error(t, err) + require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) + dbt.MustExec("commit;") + rows = dbt.MustQuery("select count(*) from t;") + for rows.Next() { + rows.Scan(&count) + } + require.NoError(t, rows.Close()) + require.Equal(t, 0, count) + + dbt.MustExec("delete from t;") + dbt.MustExec("commit;") + dbt.MustExec("set @@tidb_batch_commit=1;") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + dbt.MustExec("insert into t values (3);") + dbt.MustExec("insert into t values (4);") + dbt.MustExec("insert into t values (5);") + dbt.MustExec("commit;") + rows = dbt.MustQuery("select count(*) from t;") + for rows.Next() { + rows.Scan(&count) + } + require.NoError(t, rows.Close()) + require.Equal(t, 5, count) + }) +} + +//revive:enable:exported +>>>>>>> 9d6d6fd3da1 (session: fix select for update statement can't get stmt-count-limit error (#48412)):pkg/server/internal/testserverclient/server_client.go diff --git a/server/tidb_test.go b/server/tidb_test.go index b51aa67ccd25c..569684be80920 100644 --- a/server/tidb_test.go +++ b/server/tidb_test.go @@ -1122,6 +1122,11 @@ func TestSumAvg(t *testing.T) { ts.runTestSumAvg(t) } +func TestStmtCountLimit(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestStmtCountLimit(t) +} + func TestNullFlag(t *testing.T) { ts := createTidbTestSuite(t) diff --git a/session/session.go b/session/session.go index 5ccae09f21d35..003467fec3c3a 100644 --- a/session/session.go +++ b/session/session.go @@ -2399,6 +2399,14 @@ func runStmt(ctx context.Context, se *session, s sqlexec.Statement) (rs sqlexec. if err != nil { return nil, err } + if sessVars.TxnCtx.CouldRetry && !s.IsReadOnly(sessVars) { + // Only when the txn is could retry and the statement is not read only, need to do stmt-count-limit check, + // otherwise, the stmt won't be add into stmt history, and also don't need check. + // About `stmt-count-limit`, see more in https://docs.pingcap.com/tidb/stable/tidb-configuration-file#stmt-count-limit + if err := checkStmtLimit(ctx, se, false); err != nil { + return nil, err + } + } rs, err = s.Exec(ctx) se.updateTelemetryMetric(s.(*executor.ExecStmt)) diff --git a/session/tidb.go b/session/tidb.go index 310d76e007e5a..dad6d14daf41a 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -271,7 +271,7 @@ func finishStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.St if err != nil { return err } - return checkStmtLimit(ctx, se) + return checkStmtLimit(ctx, se, true) } func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.Statement) error { @@ -305,18 +305,29 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s return nil } -func checkStmtLimit(ctx context.Context, se *session) error { +func checkStmtLimit(ctx context.Context, se *session, isFinish bool) error { // If the user insert, insert, insert ... but never commit, TiDB would OOM. // So we limit the statement count in a transaction here. var err error sessVars := se.GetSessionVars() history := GetHistory(se) - if history.Count() > int(config.GetGlobalConfig().Performance.StmtCountLimit) { + stmtCount := history.Count() + if !isFinish { + // history stmt count + current stmt, since current stmt is not finish, it has not add to history. + stmtCount++ + } + if stmtCount > int(config.GetGlobalConfig().Performance.StmtCountLimit) { if !sessVars.BatchCommit { se.RollbackTxn(ctx) - return errors.Errorf("statement count %d exceeds the transaction limitation, autocommit = %t", - history.Count(), sessVars.IsAutocommit()) + return errors.Errorf("statement count %d exceeds the transaction limitation, transaction has been rollback, autocommit = %t", + stmtCount, sessVars.IsAutocommit()) + } + if !isFinish { + // if the stmt is not finish execute, then just return, since some work need to be done such as StmtCommit. + return nil } + // If the stmt is finish execute, and exceed the StmtCountLimit, and BatchCommit is true, + // then commit the current transaction and create a new transaction. err = sessiontxn.NewTxn(ctx, se) // The transaction does not committed yet, we need to keep it in transaction. // The last history could not be "commit"/"rollback" statement. @@ -328,6 +339,7 @@ func checkStmtLimit(ctx context.Context, se *session) error { } // GetHistory get all stmtHistory in current txn. Exported only for test. +// If stmtHistory is nil, will create a new one for current txn. func GetHistory(ctx sessionctx.Context) *StmtHistory { hist, ok := ctx.GetSessionVars().TxnCtx.History.(*StmtHistory) if ok { From aca8cfdce99a9381eac12f8e1959c6d80e4d2ea7 Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Thu, 23 Nov 2023 16:51:39 +0800 Subject: [PATCH 2/3] Revert "This is an automated cherry-pick of #48412" This reverts commit 4e10fec132f182f67e7f8a77ef9a7065d34780a4. Signed-off-by: crazycs520 --- .../internal/testserverclient/BUILD.bazel | 24 - pkg/session/test/txn/txn_test.go | 631 ------------------ server/server_test.go | 88 --- server/tidb_test.go | 5 - session/session.go | 8 - session/tidb.go | 22 +- 6 files changed, 5 insertions(+), 773 deletions(-) delete mode 100644 pkg/server/internal/testserverclient/BUILD.bazel delete mode 100644 pkg/session/test/txn/txn_test.go diff --git a/pkg/server/internal/testserverclient/BUILD.bazel b/pkg/server/internal/testserverclient/BUILD.bazel deleted file mode 100644 index b29f420f7e1f5..0000000000000 --- a/pkg/server/internal/testserverclient/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "testserverclient", - srcs = ["server_client.go"], - importpath = "github.com/pingcap/tidb/pkg/server/internal/testserverclient", - visibility = ["//pkg/server:__subpackages__"], - deps = [ - "//pkg/config", - "//pkg/errno", - "//pkg/kv", - "//pkg/parser/mysql", - "//pkg/server", - "//pkg/testkit", - "//pkg/testkit/testenv", - "//pkg/util/versioninfo", - "@com_github_go_sql_driver_mysql//:mysql", - "@com_github_pingcap_errors//:errors", - "@com_github_pingcap_failpoint//:failpoint", - "@com_github_pingcap_log//:log", - "@com_github_stretchr_testify//require", - "@org_uber_go_zap//:zap", - ], -) diff --git a/pkg/session/test/txn/txn_test.go b/pkg/session/test/txn/txn_test.go deleted file mode 100644 index 3f5893157ea34..0000000000000 --- a/pkg/session/test/txn/txn_test.go +++ /dev/null @@ -1,631 +0,0 @@ -// Copyright 2023 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 txn - -import ( - "context" - "fmt" - "strings" - "sync" - "testing" - "time" - - "github.com/pingcap/failpoint" - "github.com/pingcap/tidb/pkg/config" - "github.com/pingcap/tidb/pkg/kv" - "github.com/pingcap/tidb/pkg/parser/auth" - "github.com/pingcap/tidb/pkg/parser/mysql" - "github.com/pingcap/tidb/pkg/parser/terror" - plannercore "github.com/pingcap/tidb/pkg/planner/core" - "github.com/pingcap/tidb/pkg/testkit" - "github.com/stretchr/testify/require" -) - -// TestAutocommit . See https://dev.mysql.com/doc/internals/en/status-flags.html -func TestAutocommit(t *testing.T) { - store := testkit.CreateMockStore(t) - - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - - tk.MustExec("drop table if exists t;") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("insert t values ()") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("begin") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("insert t values ()") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("drop table if exists t") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - - tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - tk.MustExec("set autocommit=0") - require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) - tk.MustExec("insert t values ()") - require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) - tk.MustExec("commit") - require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) - tk.MustExec("drop table if exists t") - require.Equal(t, 0, int(tk.Session().Status()&mysql.ServerStatusAutocommit)) - tk.MustExec("set autocommit='On'") - require.Greater(t, int(tk.Session().Status()&mysql.ServerStatusAutocommit), 0) - - // When autocommit is 0, transaction start ts should be the first *valid* - // statement, rather than *any* statement. - tk.MustExec("create table t (id int key)") - tk.MustExec("set @@autocommit = 0") - tk.MustExec("rollback") - tk.MustExec("set @@autocommit = 0") - - tk1 := testkit.NewTestKit(t, store) - tk1.MustExec("use test") - tk1.MustExec("insert into t select 1") - //nolint:all_revive,revive - tk.MustQuery("select * from t").Check(testkit.Rows("1")) - tk.MustExec("delete from t") - - // When the transaction is rolled back, the global set statement would succeed. - tk.MustExec("set @@global.autocommit = 0") - tk.MustExec("begin") - tk.MustExec("insert into t values (1)") - tk.MustExec("set @@global.autocommit = 1") - tk.MustExec("rollback") - tk.MustQuery("select count(*) from t where id = 1").Check(testkit.Rows("0")) - tk.MustQuery("select @@global.autocommit").Check(testkit.Rows("1")) - - // When the transaction is committed because of switching mode, the session set statement shold succeed. - tk.MustExec("set autocommit = 0") - tk.MustExec("begin") - tk.MustExec("insert into t values (1)") - tk.MustExec("set autocommit = 1") - tk.MustExec("rollback") - tk.MustQuery("select count(*) from t where id = 1").Check(testkit.Rows("1")) - tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) - - tk.MustExec("set autocommit = 0") - tk.MustExec("insert into t values (2)") - tk.MustExec("set autocommit = 1") - tk.MustExec("rollback") - tk.MustQuery("select count(*) from t where id = 2").Check(testkit.Rows("1")) - tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) - - // Set should not take effect if the mode is not changed. - tk.MustExec("set autocommit = 0") - tk.MustExec("begin") - tk.MustExec("insert into t values (3)") - tk.MustExec("set autocommit = 0") - tk.MustExec("rollback") - tk.MustQuery("select count(*) from t where id = 3").Check(testkit.Rows("0")) - tk.MustQuery("select @@autocommit").Check(testkit.Rows("0")) - - tk.MustExec("set autocommit = 1") - tk.MustExec("begin") - tk.MustExec("insert into t values (4)") - tk.MustExec("set autocommit = 1") - tk.MustExec("rollback") - tk.MustQuery("select count(*) from t where id = 4").Check(testkit.Rows("0")) - tk.MustQuery("select @@autocommit").Check(testkit.Rows("1")) -} - -// TestTxnLazyInitialize tests that when autocommit = 0, not all statement starts -// a new transaction. -func TestTxnLazyInitialize(t *testing.T) { - testTxnLazyInitialize(t, false) - testTxnLazyInitialize(t, true) -} - -func testTxnLazyInitialize(t *testing.T, isPessimistic bool) { - store := testkit.CreateMockStore(t) - - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("drop table if exists t") - tk.MustExec("create table t (id int)") - if isPessimistic { - tk.MustExec("set tidb_txn_mode = 'pessimistic'") - } - - tk.MustExec("set @@autocommit = 0") - _, err := tk.Session().Txn(true) - require.True(t, kv.ErrInvalidTxn.Equal(err)) - txn, err := tk.Session().Txn(false) - require.NoError(t, err) - require.False(t, txn.Valid()) - tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) - tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) - - // Those statements should not start a new transaction automatically. - tk.MustQuery("select 1") - tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) - - tk.MustExec("set @@tidb_general_log = 0") - tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) - - tk.MustQuery("explain select * from t") - tk.MustQuery("select @@tidb_current_ts").Check(testkit.Rows("0")) - - // Begin statement should start a new transaction. - tk.MustExec("begin") - txn, err = tk.Session().Txn(false) - require.NoError(t, err) - require.True(t, txn.Valid()) - tk.MustExec("rollback") - - tk.MustExec("select * from t") - txn, err = tk.Session().Txn(false) - require.NoError(t, err) - require.True(t, txn.Valid()) - tk.MustExec("rollback") - - tk.MustExec("insert into t values (1)") - txn, err = tk.Session().Txn(false) - require.NoError(t, err) - require.True(t, txn.Valid()) - tk.MustExec("rollback") -} - -func TestDisableTxnAutoRetry(t *testing.T) { - store := testkit.CreateMockStoreWithSchemaLease(t, 1*time.Second) - - setTxnTk := testkit.NewTestKit(t, store) - setTxnTk.MustExec("set global tidb_txn_mode=''") - tk1 := testkit.NewTestKit(t, store) - tk2 := testkit.NewTestKit(t, store) - - tk1.MustExec("use test") - tk2.MustExec("use test") - - tk1.MustExec("create table no_retry (id int)") - tk1.MustExec("insert into no_retry values (1)") - tk1.MustExec("set @@tidb_disable_txn_auto_retry = 1") - - tk1.MustExec("begin") - tk1.MustExec("update no_retry set id = 2") - - tk2.MustExec("begin") - tk2.MustExec("update no_retry set id = 3") - tk2.MustExec("commit") - - // No auto retry because tidb_disable_txn_auto_retry is set to 1. - _, err := tk1.Session().Execute(context.Background(), "commit") - require.Error(t, err) - - // session 1 starts a transaction early. - // execute a select statement to clear retry history. - tk1.MustExec("select 1") - err = tk1.Session().PrepareTxnCtx(context.Background()) - require.NoError(t, err) - // session 2 update the value. - tk2.MustExec("update no_retry set id = 4") - // AutoCommit update will retry, so it would not fail. - tk1.MustExec("update no_retry set id = 5") - - // RestrictedSQL should retry. - ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnOthers) - tk1.Session().ExecuteInternal(ctx, "begin") - - tk2.MustExec("update no_retry set id = 6") - - tk1.Session().ExecuteInternal(ctx, "update no_retry set id = 7") - tk1.Session().ExecuteInternal(ctx, "commit") - - // test for disable transaction local latch - defer config.RestoreFunc()() - config.UpdateGlobal(func(conf *config.Config) { - conf.TxnLocalLatches.Enabled = false - }) - tk1.MustExec("begin") - tk1.MustExec("update no_retry set id = 9") - - tk2.MustExec("update no_retry set id = 8") - - _, err = tk1.Session().Execute(context.Background(), "commit") - require.Error(t, err) - require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) - require.Contains(t, err.Error(), kv.TxnRetryableMark) - tk1.MustExec("rollback") - - config.UpdateGlobal(func(conf *config.Config) { - conf.TxnLocalLatches.Enabled = true - }) - tk1.MustExec("begin") - tk2.MustExec("alter table no_retry add index idx(id)") - tk2.MustQuery("select * from no_retry").Check(testkit.Rows("8")) - tk1.MustExec("update no_retry set id = 10") - _, err = tk1.Session().Execute(context.Background(), "commit") - require.Error(t, err) - - // set autocommit to begin and commit - tk1.MustExec("set autocommit = 0") - tk1.MustQuery("select * from no_retry").Check(testkit.Rows("8")) - tk2.MustExec("update no_retry set id = 11") - tk1.MustExec("update no_retry set id = 12") - _, err = tk1.Session().Execute(context.Background(), "set autocommit = 1") - require.Error(t, err) - require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) - require.Contains(t, err.Error(), kv.TxnRetryableMark) - tk1.MustExec("rollback") - tk2.MustQuery("select * from no_retry").Check(testkit.Rows("11")) - - tk1.MustExec("set autocommit = 0") - tk1.MustQuery("select * from no_retry").Check(testkit.Rows("11")) - tk2.MustExec("update no_retry set id = 13") - tk1.MustExec("update no_retry set id = 14") - _, err = tk1.Session().Execute(context.Background(), "commit") - require.Error(t, err) - require.True(t, kv.ErrWriteConflict.Equal(err), fmt.Sprintf("err %v", err)) - require.Contains(t, err.Error(), kv.TxnRetryableMark) - tk1.MustExec("rollback") - tk2.MustQuery("select * from no_retry").Check(testkit.Rows("13")) -} - -// The Read-only flags are checked in the planning stage of queries, -// but this test checks we check them again at commit time. -// The main use case for this is a long-running auto-commit statement. -func TestAutoCommitRespectsReadOnly(t *testing.T) { - store := testkit.CreateMockStore(t) - var wg sync.WaitGroup - tk1 := testkit.NewTestKit(t, store) - tk2 := testkit.NewTestKit(t, store) - require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil)) - require.NoError(t, tk2.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil)) - - tk1.MustExec("create table test.auto_commit_test (a int)") - wg.Add(1) - go func() { - err := tk1.ExecToErr("INSERT INTO test.auto_commit_test VALUES (SLEEP(1))") - require.True(t, terror.ErrorEqual(err, plannercore.ErrSQLInReadOnlyMode), fmt.Sprintf("err %v", err)) - wg.Done() - }() - tk2.MustExec("SET GLOBAL tidb_restricted_read_only = 1") - err := tk2.ExecToErr("INSERT INTO test.auto_commit_test VALUES (0)") // should also be an error - require.True(t, terror.ErrorEqual(err, plannercore.ErrSQLInReadOnlyMode), fmt.Sprintf("err %v", err)) - // Reset and check with the privilege to ignore the readonly flag and continue to insert. - wg.Wait() - tk1.MustExec("SET GLOBAL tidb_restricted_read_only = 0") - tk1.MustExec("SET GLOBAL tidb_super_read_only = 0") - tk1.MustExec("GRANT RESTRICTED_REPLICA_WRITER_ADMIN on *.* to 'root'") - - wg.Add(1) - go func() { - tk1.MustExec("INSERT INTO test.auto_commit_test VALUES (SLEEP(1))") - wg.Done() - }() - tk2.MustExec("SET GLOBAL tidb_restricted_read_only = 1") - tk2.MustExec("INSERT INTO test.auto_commit_test VALUES (0)") - - // wait for go routines - wg.Wait() - tk1.MustExec("SET GLOBAL tidb_restricted_read_only = 0") - tk1.MustExec("SET GLOBAL tidb_super_read_only = 0") -} - -func TestRetryForCurrentTxn(t *testing.T) { - store := testkit.CreateMockStore(t) - - setTxnTk := testkit.NewTestKit(t, store) - setTxnTk.MustExec("set global tidb_txn_mode=''") - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - - tk.MustExec("create table history (a int)") - tk.MustExec("insert history values (1)") - - // Firstly, enable retry. - tk.MustExec("set tidb_disable_txn_auto_retry = 0") - tk.MustExec("begin") - tk.MustExec("update history set a = 2") - // Disable retry now. - tk.MustExec("set tidb_disable_txn_auto_retry = 1") - - tk1 := testkit.NewTestKit(t, store) - tk1.MustExec("use test") - tk1.MustExec("update history set a = 3") - - tk.MustExec("commit") - tk.MustQuery("select * from history").Check(testkit.Rows("2")) -} - -func TestBatchCommit(t *testing.T) { - store := testkit.CreateMockStore(t) - setTxnTk := testkit.NewTestKit(t, store) - setTxnTk.MustExec("set global tidb_txn_mode=''") - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set tidb_batch_commit = 1") - tk.MustExec("set tidb_disable_txn_auto_retry = 0") - tk.MustExec("create table t (id int)") - defer config.RestoreFunc()() - config.UpdateGlobal(func(conf *config.Config) { - conf.Performance.StmtCountLimit = 3 - }) - tk1 := testkit.NewTestKit(t, store) - tk1.MustExec("use test") - tk.MustExec("SET SESSION autocommit = 1") - tk.MustExec("begin") - tk.MustExec("insert into t values (1)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("insert into t values (2)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("rollback") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - - // The above rollback will not make the session in transaction. - tk.MustExec("insert into t values (1)") - tk1.MustQuery("select * from t").Check(testkit.Rows("1")) - tk.MustExec("delete from t") - - tk.MustExec("begin") - tk.MustExec("insert into t values (5)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("insert into t values (6)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("insert into t values (7)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) - - tk.MustExec("delete from t") - tk.MustExec("commit") - tk.MustExec("begin") - tk.MustExec("explain analyze insert into t values (5)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("explain analyze insert into t values (6)") - tk1.MustQuery("select * from t").Check(testkit.Rows()) - tk.MustExec("explain analyze insert into t values (7)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) - - // The session is still in transaction. - tk.MustExec("insert into t values (8)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) - tk.MustExec("insert into t values (9)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) - tk.MustExec("insert into t values (10)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) - tk.MustExec("commit") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7", "8", "9", "10")) - - // The above commit will not make the session in transaction. - tk.MustExec("insert into t values (11)") - tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7", "8", "9", "10", "11")) - - tk.MustExec("delete from t") - tk.MustExec("SET SESSION autocommit = 0") - tk.MustExec("insert into t values (1)") - tk.MustExec("insert into t values (2)") - tk.MustExec("insert into t values (3)") - tk.MustExec("rollback") - tk1.MustExec("insert into t values (4)") - tk1.MustExec("insert into t values (5)") - tk.MustQuery("select * from t").Check(testkit.Rows("4", "5")) -} - -func TestTxnRetryErrMsg(t *testing.T) { - store := testkit.CreateMockStore(t) - setTxnTk := testkit.NewTestKit(t, store) - setTxnTk.MustExec("set global tidb_txn_mode=''") - tk1 := testkit.NewTestKit(t, store) - tk2 := testkit.NewTestKit(t, store) - tk1.MustExec("use test") - tk1.MustExec("create table no_retry (id int)") - tk1.MustExec("insert into no_retry values (1)") - tk1.MustExec("begin") - tk2.MustExec("use test") - tk2.MustExec("update no_retry set id = id + 1") - tk1.MustExec("update no_retry set id = id + 1") - require.NoError(t, failpoint.Enable("tikvclient/mockRetryableErrorResp", `return(true)`)) - _, err := tk1.Session().Execute(context.Background(), "commit") - require.NoError(t, failpoint.Disable("tikvclient/mockRetryableErrorResp")) - require.Error(t, err) - require.True(t, kv.ErrTxnRetryable.Equal(err), "error: %s", err) - require.True(t, strings.Contains(err.Error(), "mock retryable error"), "error: %s", err) - require.True(t, strings.Contains(err.Error(), kv.TxnRetryableMark), "error: %s", err) -} - -func TestSetTxnScope(t *testing.T) { - // Check the default value of @@tidb_enable_local_txn and @@txn_scope whitout configuring the zone label. - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Check the default value of @@tidb_enable_local_txn and @@txn_scope with configuring the zone label. - require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) - tk = testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) - - // @@tidb_enable_local_txn is off without configuring the zone label. - tk = testkit.NewTestKit(t, store) - tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to local. - err := tk.ExecToErr("set @@txn_scope = 'local';") - require.Error(t, err) - require.Regexp(t, `.*txn_scope can not be set to local when tidb_enable_local_txn is off.*`, err) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to global. - tk.MustExec("set @@txn_scope = 'global';") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - - // @@tidb_enable_local_txn is off with configuring the zone label. - require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) - tk = testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustQuery("select @@global.tidb_enable_local_txn;").Check(testkit.Rows("0")) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to local. - err = tk.ExecToErr("set @@txn_scope = 'local';") - require.Error(t, err) - require.Regexp(t, `.*txn_scope can not be set to local when tidb_enable_local_txn is off.*`, err) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to global. - tk.MustExec("set @@txn_scope = 'global';") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) - - // @@tidb_enable_local_txn is on without configuring the zone label. - tk = testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set global tidb_enable_local_txn = on;") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to local. - err = tk.ExecToErr("set @@txn_scope = 'local';") - require.Error(t, err) - require.Regexp(t, `.*txn_scope can not be set to local when zone label is empty or "global".*`, err) - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to global. - tk.MustExec("set @@txn_scope = 'global';") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - - // @@tidb_enable_local_txn is on with configuring the zone label. - require.NoError(t, failpoint.Enable("tikvclient/injectTxnScope", `return("bj")`)) - tk = testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set global tidb_enable_local_txn = on;") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.LocalTxnScope)) - require.Equal(t, "bj", tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to global. - tk.MustExec("set @@txn_scope = 'global';") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.GlobalTxnScope)) - require.Equal(t, kv.GlobalTxnScope, tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Set @@txn_scope to local. - tk.MustExec("set @@txn_scope = 'local';") - tk.MustQuery("select @@txn_scope;").Check(testkit.Rows(kv.LocalTxnScope)) - require.Equal(t, "bj", tk.Session().GetSessionVars().CheckAndGetTxnScope()) - // Try to set @@txn_scope to an invalid value. - err = tk.ExecToErr("set @@txn_scope='foo'") - require.Error(t, err) - require.Regexp(t, `.*txn_scope value should be global or local.*`, err) - require.NoError(t, failpoint.Disable("tikvclient/injectTxnScope")) -} - -func TestErrorRollback(t *testing.T) { - store := testkit.CreateMockStore(t) - - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("drop table if exists t_rollback") - tk.MustExec("create table t_rollback (c1 int, c2 int, primary key(c1))") - tk.MustExec("insert into t_rollback values (0, 0)") - - var wg sync.WaitGroup - cnt := 4 - wg.Add(cnt) - num := 20 - - for i := 0; i < cnt; i++ { - go func() { - defer wg.Done() - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set @@session.tidb_retry_limit = 100") - for j := 0; j < num; j++ { - _, _ = tk.Exec("insert into t_rollback values (1, 1)") - tk.MustExec("update t_rollback set c2 = c2 + 1 where c1 = 0") - } - }() - } - - wg.Wait() - tk.MustQuery("select c2 from t_rollback where c1 = 0").Check(testkit.Rows(fmt.Sprint(cnt * num))) -} - -// TestInTrans . See https://dev.mysql.com/doc/internals/en/status-flags.html -func TestInTrans(t *testing.T) { - store := testkit.CreateMockStore(t) - - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("drop table if exists t;") - tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") - tk.MustExec("insert t values ()") - tk.MustExec("begin") - txn, err := tk.Session().Txn(true) - require.NoError(t, err) - require.True(t, txn.Valid()) - tk.MustExec("insert t values ()") - require.True(t, txn.Valid()) - tk.MustExec("drop table if exists t;") - require.False(t, txn.Valid()) - tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") - require.False(t, txn.Valid()) - tk.MustExec("insert t values ()") - require.False(t, txn.Valid()) - tk.MustExec("commit") - tk.MustExec("insert t values ()") - - tk.MustExec("set autocommit=0") - tk.MustExec("begin") - require.True(t, txn.Valid()) - tk.MustExec("insert t values ()") - require.True(t, txn.Valid()) - tk.MustExec("commit") - require.False(t, txn.Valid()) - tk.MustExec("insert t values ()") - require.True(t, txn.Valid()) - tk.MustExec("commit") - require.False(t, txn.Valid()) - - tk.MustExec("set autocommit=1") - tk.MustExec("drop table if exists t") - tk.MustExec("create table t (id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL)") - tk.MustExec("begin") - require.True(t, txn.Valid()) - tk.MustExec("insert t values ()") - require.True(t, txn.Valid()) - tk.MustExec("rollback") - require.False(t, txn.Valid()) -} - -func TestCommitRetryCount(t *testing.T) { - store := testkit.CreateMockStore(t) - - setTxnTk := testkit.NewTestKit(t, store) - setTxnTk.MustExec("set global tidb_txn_mode=''") - tk1 := testkit.NewTestKit(t, store) - tk1.MustExec("use test") - tk2 := testkit.NewTestKit(t, store) - tk2.MustExec("use test") - - tk1.MustExec("create table no_retry (id int)") - tk1.MustExec("insert into no_retry values (1)") - tk1.MustExec("set @@tidb_retry_limit = 0") - - tk1.MustExec("begin") - tk1.MustExec("update no_retry set id = 2") - - tk2.MustExec("begin") - tk2.MustExec("update no_retry set id = 3") - tk2.MustExec("commit") - - // No auto retry because retry limit is set to 0. - require.Error(t, tk1.ExecToErr("commit")) -} diff --git a/server/server_test.go b/server/server_test.go index a5aeb878a4bba..2e6477d00ff56 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -37,7 +37,6 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/log" -<<<<<<< HEAD:server/server_test.go "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/kv" tmysql "github.com/pingcap/tidb/parser/mysql" @@ -48,16 +47,6 @@ import ( "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/replayer" "github.com/pingcap/tidb/util/versioninfo" -======= - "github.com/pingcap/tidb/pkg/config" - "github.com/pingcap/tidb/pkg/errno" - "github.com/pingcap/tidb/pkg/kv" - tmysql "github.com/pingcap/tidb/pkg/parser/mysql" - "github.com/pingcap/tidb/pkg/server" - "github.com/pingcap/tidb/pkg/testkit" - "github.com/pingcap/tidb/pkg/testkit/testenv" - "github.com/pingcap/tidb/pkg/util/versioninfo" ->>>>>>> 9d6d6fd3da1 (session: fix select for update statement can't get stmt-count-limit error (#48412)):pkg/server/internal/testserverclient/server_client.go "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -2536,7 +2525,6 @@ func (cli *testServerClient) runTestInfoschemaClientErrors(t *testing.T) { }) } -<<<<<<< HEAD:server/server_test.go func TestIssue46197(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) @@ -2581,79 +2569,3 @@ func TestIssue46197(t *testing.T) { path := testdata.ConvertRowsToStrings(tk.MustQuery("select @@tidb_last_plan_replayer_token").Rows()) require.NoError(t, os.Remove(filepath.Join(replayer.GetPlanReplayerDirName(), path[0]))) } -======= -func (cli *TestServerClient) RunTestStmtCountLimit(t *testing.T) { - originalStmtCountLimit := config.GetGlobalConfig().Performance.StmtCountLimit - config.UpdateGlobal(func(conf *config.Config) { - conf.Performance.StmtCountLimit = 3 - }) - defer func() { - config.UpdateGlobal(func(conf *config.Config) { - conf.Performance.StmtCountLimit = originalStmtCountLimit - }) - }() - - cli.RunTests(t, nil, func(dbt *testkit.DBTestKit) { - dbt.MustExec("create table t (id int key);") - dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") - dbt.MustExec("set autocommit=0;") - dbt.MustExec("begin optimistic;") - dbt.MustExec("insert into t values (1);") - dbt.MustExec("insert into t values (2);") - _, err := dbt.GetDB().Query("select * from t for update;") - require.Error(t, err) - require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) - dbt.MustExec("insert into t values (3);") - dbt.MustExec("commit;") - rows := dbt.MustQuery("select * from t;") - var id int - count := 0 - for rows.Next() { - rows.Scan(&id) - count++ - } - require.NoError(t, rows.Close()) - require.Equal(t, 3, id) - require.Equal(t, 1, count) - - dbt.MustExec("delete from t;") - dbt.MustExec("commit;") - dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") - dbt.MustExec("set autocommit=0;") - dbt.MustExec("begin optimistic;") - dbt.MustExec("insert into t values (1);") - dbt.MustExec("insert into t values (2);") - _, err = dbt.GetDB().Exec("insert into t values (3);") - require.Error(t, err) - require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) - dbt.MustExec("commit;") - rows = dbt.MustQuery("select count(*) from t;") - for rows.Next() { - rows.Scan(&count) - } - require.NoError(t, rows.Close()) - require.Equal(t, 0, count) - - dbt.MustExec("delete from t;") - dbt.MustExec("commit;") - dbt.MustExec("set @@tidb_batch_commit=1;") - dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") - dbt.MustExec("set autocommit=0;") - dbt.MustExec("begin optimistic;") - dbt.MustExec("insert into t values (1);") - dbt.MustExec("insert into t values (2);") - dbt.MustExec("insert into t values (3);") - dbt.MustExec("insert into t values (4);") - dbt.MustExec("insert into t values (5);") - dbt.MustExec("commit;") - rows = dbt.MustQuery("select count(*) from t;") - for rows.Next() { - rows.Scan(&count) - } - require.NoError(t, rows.Close()) - require.Equal(t, 5, count) - }) -} - -//revive:enable:exported ->>>>>>> 9d6d6fd3da1 (session: fix select for update statement can't get stmt-count-limit error (#48412)):pkg/server/internal/testserverclient/server_client.go diff --git a/server/tidb_test.go b/server/tidb_test.go index 569684be80920..b51aa67ccd25c 100644 --- a/server/tidb_test.go +++ b/server/tidb_test.go @@ -1122,11 +1122,6 @@ func TestSumAvg(t *testing.T) { ts.runTestSumAvg(t) } -func TestStmtCountLimit(t *testing.T) { - ts := createTidbTestSuite(t) - ts.RunTestStmtCountLimit(t) -} - func TestNullFlag(t *testing.T) { ts := createTidbTestSuite(t) diff --git a/session/session.go b/session/session.go index 003467fec3c3a..5ccae09f21d35 100644 --- a/session/session.go +++ b/session/session.go @@ -2399,14 +2399,6 @@ func runStmt(ctx context.Context, se *session, s sqlexec.Statement) (rs sqlexec. if err != nil { return nil, err } - if sessVars.TxnCtx.CouldRetry && !s.IsReadOnly(sessVars) { - // Only when the txn is could retry and the statement is not read only, need to do stmt-count-limit check, - // otherwise, the stmt won't be add into stmt history, and also don't need check. - // About `stmt-count-limit`, see more in https://docs.pingcap.com/tidb/stable/tidb-configuration-file#stmt-count-limit - if err := checkStmtLimit(ctx, se, false); err != nil { - return nil, err - } - } rs, err = s.Exec(ctx) se.updateTelemetryMetric(s.(*executor.ExecStmt)) diff --git a/session/tidb.go b/session/tidb.go index dad6d14daf41a..310d76e007e5a 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -271,7 +271,7 @@ func finishStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.St if err != nil { return err } - return checkStmtLimit(ctx, se, true) + return checkStmtLimit(ctx, se) } func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.Statement) error { @@ -305,29 +305,18 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s return nil } -func checkStmtLimit(ctx context.Context, se *session, isFinish bool) error { +func checkStmtLimit(ctx context.Context, se *session) error { // If the user insert, insert, insert ... but never commit, TiDB would OOM. // So we limit the statement count in a transaction here. var err error sessVars := se.GetSessionVars() history := GetHistory(se) - stmtCount := history.Count() - if !isFinish { - // history stmt count + current stmt, since current stmt is not finish, it has not add to history. - stmtCount++ - } - if stmtCount > int(config.GetGlobalConfig().Performance.StmtCountLimit) { + if history.Count() > int(config.GetGlobalConfig().Performance.StmtCountLimit) { if !sessVars.BatchCommit { se.RollbackTxn(ctx) - return errors.Errorf("statement count %d exceeds the transaction limitation, transaction has been rollback, autocommit = %t", - stmtCount, sessVars.IsAutocommit()) - } - if !isFinish { - // if the stmt is not finish execute, then just return, since some work need to be done such as StmtCommit. - return nil + return errors.Errorf("statement count %d exceeds the transaction limitation, autocommit = %t", + history.Count(), sessVars.IsAutocommit()) } - // If the stmt is finish execute, and exceed the StmtCountLimit, and BatchCommit is true, - // then commit the current transaction and create a new transaction. err = sessiontxn.NewTxn(ctx, se) // The transaction does not committed yet, we need to keep it in transaction. // The last history could not be "commit"/"rollback" statement. @@ -339,7 +328,6 @@ func checkStmtLimit(ctx context.Context, se *session, isFinish bool) error { } // GetHistory get all stmtHistory in current txn. Exported only for test. -// If stmtHistory is nil, will create a new one for current txn. func GetHistory(ctx sessionctx.Context) *StmtHistory { hist, ok := ctx.GetSessionVars().TxnCtx.History.(*StmtHistory) if ok { From b8e19423dfbe600a3d0cba96097ee59d94aa2d2c Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Thu, 23 Nov 2023 17:00:03 +0800 Subject: [PATCH 3/3] session: fix select for update statement can't get stmt-count-limit error (#48412) Signed-off-by: crazycs520 --- server/server_test.go | 74 +++++++++++++++++++++++++++++ server/tidb_test.go | 5 ++ session/session.go | 8 ++++ session/sessiontest/session_test.go | 10 ++++ session/tidb.go | 22 +++++++-- 5 files changed, 114 insertions(+), 5 deletions(-) diff --git a/server/server_test.go b/server/server_test.go index 2e6477d00ff56..a54fcd18ed2e1 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -37,6 +37,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/log" + "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/kv" tmysql "github.com/pingcap/tidb/parser/mysql" @@ -2569,3 +2570,76 @@ func TestIssue46197(t *testing.T) { path := testdata.ConvertRowsToStrings(tk.MustQuery("select @@tidb_last_plan_replayer_token").Rows()) require.NoError(t, os.Remove(filepath.Join(replayer.GetPlanReplayerDirName(), path[0]))) } + +func (cli *testServerClient) RunTestStmtCountLimit(t *testing.T) { + originalStmtCountLimit := config.GetGlobalConfig().Performance.StmtCountLimit + config.UpdateGlobal(func(conf *config.Config) { + conf.Performance.StmtCountLimit = 3 + }) + defer func() { + config.UpdateGlobal(func(conf *config.Config) { + conf.Performance.StmtCountLimit = originalStmtCountLimit + }) + }() + + cli.runTests(t, nil, func(dbt *testkit.DBTestKit) { + dbt.MustExec("create table t (id int key);") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + _, err := dbt.GetDB().Query("select * from t for update;") + require.Error(t, err) + require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) + dbt.MustExec("insert into t values (3);") + dbt.MustExec("commit;") + rows := dbt.MustQuery("select * from t;") + var id int + count := 0 + for rows.Next() { + rows.Scan(&id) + count++ + } + require.NoError(t, rows.Close()) + require.Equal(t, 3, id) + require.Equal(t, 1, count) + + dbt.MustExec("delete from t;") + dbt.MustExec("commit;") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + _, err = dbt.GetDB().Exec("insert into t values (3);") + require.Error(t, err) + require.Equal(t, "Error 1105 (HY000): statement count 4 exceeds the transaction limitation, transaction has been rollback, autocommit = false", err.Error()) + dbt.MustExec("commit;") + rows = dbt.MustQuery("select count(*) from t;") + for rows.Next() { + rows.Scan(&count) + } + require.NoError(t, rows.Close()) + require.Equal(t, 0, count) + + dbt.MustExec("delete from t;") + dbt.MustExec("commit;") + dbt.MustExec("set @@tidb_batch_commit=1;") + dbt.MustExec("set @@tidb_disable_txn_auto_retry=0;") + dbt.MustExec("set autocommit=0;") + dbt.MustExec("begin optimistic;") + dbt.MustExec("insert into t values (1);") + dbt.MustExec("insert into t values (2);") + dbt.MustExec("insert into t values (3);") + dbt.MustExec("insert into t values (4);") + dbt.MustExec("insert into t values (5);") + dbt.MustExec("commit;") + rows = dbt.MustQuery("select count(*) from t;") + for rows.Next() { + rows.Scan(&count) + } + require.NoError(t, rows.Close()) + require.Equal(t, 5, count) + }) +} diff --git a/server/tidb_test.go b/server/tidb_test.go index b51aa67ccd25c..569684be80920 100644 --- a/server/tidb_test.go +++ b/server/tidb_test.go @@ -1122,6 +1122,11 @@ func TestSumAvg(t *testing.T) { ts.runTestSumAvg(t) } +func TestStmtCountLimit(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestStmtCountLimit(t) +} + func TestNullFlag(t *testing.T) { ts := createTidbTestSuite(t) diff --git a/session/session.go b/session/session.go index 5ccae09f21d35..003467fec3c3a 100644 --- a/session/session.go +++ b/session/session.go @@ -2399,6 +2399,14 @@ func runStmt(ctx context.Context, se *session, s sqlexec.Statement) (rs sqlexec. if err != nil { return nil, err } + if sessVars.TxnCtx.CouldRetry && !s.IsReadOnly(sessVars) { + // Only when the txn is could retry and the statement is not read only, need to do stmt-count-limit check, + // otherwise, the stmt won't be add into stmt history, and also don't need check. + // About `stmt-count-limit`, see more in https://docs.pingcap.com/tidb/stable/tidb-configuration-file#stmt-count-limit + if err := checkStmtLimit(ctx, se, false); err != nil { + return nil, err + } + } rs, err = s.Exec(ctx) se.updateTelemetryMetric(s.(*executor.ExecStmt)) diff --git a/session/sessiontest/session_test.go b/session/sessiontest/session_test.go index 6124d4e4e269e..79f05e0abc2dc 100644 --- a/session/sessiontest/session_test.go +++ b/session/sessiontest/session_test.go @@ -968,6 +968,16 @@ func TestBatchCommit(t *testing.T) { tk.MustExec("insert into t values (7)") tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + tk.MustExec("delete from t") + tk.MustExec("commit") + tk.MustExec("begin") + tk.MustExec("explain analyze insert into t values (5)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("explain analyze insert into t values (6)") + tk1.MustQuery("select * from t").Check(testkit.Rows()) + tk.MustExec("explain analyze insert into t values (7)") + tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) + // The session is still in transaction. tk.MustExec("insert into t values (8)") tk1.MustQuery("select * from t").Check(testkit.Rows("5", "6", "7")) diff --git a/session/tidb.go b/session/tidb.go index 310d76e007e5a..dad6d14daf41a 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -271,7 +271,7 @@ func finishStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.St if err != nil { return err } - return checkStmtLimit(ctx, se) + return checkStmtLimit(ctx, se, true) } func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql sqlexec.Statement) error { @@ -305,18 +305,29 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s return nil } -func checkStmtLimit(ctx context.Context, se *session) error { +func checkStmtLimit(ctx context.Context, se *session, isFinish bool) error { // If the user insert, insert, insert ... but never commit, TiDB would OOM. // So we limit the statement count in a transaction here. var err error sessVars := se.GetSessionVars() history := GetHistory(se) - if history.Count() > int(config.GetGlobalConfig().Performance.StmtCountLimit) { + stmtCount := history.Count() + if !isFinish { + // history stmt count + current stmt, since current stmt is not finish, it has not add to history. + stmtCount++ + } + if stmtCount > int(config.GetGlobalConfig().Performance.StmtCountLimit) { if !sessVars.BatchCommit { se.RollbackTxn(ctx) - return errors.Errorf("statement count %d exceeds the transaction limitation, autocommit = %t", - history.Count(), sessVars.IsAutocommit()) + return errors.Errorf("statement count %d exceeds the transaction limitation, transaction has been rollback, autocommit = %t", + stmtCount, sessVars.IsAutocommit()) + } + if !isFinish { + // if the stmt is not finish execute, then just return, since some work need to be done such as StmtCommit. + return nil } + // If the stmt is finish execute, and exceed the StmtCountLimit, and BatchCommit is true, + // then commit the current transaction and create a new transaction. err = sessiontxn.NewTxn(ctx, se) // The transaction does not committed yet, we need to keep it in transaction. // The last history could not be "commit"/"rollback" statement. @@ -328,6 +339,7 @@ func checkStmtLimit(ctx context.Context, se *session) error { } // GetHistory get all stmtHistory in current txn. Exported only for test. +// If stmtHistory is nil, will create a new one for current txn. func GetHistory(ctx sessionctx.Context) *StmtHistory { hist, ok := ctx.GetSessionVars().TxnCtx.History.(*StmtHistory) if ok {