Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: Support LOCK/UNLOCK TABLES feature #10343

Merged
merged 61 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
6ef6a15
update go.mod for parser
crazycs520 Apr 26, 2019
5ccef6f
add lock tables and test in table_test, todo: add locks info to session
crazycs520 Apr 29, 2019
1cd6c41
add lock tables and test in table_test, todo: add locks info to session
crazycs520 Apr 29, 2019
bbeef6c
release lock before add lock and refactor struct
crazycs520 Apr 30, 2019
b937cd2
todo: support lock/unlock mutitable and check table lock after privil…
crazycs520 Apr 30, 2019
7415cf7
support lock multi-table lock but with diff schema version, todo: add…
crazycs520 May 3, 2019
07c721c
add check after privilege check
crazycs520 May 4, 2019
6fa4022
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 May 5, 2019
d7f73a6
fix unit test
crazycs520 May 5, 2019
68b611d
refine code
crazycs520 May 5, 2019
9e507a4
fix show privilege check table lock and not support lock table in INF…
crazycs520 May 5, 2019
86c9be1
add more unit test
crazycs520 May 5, 2019
12d6fe1
add mutex in session.lockedtables to avoid concurrent problem
crazycs520 May 5, 2019
45e9120
add none unique table check in lock tables
crazycs520 May 5, 2019
9b4841a
fix ci
crazycs520 May 5, 2019
afd98cf
add table lock test in transaction
crazycs520 May 5, 2019
7886732
add concurrent lock tables test
crazycs520 May 5, 2019
3419f71
remove blank line
crazycs520 May 5, 2019
9e4b024
add todo: clean table lock when tidb-server was kill -9
crazycs520 May 5, 2019
2126cd8
add lock unsupport schema check,test and add comments
crazycs520 May 6, 2019
fee3c99
Merge branch 'master' into table_lock
crazycs520 May 6, 2019
5b1a0ca
fix table lock bug in drop table/schema, truncate table bug
crazycs520 May 7, 2019
8263814
fix test
crazycs520 May 7, 2019
461fffd
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 May 20, 2019
919ac2c
fix test
crazycs520 May 20, 2019
196eeeb
add config field to control enable table feature
crazycs520 May 20, 2019
a9f8008
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 May 21, 2019
5345a68
remove redundant file
crazycs520 May 21, 2019
1e81a14
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 May 27, 2019
52239f9
address comment
crazycs520 May 27, 2019
116803a
address comment and add test
crazycs520 May 29, 2019
521493b
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 May 29, 2019
4bb0cdc
address comment
crazycs520 May 31, 2019
6500545
add todo comment
crazycs520 May 31, 2019
2338305
remove redundant code
crazycs520 May 31, 2019
1246312
address comment
crazycs520 Jun 3, 2019
e317505
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 Jun 3, 2019
871ccbc
address comment
crazycs520 Jun 5, 2019
d25ad49
add comment
crazycs520 Jun 5, 2019
dcd6e5b
ignore table not exists error for drop table not exists
crazycs520 Jun 5, 2019
e97b08d
refine comment
crazycs520 Jun 5, 2019
276b6c2
refine code
crazycs520 Jun 5, 2019
b05c612
address comment
crazycs520 Jun 5, 2019
5bdc187
address comment
crazycs520 Jun 6, 2019
926ca8d
add comment
crazycs520 Jun 6, 2019
2daf292
fix ci
crazycs520 Jun 6, 2019
055f73f
update parser in go.mod
crazycs520 Jun 10, 2019
ed419cf
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 Jun 10, 2019
d04d8cf
fix race by avoid race
crazycs520 Jun 10, 2019
101102a
Address comment
crazycs520 Jun 17, 2019
af345bd
try to remove mutex
crazycs520 Jun 17, 2019
58b4fb1
Merge branch 'master' of https://github.com/pingcap/tidb into table_lock
crazycs520 Jun 17, 2019
a316a7b
address comment
crazycs520 Jun 17, 2019
099ca8f
fix operate database when session holding table locks.
crazycs520 Jun 17, 2019
dcfe7f6
fix ci
crazycs520 Jun 17, 2019
dcf5793
Merge branch 'master' into table_lock
crazycs520 Jun 17, 2019
7f2b121
Merge branch 'master' into table_lock
crazycs520 Jun 17, 2019
c511160
add todo
crazycs520 Jun 18, 2019
f5c3df1
Merge branch 'master' into table_lock
crazycs520 Jun 18, 2019
defcb0a
fix data race in test
crazycs520 Jun 18, 2019
db760e1
Merge branch 'master' into table_lock
crazycs520 Jun 18, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ type Config struct {
// TreatOldVersionUTF8AsUTF8MB4 is use to treat old version table/column UTF8 charset as UTF8MB4. This is for compatibility.
// Currently not support dynamic modify, because this need to reload all old version schema.
TreatOldVersionUTF8AsUTF8MB4 bool `toml:"treat-old-version-utf8-as-utf8mb4" json:"treat-old-version-utf8-as-utf8mb4"`
// EnableTableLock indicate whether enable table lock.
// TODO: remove this after table lock features stable.
EnableTableLock bool `toml:"enable-table-lock" json:"enable-table-lock"`
}

