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

*: add split table syntax to split table region (#10553) (#10761) #10882

Merged
merged 3 commits into from
Jun 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 23 additions & 7 deletions executor/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (b *executorBuilder) build(p plannercore.Plan) Executor {
case *plannercore.PhysicalIndexLookUpReader:
return b.buildIndexLookUpReader(v)
case *plannercore.SplitRegion:
return b.buildSplitIndexRegion(v)
return b.buildSplitRegion(v)
default:
b.err = ErrUnknownPlan.GenWithStack("Unknown Plan %T", p)
return nil
Expand Down Expand Up @@ -1324,17 +1324,33 @@ func (b *executorBuilder) buildUnionAll(v *plannercore.PhysicalUnionAll) Executo
return e
}

func (b *executorBuilder) buildSplitIndexRegion(v *plannercore.SplitRegion) Executor {
func (b *executorBuilder) buildSplitRegion(v *plannercore.SplitRegion) Executor {
base := newBaseExecutor(b.ctx, nil, v.ExplainID())
base.initCap = chunk.ZeroCapacity
return &SplitIndexRegionExec{
if v.IndexInfo != nil {
return &SplitIndexRegionExec{
baseExecutor: base,
tableInfo: v.TableInfo,
indexInfo: v.IndexInfo,
lower: v.Lower,
upper: v.Upper,
num: v.Num,
valueLists: v.ValueLists,
}
}
if len(v.ValueLists) > 0 {
return &SplitTableRegionExec{
baseExecutor: base,
tableInfo: v.TableInfo,
valueLists: v.ValueLists,
}
}
return &SplitTableRegionExec{
baseExecutor: base,
tableInfo: v.TableInfo,
indexInfo: v.IndexInfo,
lower: v.Lower,
upper: v.Upper,
lower: v.Lower[0],
upper: v.Upper[0],
num: v.Num,
valueLists: v.ValueLists,
}
}

Expand Down
53 changes: 47 additions & 6 deletions executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3518,7 +3518,7 @@ func (s *testSuite) TestUnsignedFeedback(c *C) {
c.Assert(result.Rows()[2][3], Equals, "table:t, range:[0,+inf], keep order:false")
}

func (s *testSuite) TestSplitIndexRegion(c *C) {
func (s *testSuite) TestSplitRegion(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
Expand All @@ -3529,26 +3529,27 @@ func (s *testSuite) TestSplitIndexRegion(c *C) {
terr := errors.Cause(err).(*terror.Error)
c.Assert(terr.Code(), Equals, terror.ErrCode(mysql.WarnDataTruncated))

// Test for split index region.
// Check min value is more than max value.
tk.MustExec(`split table t index idx1 between (0) and (1000000000) regions 10`)
_, err = tk.Exec(`split table t index idx1 between (2,'a') and (1,'c') regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split index region `idx1` lower value (2,a) should less than the upper value (1,c)")
c.Assert(err.Error(), Equals, "Split index `idx1` region lower value (2,a) should less than the upper value (1,c)")

// Check min value is invalid.
_, err = tk.Exec(`split table t index idx1 between () and (1) regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split index region `idx1` lower value count should more than 0")
c.Assert(err.Error(), Equals, "Split index `idx1` region lower value count should more than 0")

// Check max value is invalid.
_, err = tk.Exec(`split table t index idx1 between (1) and () regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split index region `idx1` upper value count should more than 0")
c.Assert(err.Error(), Equals, "Split index `idx1` region upper value count should more than 0")

// Check pre-split region num is too large.
_, err = tk.Exec(`split table t index idx1 between (0) and (1000000000) regions 10000`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split index region num is exceed the limit 1000")
c.Assert(err.Error(), Equals, "Split index region num exceeded the limit 1000")

// Check pre-split region num 0 is invalid.
_, err = tk.Exec(`split table t index idx1 between (0) and (1000000000) regions 0`)
Expand All @@ -3558,7 +3559,47 @@ func (s *testSuite) TestSplitIndexRegion(c *C) {
// Test truncate error msg.
_, err = tk.Exec(`split table t index idx1 between ("aa") and (1000000000) regions 0`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[types:1265]Incorrect value: 'aa' for index column 'b'")
c.Assert(err.Error(), Equals, "[types:1265]Incorrect value: 'aa' for column 'b'")

// Test for split table region.
tk.MustExec(`split table t between (0) and (1000000000) regions 10`)
// Check the ower value is more than the upper value.
_, err = tk.Exec(`split table t between (2) and (1) regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table `t` region lower value 2 should less than the upper value 1")

// Check the lower value is invalid.
_, err = tk.Exec(`split table t between () and (1) regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table region lower value count should be 1")

// Check upper value is invalid.
_, err = tk.Exec(`split table t between (1) and () regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table region upper value count should be 1")

// Check pre-split region num is too large.
_, err = tk.Exec(`split table t between (0) and (1000000000) regions 10000`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table region num exceeded the limit 1000")

// Check pre-split region num 0 is invalid.
_, err = tk.Exec(`split table t between (0) and (1000000000) regions 0`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table region num should more than 0")

// Test truncate error msg.
_, err = tk.Exec(`split table t between ("aa") and (1000000000) regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[types:1265]Incorrect value: 'aa' for column '_tidb_rowid'")

// Test split table region step is too small.
_, err = tk.Exec(`split table t between (0) and (100) regions 10`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "Split table `t` region step value should more than 1000, step 10 is invalid")

// Test split region by syntax
tk.MustExec(`split table t by (0),(1000),(1000000)`)
}

type testOOMSuite struct {
Expand Down
108 changes: 106 additions & 2 deletions executor/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/cznic/mathutil"
"github.com/pingcap/errors"
"github.com/pingcap/parser/model"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/tablecodec"
Expand Down Expand Up @@ -126,9 +127,9 @@ func (e *SplitIndexRegionExec) getSplitIdxKeys() ([][]byte, error) {
lowerStr, err1 := datumSliceToString(e.lower)
upperStr, err2 := datumSliceToString(e.upper)
if err1 != nil || err2 != nil {
return nil, errors.Errorf("Split index region `%v` lower value %v should less than the upper value %v", e.indexInfo.Name, e.lower, e.upper)
return nil, errors.Errorf("Split index `%v` region lower value %v should less than the upper value %v", e.indexInfo.Name, e.lower, e.upper)
}
return nil, errors.Errorf("Split index region `%v` lower value %v should less than the upper value %v", e.indexInfo.Name, lowerStr, upperStr)
return nil, errors.Errorf("Split index `%v` region lower value %v should less than the upper value %v", e.indexInfo.Name, lowerStr, upperStr)
}
return getValuesList(lowerIdxKey, upperIdxKey, e.num, idxKeys), nil
}
Expand Down Expand Up @@ -204,3 +205,106 @@ func datumSliceToString(ds []types.Datum) (string, error) {
str += ")"
return str, nil
}

// SplitTableRegionExec represents a split table regions executor.
type SplitTableRegionExec struct {
baseExecutor

tableInfo *model.TableInfo
lower types.Datum
upper types.Datum
num int
valueLists [][]types.Datum
}

// Next implements the Executor Next interface.
func (e *SplitTableRegionExec) Next(ctx context.Context, _ *chunk.Chunk) error {
store := e.ctx.GetStore()
s, ok := store.(splitableStore)
if !ok {
return nil
}
splitKeys, err := e.getSplitTableKeys()
if err != nil {
return err
}
regionIDs := make([]uint64, 0, len(splitKeys))
for _, key := range splitKeys {
regionID, err := s.SplitRegionAndScatter(key)
if err != nil {
logutil.Logger(context.Background()).Warn("split table region failed",
zap.String("table", e.tableInfo.Name.L),
zap.Error(err))
continue
}
regionIDs = append(regionIDs, regionID)
}
if !e.ctx.GetSessionVars().WaitTableSplitFinish {
return nil
}
for _, regionID := range regionIDs {
err := s.WaitScatterRegionFinish(regionID)
if err != nil {
logutil.Logger(context.Background()).Warn("wait scatter region failed",
zap.Uint64("regionID", regionID),
zap.String("table", e.tableInfo.Name.L),
zap.Error(err))
}
}
return nil
}

var minRegionStepValue = uint64(1000)

func (e *SplitTableRegionExec) getSplitTableKeys() ([][]byte, error) {
var keys [][]byte
if e.num > 0 {
keys = make([][]byte, 0, e.num)
} else {
keys = make([][]byte, 0, len(e.valueLists))
}
recordPrefix := tablecodec.GenTableRecordPrefix(e.tableInfo.ID)
if len(e.valueLists) > 0 {
for _, v := range e.valueLists {
key := tablecodec.EncodeRecordKey(recordPrefix, v[0].GetInt64())
keys = append(keys, key)
}
return keys, nil
}
isUnsigned := false
if e.tableInfo.PKIsHandle {
if pkCol := e.tableInfo.GetPkColInfo(); pkCol != nil {
isUnsigned = mysql.HasUnsignedFlag(pkCol.Flag)
}
}
var step uint64
var lowerValue int64
if isUnsigned {
lowerRecordID := e.lower.GetUint64()
upperRecordID := e.upper.GetUint64()
if upperRecordID <= lowerRecordID {
return nil, errors.Errorf("Split table `%s` region lower value %v should less than the upper value %v", e.tableInfo.Name, lowerRecordID, upperRecordID)
}
step = (upperRecordID - lowerRecordID) / uint64(e.num)
lowerValue = int64(lowerRecordID)
} else {
lowerRecordID := e.lower.GetInt64()
upperRecordID := e.upper.GetInt64()
if upperRecordID <= lowerRecordID {
return nil, errors.Errorf("Split table `%s` region lower value %v should less than the upper value %v", e.tableInfo.Name, lowerRecordID, upperRecordID)
}
step = uint64(upperRecordID-lowerRecordID) / uint64(e.num)
lowerValue = lowerRecordID
}
if step < minRegionStepValue {
return nil, errors.Errorf("Split table `%s` region step value should more than %v, step %v is invalid", e.tableInfo.Name, minRegionStepValue, step)
}

recordID := lowerValue
for i := 1; i < e.num; i++ {
recordID += int64(step)
key := tablecodec.EncodeRecordKey(recordPrefix, recordID)
keys = append(keys, key)
}
return keys, nil
}
76 changes: 76 additions & 0 deletions executor/split_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/pingcap/parser/model"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/mock"
)
Expand Down Expand Up @@ -285,6 +286,81 @@ func (s *testSplitIndex) TestSplitIndex(c *C) {
}
}

func (s *testSplitIndex) TestSplitTable(c *C) {
tbInfo := &model.TableInfo{
Name: model.NewCIStr("t1"),
ID: rand.Int63(),
Columns: []*model.ColumnInfo{
{
Name: model.NewCIStr("c0"),
ID: 1,
Offset: 1,
DefaultValue: 0,
State: model.StatePublic,
FieldType: *types.NewFieldType(mysql.TypeLong),
},
},
}
defer func(originValue uint64) {
minRegionStepValue = originValue
}(minRegionStepValue)
minRegionStepValue = 10
// range is 0 ~ 100, and split into 10 region.
// So 10 regions range is like below:
// region1: [-inf ~ 10)
// region2: [10 ~ 20)
// region3: [20 ~ 30)
// region4: [30 ~ 40)
// region5: [40 ~ 50)
// region6: [50 ~ 60)
// region7: [60 ~ 70)
// region8: [70 ~ 80)
// region9: [80 ~ 90 )
// region10: [90 ~ +inf)
ctx := mock.NewContext()
e := &SplitTableRegionExec{
baseExecutor: newBaseExecutor(ctx, nil, ""),
tableInfo: tbInfo,
lower: types.NewDatum(0),
upper: types.NewDatum(100),
num: 10,
}
valueList, err := e.getSplitTableKeys()
c.Assert(err, IsNil)
c.Assert(len(valueList), Equals, e.num-1)

cases := []struct {
value int
lessEqualIdx int
}{
{-1, -1},
{0, -1},
{1, -1},
{10, 0},
{11, 0},
{20, 1},
{21, 1},
{31, 2},
{41, 3},
{51, 4},
{61, 5},
{71, 6},
{81, 7},
{91, 8},
{100, 8},
{1000, 8},
}

recordPrefix := tablecodec.GenTableRecordPrefix(e.tableInfo.ID)
for _, ca := range cases {
// test for minInt64 handle
key := tablecodec.EncodeRecordKey(recordPrefix, int64(ca.value))
c.Assert(err, IsNil)
idx := searchLessEqualIdx(valueList, key)
c.Assert(idx, Equals, ca.lessEqualIdx, Commentf("%#v", ca))
}
}

func searchLessEqualIdx(valueList [][]byte, value []byte) int {
idx := -1
for i, v := range valueList {
Expand Down
Loading