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 auto_increment_increment & auto_increment_offset. #14301

Merged
merged 19 commits into from
Jan 8, 2020
Merged
12 changes: 6 additions & 6 deletions executor/insert_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,23 +716,23 @@ func (e *InsertValues) lazyAdjustAutoIncrementDatum(ctx context.Context, rows []
i++
cnt++
}
// Alloc batch N consecutive (min, max] autoIDs.
// max value can be derived from adding one for cnt times.
min, _, err := table.AllocBatchAutoIncrementValue(ctx, e.Table, e.ctx, cnt)
// AllocBatchAutoIncrementValue allocates batch N consecutive autoIDs.
// The max value can be derived from adding the increment value to min for cnt-1 times.
min, increment, err := table.AllocBatchAutoIncrementValue(ctx, e.Table, e.ctx, cnt)
if e.handleErr(col, &autoDatum, cnt, err) != nil {
return nil, err
}
// It's compatible with mysql setting the first allocated autoID to lastInsertID.
// Cause autoID may be specified by user, judge only the first row is not suitable.
if e.lastInsertID == 0 {
e.lastInsertID = uint64(min) + 1
e.lastInsertID = uint64(min)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the previous code is uint64(min) + 1 but now it can be uint64(min)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already derive the firstID and increment from the allocated range.
AllocBatchAutoIncrementValue now return firstID and increment.

}
// Assign autoIDs to rows.
for j := 0; j < cnt; j++ {
offset := j + start
d := rows[offset][colIdx]

id := int64(uint64(min) + uint64(j) + 1)
id := int64(uint64(min) + uint64(j)*uint64(increment))
d.SetAutoID(id, col.Flag)
retryInfo.AddAutoIncrementID(id)

Expand Down Expand Up @@ -871,7 +871,7 @@ func (e *InsertValues) adjustAutoRandomDatum(ctx context.Context, d types.Datum,
func (e *InsertValues) allocAutoRandomID(fieldType *types.FieldType) (int64, error) {
alloc := e.Table.Allocator(e.ctx, autoid.AutoRandomType)
tableInfo := e.Table.Meta()
_, autoRandomID, err := alloc.Alloc(tableInfo.ID, 1)
_, autoRandomID, err := alloc.Alloc(tableInfo.ID, 1, 1, 1)
if err != nil {
return 0, err
}
Expand Down
70 changes: 70 additions & 0 deletions executor/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,3 +830,73 @@ func (s *testSuite3) TestDMLCast(c *C) {
tk.MustExec("delete from t where a = ''")
tk.MustQuery(`select * from t`).Check(testkit.Rows())
}

// There is a potential issue in MySQL: when the value of auto_increment_offset is greater
// than that of auto_increment_increment, the value of auto_increment_offset is ignored
// (https://dev.mysql.com/doc/refman/8.0/en/replication-options-master.html#sysvar_auto_increment_increment),
// This issue is a flaw of the implementation of MySQL and it doesn't exist in TiDB.
func (s *testSuite3) TestAutoIDIncrementAndOffset(c *C) {
tk := testkit.NewTestKit(c, s.store)
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
tk.MustExec(`use test`)
// Test for offset is larger than increment.
tk.Se.GetSessionVars().AutoIncrementIncrement = 5
tk.Se.GetSessionVars().AutoIncrementOffset = 10
tk.MustExec(`create table io (a int key auto_increment)`)
tk.MustExec(`insert into io values (null),(null),(null)`)
tk.MustQuery(`select * from io`).Check(testkit.Rows("10", "15", "20"))
tk.MustExec(`drop table io`)

// Test handle is PK.
tk.MustExec(`create table io (a int key auto_increment)`)
tk.Se.GetSessionVars().AutoIncrementOffset = 10
tk.Se.GetSessionVars().AutoIncrementIncrement = 2
tk.MustExec(`insert into io values (),(),()`)
tk.MustQuery(`select * from io`).Check(testkit.Rows("10", "12", "14"))
tk.MustExec(`delete from io`)
tangenta marked this conversation as resolved.
Show resolved Hide resolved

// Test reset the increment.
tk.Se.GetSessionVars().AutoIncrementIncrement = 5
tk.MustExec(`insert into io values (),(),()`)
tk.MustQuery(`select * from io`).Check(testkit.Rows("15", "20", "25"))
tk.MustExec(`delete from io`)

tk.Se.GetSessionVars().AutoIncrementIncrement = 10
tk.MustExec(`insert into io values (),(),()`)
tk.MustQuery(`select * from io`).Check(testkit.Rows("30", "40", "50"))
tk.MustExec(`delete from io`)

tk.Se.GetSessionVars().AutoIncrementIncrement = 5
tk.MustExec(`insert into io values (),(),()`)
tk.MustQuery(`select * from io`).Check(testkit.Rows("55", "60", "65"))
tk.MustExec(`drop table io`)

// Test handle is not PK.
tk.Se.GetSessionVars().AutoIncrementIncrement = 2
tk.Se.GetSessionVars().AutoIncrementOffset = 10
tk.MustExec(`create table io (a int, b int auto_increment, key(b))`)
tk.MustExec(`insert into io(b) values (null),(null),(null)`)
// AutoID allocation will take increment and offset into consideration.
tk.MustQuery(`select b from io`).Check(testkit.Rows("10", "12", "14"))
// HandleID allocation will ignore the increment and offset.
tk.MustQuery(`select _tidb_rowid from io`).Check(testkit.Rows("15", "16", "17"))
tk.MustExec(`delete from io`)

tk.Se.GetSessionVars().AutoIncrementIncrement = 10
tk.MustExec(`insert into io(b) values (null),(null),(null)`)
tk.MustQuery(`select b from io`).Check(testkit.Rows("20", "30", "40"))
tk.MustQuery(`select _tidb_rowid from io`).Check(testkit.Rows("41", "42", "43"))

// Test invalid value.
tk.Se.GetSessionVars().AutoIncrementIncrement = -1
tk.Se.GetSessionVars().AutoIncrementOffset = -2
_, err := tk.Exec(`insert into io(b) values (null),(null),(null)`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[autoid:8060]Invalid auto_increment settings: auto_increment_increment: -1, auto_increment_offset: -2, both of them must be in range [1..65535]")
tk.MustExec(`delete from io`)

tk.Se.GetSessionVars().AutoIncrementIncrement = 65536
tk.Se.GetSessionVars().AutoIncrementOffset = 65536
_, err = tk.Exec(`insert into io(b) values (null),(null),(null)`)
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "[autoid:8060]Invalid auto_increment settings: auto_increment_increment: 65536, auto_increment_offset: 65536, both of them must be in range [1..65535]")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ require (
github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989
github.com/pingcap/kvproto v0.0.0-20191217072959-393e6c0fd4b7
github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9
github.com/pingcap/parser v0.0.0-20191230064650-03937644ab9b
github.com/pingcap/parser v0.0.0-20200102065246-18bb5e80b59c
github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d
github.com/pingcap/sysutil v0.0.0-20191216090214-5f9620d22b3b
github.com/pingcap/tidb-tools v3.0.6-0.20191106033616-90632dda3863+incompatible
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ github.com/pingcap/kvproto v0.0.0-20191217072959-393e6c0fd4b7 h1:thLL2vFObG8vxBC
github.com/pingcap/kvproto v0.0.0-20191217072959-393e6c0fd4b7/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w=
github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 h1:AJD9pZYm72vMgPcQDww9rkZ1DnWfl0pXV3BOWlkYIjA=
github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=
github.com/pingcap/parser v0.0.0-20191230064650-03937644ab9b h1:f26xGEkqFign3gd8dDLWsY5vXLYowSOEAAAxzqivdn0=
github.com/pingcap/parser v0.0.0-20191230064650-03937644ab9b/go.mod h1:9v0Edh8IbgjGYW2ArJr19E+bvL8zKahsFp+ixWeId+4=
github.com/pingcap/parser v0.0.0-20200102065246-18bb5e80b59c h1:SY7PqxZ/Jx0ddH7wvD7//fcX5l2eavAPPP+cEu0wY9k=
github.com/pingcap/parser v0.0.0-20200102065246-18bb5e80b59c/go.mod h1:9v0Edh8IbgjGYW2ArJr19E+bvL8zKahsFp+ixWeId+4=
github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d h1:Ui80aiLTyd0EZD56o2tjFRYpHfhazBjtBdKeR8UoTFY=
github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d/go.mod h1:CML+b1JVjN+VbDijaIcUSmuPgpDjXEY7UiOx5yDP8eE=
github.com/pingcap/sysutil v0.0.0-20191216090214-5f9620d22b3b h1:EEyo/SCRswLGuSk+7SB86Ak1p8bS6HL1Mi4Dhyuv6zg=
Expand Down
100 changes: 89 additions & 11 deletions meta/autoid/autoid.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const (
minStep = 30000
maxStep = 2000000
defaultConsumeTime = 10 * time.Second
minIncrement = 1
maxIncrement = 65535
)

// RowIDBitLength is the bit number of a row id in TiDB.
Expand Down Expand Up @@ -80,7 +82,12 @@ type Allocator interface {
// Alloc allocs N consecutive autoID for table with tableID, returning (min, max] of the allocated autoID batch.
// It gets a batch of autoIDs at a time. So it does not need to access storage for each call.
// The consecutive feature is used to insert multiple rows in a statement.
Alloc(tableID int64, n uint64) (int64, int64, error)
// increment & offset is used to validate the start position (the allocator's base is not always the last allocated id).
// The returned range is (min, max]:
// case increment=1 & offset=1: you can derive the ids like min+1, min+2... max.
// case increment=x & offset=y: you firstly need to seek to firstID by `SeekToFirstAutoIDXXX`, then derive the IDs like firstID, firstID + increment * 2... in the caller.
Alloc(tableID int64, n uint64, increment, offset int64) (int64, int64, error)

// Rebase rebases the autoID base for table with tableID and the new base value.
// If allocIDs is true, it will allocate some IDs and save to the cache.
// If allocIDs is false, it will not allocate IDs.
Expand Down Expand Up @@ -309,23 +316,86 @@ func NewAllocatorsFromTblInfo(store kv.Storage, schemaID int64, tblInfo *model.T
}

// Alloc implements autoid.Allocator Alloc interface.
func (alloc *allocator) Alloc(tableID int64, n uint64) (int64, int64, error) {
// For autoIncrement allocator, the increment and offset should always be positive in [1, 65535].
// Attention:
// When increment and offset is not the default value(1), the return range (min, max] need to
// calculate the correct start position rather than simply the add 1 to min. Then you can derive
// the successive autoID by adding increment * cnt to firstID for (n-1) times.
//
// Example:
// (6, 13] is returned, increment = 4, offset = 1, n = 2.
// 6 is the last allocated value for other autoID or handle, maybe with different increment and step,
// but actually we don't care about it, all we need is to calculate the new autoID corresponding to the
// increment and offset at this time now. To simplify the rule is like (ID - offset) % increment = 0,
// so the first autoID should be 9, then add increment to it to get 13.
func (alloc *allocator) Alloc(tableID int64, n uint64, increment, offset int64) (int64, int64, error) {
if tableID == 0 {
return 0, 0, errInvalidTableID.GenWithStackByArgs("Invalid tableID")
}
if n == 0 {
return 0, 0, nil
}
if alloc.allocType == AutoIncrementType || alloc.allocType == RowIDAllocType {
if !validIncrementAndOffset(increment, offset) {
return 0, 0, errInvalidIncrementAndOffset.GenWithStackByArgs(increment, offset)
}
}
alloc.mu.Lock()
defer alloc.mu.Unlock()
if alloc.isUnsigned {
return alloc.alloc4Unsigned(tableID, n)
return alloc.alloc4Unsigned(tableID, n, increment, offset)
}
return alloc.alloc4Signed(tableID, n, increment, offset)
}

func validIncrementAndOffset(increment, offset int64) bool {
return (increment >= minIncrement && increment <= maxIncrement) && (offset >= minIncrement && offset <= maxIncrement)
}

// CalcNeededBatchSize is used to calculate batch size for autoID allocation.
// It firstly seeks to the first valid position based on increment and offset,
// then plus the length remained, which could be (n-1) * increment.
func CalcNeededBatchSize(base, n, increment, offset int64, isUnsigned bool) int64 {
if increment == 1 {
return n
}
if isUnsigned {
// SeekToFirstAutoIDUnSigned seeks to the next unsigned valid position.
nr := SeekToFirstAutoIDUnSigned(uint64(base), uint64(increment), uint64(offset))
// Calculate the total batch size needed.
nr += (uint64(n) - 1) * uint64(increment)
return int64(nr - uint64(base))
}
return alloc.alloc4Signed(tableID, n)
nr := SeekToFirstAutoIDSigned(base, increment, offset)
// Calculate the total batch size needed.
nr += (n - 1) * increment
return nr - base
}

func (alloc *allocator) alloc4Signed(tableID int64, n uint64) (int64, int64, error) {
n1 := int64(n)
// SeekToFirstAutoIDSigned seeks to the next valid signed position.
func SeekToFirstAutoIDSigned(base, increment, offset int64) int64 {
nr := (base + increment - offset) / increment
nr = nr*increment + offset
return nr
}

// SeekToFirstAutoIDUnSigned seeks to the next valid unsigned position.
func SeekToFirstAutoIDUnSigned(base, increment, offset uint64) uint64 {
nr := (base + increment - offset) / increment
nr = nr*increment + offset
return nr
}

func (alloc *allocator) alloc4Signed(tableID int64, n uint64, increment, offset int64) (int64, int64, error) {
// Check offset rebase if necessary.
if offset-1 > alloc.base {
if err := alloc.rebase4Signed(tableID, offset-1, true); err != nil {
return 0, 0, err
}
}
// CalcNeededBatchSize calculates the total batch size needed.
n1 := CalcNeededBatchSize(alloc.base, int64(n), increment, offset, alloc.isUnsigned)

// Condition alloc.base+N1 > alloc.end will overflow when alloc.base + N1 > MaxInt64. So need this.
if math.MaxInt64-alloc.base <= n1 {
return 0, 0, ErrAutoincReadFailed
Expand Down Expand Up @@ -378,14 +448,22 @@ func (alloc *allocator) alloc4Signed(tableID int64, n uint64) (int64, int64, err
return min, alloc.base, nil
}

func (alloc *allocator) alloc4Unsigned(tableID int64, n uint64) (int64, int64, error) {
n1 := int64(n)
func (alloc *allocator) alloc4Unsigned(tableID int64, n uint64, increment, offset int64) (int64, int64, error) {
// Check offset rebase if necessary.
if uint64(offset-1) > uint64(alloc.base) {
if err := alloc.rebase4Unsigned(tableID, uint64(offset-1), true); err != nil {
return 0, 0, err
}
}
// CalcNeededBatchSize calculates the total batch size needed.
n1 := CalcNeededBatchSize(alloc.base, int64(n), increment, offset, alloc.isUnsigned)
AilinKid marked this conversation as resolved.
Show resolved Hide resolved

// Condition alloc.base+n1 > alloc.end will overflow when alloc.base + n1 > MaxInt64. So need this.
if math.MaxUint64-uint64(alloc.base) <= n {
if math.MaxUint64-uint64(alloc.base) <= uint64(n1) {
return 0, 0, ErrAutoincReadFailed
}
// The local rest is not enough for alloc, skip it.
if uint64(alloc.base)+n > uint64(alloc.end) {
if uint64(alloc.base)+uint64(n1) > uint64(alloc.end) {
var newBase, newEnd int64
startTime := time.Now()
// Although it may skip a segment here, we still treat it as consumed.
Expand Down Expand Up @@ -429,7 +507,7 @@ func (alloc *allocator) alloc4Unsigned(tableID int64, n uint64) (int64, int64, e
zap.Int64("database ID", alloc.dbID))
min := alloc.base
// Use uint64 n directly.
alloc.base = int64(uint64(alloc.base) + n)
alloc.base = int64(uint64(alloc.base) + uint64(n1))
return min, alloc.base, nil
}

Expand Down
Loading