// Log is the log section of config.
Expand Down Expand Up @@ -320,6 +323,7 @@ var defaultConf = Config{
EnableStreaming: false,
CheckMb4ValueInUTF8: true,
TreatOldVersionUTF8AsUTF8MB4: true,
EnableTableLock: false,
TxnLocalLatches: TxnLocalLatches{
Enabled: true,
Capacity: 2048000,
Expand Down
3 changes: 3 additions & 0 deletions config/config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ check-mb4-value-in-utf8 = true
# treat-old-version-utf8-as-utf8mb4 use for upgrade compatibility. Set to true will treat old version table/column UTF8 charset as UTF8MB4.
treat-old-version-utf8-as-utf8mb4 = true

# enable-table-lock is used to control table lock feature. Default is false, indicate the table lock feature is disabled.
enable-table-lock = false

[log]
# Log level: debug, info, warn, error, fatal.
level = "info"
Expand Down
2 changes: 2 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ unrecognized-option-test = true

_, err = f.WriteString(`
token-limit = 0
enable-table-lock = true
[performance]
[tikv-client]
commit-timeout="41s"
Expand All @@ -78,6 +79,7 @@ max-batch-size=128
c.Assert(conf.TiKVClient.CommitTimeout, Equals, "41s")
c.Assert(conf.TiKVClient.MaxBatchSize, Equals, uint(128))
c.Assert(conf.TokenLimit, Equals, uint(1000))
c.Assert(conf.EnableTableLock, IsTrue)
c.Assert(f.Close(), IsNil)
c.Assert(os.Remove(configFile), IsNil)

Expand Down
318 changes: 318 additions & 0 deletions ddl/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/pingcap/parser/mysql"
tmysql "github.com/pingcap/parser/mysql"
"github.com/pingcap/parser/terror"
"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/ddl"
testddlutil "github.com/pingcap/tidb/ddl/testutil"
"github.com/pingcap/tidb/domain"
Expand Down Expand Up @@ -2758,6 +2759,323 @@ func (s *testDBSuite2) TestAlterShardRowIDBits(c *C) {
c.Assert(err.Error(), Equals, "[autoid:1467]Failed to read auto-increment value from storage engine")
}

func (s *testDBSuite2) TestLockTables(c *C) {
s.tk = testkit.NewTestKit(c, s.store)
tk := s.tk
tk.MustExec("use test")
tk.MustExec("drop table if exists t1,t2")
defer tk.MustExec("drop table if exists t1,t2")
tk.MustExec("create table t1 (a int)")
tk.MustExec("create table t2 (a int)")

// recover table lock config.
originValue := config.GetGlobalConfig().EnableTableLock
defer func() {
config.GetGlobalConfig().EnableTableLock = originValue
}()

// Test for enable table lock config.
config.GetGlobalConfig().EnableTableLock = false
tk.MustExec("lock tables t1 write")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockNone)
config.GetGlobalConfig().EnableTableLock = true

// Test lock 1 table.
tk.MustExec("lock tables t1 write")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockWrite)
tk.MustExec("lock tables t1 read")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockRead)
tk.MustExec("lock tables t1 write")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockWrite)

// Test lock multi tables.
tk.MustExec("lock tables t1 write, t2 read")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockWrite)
checkTableLock(c, tk.Se, "test", "t2", model.TableLockRead)
tk.MustExec("lock tables t1 read, t2 write")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockRead)
checkTableLock(c, tk.Se, "test", "t2", model.TableLockWrite)
tk.MustExec("lock tables t2 write")
checkTableLock(c, tk.Se, "test", "t2", model.TableLockWrite)
checkTableLock(c, tk.Se, "test", "t1", model.TableLockNone)
tk.MustExec("lock tables t1 write")
checkTableLock(c, tk.Se, "test", "t1", model.TableLockWrite)
checkTableLock(c, tk.Se, "test", "t2", model.TableLockNone)

tk2 := testkit.NewTestKit(c, s.store)
tk2.MustExec("use test")

// Test read lock.
tk.MustExec("lock tables t1 read")
tk.MustQuery("select * from t1")
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
tk2.MustQuery("select * from t1")
_, err := tk.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLockedForWrite), IsTrue)
_, err = tk.Exec("update t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLockedForWrite), IsTrue)
_, err = tk.Exec("delete from t1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLockedForWrite), IsTrue)

_, err = tk2.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("update t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("delete from t1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
tk2.MustExec("lock tables t1 read")
_, err = tk2.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLockedForWrite), IsTrue)

// Test write lock.
_, err = tk.Exec("lock tables t1 write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
tk2.MustExec("unlock tables")
tk.MustExec("lock tables t1 write")
tk.MustQuery("select * from t1")
tk.MustExec("delete from t1")
tk.MustExec("insert into t1 set a=1")

_, err = tk2.Exec("select * from t1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("lock tables t1 write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)

// Test write local lock.
tk.MustExec("lock tables t1 write local")
tk.MustQuery("select * from t1")
tk.MustExec("delete from t1")
tk.MustExec("insert into t1 set a=1")

tk2.MustQuery("select * from t1")
_, err = tk2.Exec("delete from t1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("lock tables t1 write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)
_, err = tk2.Exec("lock tables t1 read")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)

// Test none unique table.
_, err = tk.Exec("lock tables t1 read, t1 write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrNonuniqTable), IsTrue)

// Test lock table by other session in transaction and commit without retry.
tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")
tk.MustExec("set @@session.tidb_disable_txn_auto_retry=1")
tk.MustExec("begin")
tk.MustExec("insert into t1 set a=1")
tk2.MustExec("lock tables t1 write")
_, err = tk.Exec("commit")
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[domain:2]Information schema is changed. [try again later]")

// Test lock table by other session in transaction and commit with retry.
tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")
tk.MustExec("begin")
tk.MustExec("insert into t1 set a=1")
tk.MustExec("set @@session.tidb_disable_txn_auto_retry=0")
tk2.MustExec("lock tables t1 write")
_, err = tk.Exec("commit")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue, Commentf("err: %v\n", err))

// Test for lock the same table multiple times.
tk2.MustExec("lock tables t1 write")
tk2.MustExec("lock tables t1 write, t2 read")

// Test lock tables and drop tables
tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")
tk.MustExec("lock tables t1 write, t2 write")
tk.MustExec("drop table t1")
tk2.MustExec("create table t1 (a int)")
tk.MustExec("lock tables t1 write, t2 read")

// Test lock tables and drop database.
tk.MustExec("unlock tables")
tk.MustExec("create database test_lock")
tk.MustExec("create table test_lock.t3 (a int)")
tk.MustExec("lock tables t1 write, test_lock.t3 write")
tk.MustExec("drop database test_lock")
tk2.MustExec("create table t3 (a int)")
tk.MustExec("lock tables t1 write, t3 write")
tk.MustExec("drop table t3")

// Test lock tables and truncate tables.
tk.MustExec("unlock tables")
tk.MustExec("lock tables t1 write, t2 read")
tk.MustExec("truncate table t1")
tk.MustExec("insert into t1 set a=1")
_, err = tk2.Exec("insert into t1 set a=1")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableLocked), IsTrue)

// Test for lock unsupported schema tables.
_, err = tk2.Exec("lock tables performance_schema.global_status write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrAccessDenied), IsTrue)
_, err = tk2.Exec("lock tables information_schema.tables write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrAccessDenied), IsTrue)
_, err = tk2.Exec("lock tables mysql.db write")
c.Assert(terror.ErrorEqual(err, infoschema.ErrAccessDenied), IsTrue)

// Test create table/view when session is holding the table locks.
tk.MustExec("unlock tables")
tk.MustExec("lock tables t1 write, t2 read")
_, err = tk.Exec("create table t3 (a int)")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLocked), IsTrue)
_, err = tk.Exec("create view v1 as select * from t1;")
c.Assert(terror.ErrorEqual(err, infoschema.ErrTableNotLocked), IsTrue)

// Test for lock view was not supported.
tk.MustExec("unlock tables")
tk.MustExec("create view v1 as select * from t1;")
_, err = tk.Exec("lock tables v1 read")
c.Assert(terror.ErrorEqual(err, table.ErrUnsupportedOp), IsTrue)

tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")
}

// TestConcurrentLockTables test concurrent lock/unlock tables.
func (s *testDBSuite2) TestConcurrentLockTables(c *C) {
zimulala marked this conversation as resolved.
Show resolved Hide resolved
s.tk = testkit.NewTestKit(c, s.store)
tk2 := testkit.NewTestKit(c, s.store)
tk := s.tk
tk.MustExec("use test")
tk.MustExec("drop table if exists t1")
defer tk.MustExec("drop table if exists t1")
tk.MustExec("create table t1 (a int)")
tk2.MustExec("use test")

// recover table lock config.
originValue := config.GetGlobalConfig().EnableTableLock
defer func() {
config.GetGlobalConfig().EnableTableLock = originValue
}()

// Test for enable table lock config.
config.GetGlobalConfig().EnableTableLock = true

// Test concurrent lock tables read.
sql1 := "lock tables t1 read"
sql2 := "lock tables t1 read"
s.testParallelExecSQL(c, sql1, sql2, tk.Se, tk2.Se, func(c *C, err1, err2 error) {
c.Assert(err1, IsNil)
c.Assert(err2, IsNil)
})
tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")

// Test concurrent lock tables write.
sql1 = "lock tables t1 write"
sql2 = "lock tables t1 write"
s.testParallelExecSQL(c, sql1, sql2, tk.Se, tk2.Se, func(c *C, err1, err2 error) {
c.Assert(err1, IsNil)
c.Assert(terror.ErrorEqual(err2, infoschema.ErrTableLocked), IsTrue)
})
tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")

// Test concurrent lock tables write local.
sql1 = "lock tables t1 write local"
sql2 = "lock tables t1 write local"
s.testParallelExecSQL(c, sql1, sql2, tk.Se, tk2.Se, func(c *C, err1, err2 error) {
c.Assert(err1, IsNil)
c.Assert(terror.ErrorEqual(err2, infoschema.ErrTableLocked), IsTrue)
})

tk.MustExec("unlock tables")
tk2.MustExec("unlock tables")
}

func (s *testDBSuite2) testParallelExecSQL(c *C, sql1, sql2 string, se1, se2 session.Session, f checkRet) {
callback := &ddl.TestDDLCallback{}
times := 0
callback.OnJobRunBeforeExported = func(job *model.Job) {
if times != 0 {
return
}
var qLen int
for {
err := kv.RunInNewTxn(s.store, false, func(txn kv.Transaction) error {
jobs, err1 := admin.GetDDLJobs(txn)
if err1 != nil {
return err1
}
qLen = len(jobs)
return nil
})
c.Assert(err, IsNil)
if qLen == 2 {
break
}
time.Sleep(5 * time.Millisecond)
}
times++
}
d := s.dom.DDL()
originalCallback := d.GetHook()
defer d.(ddl.DDLForTest).SetHook(originalCallback)
d.(ddl.DDLForTest).SetHook(callback)

wg := sync.WaitGroup{}
var err1 error
var err2 error
wg.Add(2)
ch := make(chan struct{})
// Make sure the sql1 is put into the DDLJobQueue.
go func() {
var qLen int
for {
err := kv.RunInNewTxn(s.store, false, func(txn kv.Transaction) error {
jobs, err3 := admin.GetDDLJobs(txn)
if err3 != nil {
return err3
}
qLen = len(jobs)
return nil
})
c.Assert(err, IsNil)
if qLen == 1 {
// Make sure sql2 is executed after the sql1.
close(ch)
break
}
time.Sleep(5 * time.Millisecond)
}
}()
go func() {
defer wg.Done()
_, err1 = se1.Execute(context.Background(), sql1)
}()
go func() {
defer wg.Done()
<-ch
_, err2 = se2.Execute(context.Background(), sql2)
}()

wg.Wait()
f(c, err1, err2)
}

func checkTableLock(c *C, se session.Session, dbName, tableName string, lockTp model.TableLockType) {
tb := testGetTableByName(c, se, dbName, tableName)
dom := domain.GetDomain(se)
if lockTp != model.TableLockNone {
c.Assert(tb.Meta().Lock, NotNil)
c.Assert(tb.Meta().Lock.Tp, Equals, lockTp)
c.Assert(tb.Meta().Lock.State, Equals, model.TableLockStatePublic)
c.Assert(len(tb.Meta().Lock.Sessions) == 1, IsTrue)
c.Assert(tb.Meta().Lock.Sessions[0].ServerID, Equals, dom.DDL().GetID())
c.Assert(tb.Meta().Lock.Sessions[0].SessionID, Equals, se.GetSessionVars().ConnectionID)
} else {
c.Assert(tb.Meta().Lock, IsNil)
}
}

func (s *testDBSuite2) TestDDLWithInvalidTableInfo(c *C) {
s.tk = testkit.NewTestKit(c, s.store)
tk := s.tk
Expand Down
Loading