Skip to content

Commit f64176b

Browse files
authored
executor: fix mysql_insert_id() for "INSERT .. ON DUPLICATE KEY" statement (#56514) (#57107)
close #55965
1 parent db8f4c9 commit f64176b

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

executor/insert.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func (e *InsertValues) prefetchDataCache(ctx context.Context, txn kv.Transaction
186186
}
187187

188188
// updateDupRow updates a duplicate row to a new row.
189-
func (e *InsertExec) updateDupRow(ctx context.Context, idxInBatch int, txn kv.Transaction, row toBeCheckedRow, handle kv.Handle, onDuplicate []*expression.Assignment) error {
189+
func (e *InsertExec) updateDupRow(ctx context.Context, idxInBatch int, txn kv.Transaction, row toBeCheckedRow, handle kv.Handle, onDuplicate []*expression.Assignment, autoColIdx int) error {
190190
oldRow, err := getOldRow(ctx, e.ctx, txn, row.t, handle, e.GenExprs)
191191
if err != nil {
192192
return err
@@ -197,7 +197,7 @@ func (e *InsertExec) updateDupRow(ctx context.Context, idxInBatch int, txn kv.Tr
197197
extraCols = e.ctx.GetSessionVars().CurrInsertBatchExtraCols[idxInBatch]
198198
}
199199

200-
err = e.doDupRowUpdate(ctx, handle, oldRow, row.row, extraCols, e.OnDuplicate, idxInBatch)
200+
err = e.doDupRowUpdate(ctx, handle, oldRow, row.row, extraCols, e.OnDuplicate, idxInBatch, autoColIdx)
201201
if e.ctx.GetSessionVars().StmtCtx.DupKeyAsWarning && kv.ErrKeyExists.Equal(err) {
202202
e.ctx.GetSessionVars().StmtCtx.AppendWarning(err)
203203
return nil
@@ -235,14 +235,19 @@ func (e *InsertExec) batchUpdateDupRows(ctx context.Context, newRows [][]types.D
235235
e.stats.Prefetch += time.Since(prefetchStart)
236236
}
237237

238+
_, autoColIdx, found := findAutoIncrementColumn(e.Table)
239+
if !found {
240+
autoColIdx = -1
241+
}
242+
238243
for i, r := range toBeCheckedRows {
239244
if r.handleKey != nil {
240245
handle, err := tablecodec.DecodeRowKey(r.handleKey.newKey)
241246
if err != nil {
242247
return err
243248
}
244249

245-
err = e.updateDupRow(ctx, i, txn, r, handle, e.OnDuplicate)
250+
err = e.updateDupRow(ctx, i, txn, r, handle, e.OnDuplicate, autoColIdx)
246251
if err == nil {
247252
continue
248253
}
@@ -259,7 +264,7 @@ func (e *InsertExec) batchUpdateDupRows(ctx context.Context, newRows [][]types.D
259264
if handle == nil {
260265
continue
261266
}
262-
err = e.updateDupRow(ctx, i, txn, r, handle, e.OnDuplicate)
267+
err = e.updateDupRow(ctx, i, txn, r, handle, e.OnDuplicate, autoColIdx)
263268
if err != nil {
264269
if kv.IsErrNotFound(err) {
265270
// Data index inconsistent? A unique key provide the handle information, but the
@@ -380,7 +385,7 @@ func (e *InsertExec) initEvalBuffer4Dup() {
380385

381386
// doDupRowUpdate updates the duplicate row.
382387
func (e *InsertExec) doDupRowUpdate(ctx context.Context, handle kv.Handle, oldRow []types.Datum, newRow []types.Datum,
383-
extraCols []types.Datum, cols []*expression.Assignment, idxInBatch int) error {
388+
extraCols []types.Datum, cols []*expression.Assignment, idxInBatch int, autoColIdx int) error {
384389
assignFlag := make([]bool, len(e.Table.WritableCols()))
385390
// See http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values
386391
e.curInsertVals.SetDatums(newRow...)
@@ -429,6 +434,17 @@ func (e *InsertExec) doDupRowUpdate(ctx context.Context, handle kv.Handle, oldRo
429434
if err != nil {
430435
return err
431436
}
437+
438+
if autoColIdx >= 0 {
439+
if e.ctx.GetSessionVars().StmtCtx.AffectedRows() > 0 {
440+
// If "INSERT ... ON DUPLICATE KEY UPDATE" duplicate and update a row,
441+
// auto increment value should be set correctly for mysql_insert_id()
442+
// See https://github.com/pingcap/tidb/issues/55965
443+
e.ctx.GetSessionVars().StmtCtx.InsertID = newData[autoColIdx].GetUint64()
444+
} else {
445+
e.ctx.GetSessionVars().StmtCtx.InsertID = 0
446+
}
447+
}
432448
return nil
433449
}
434450

executor/insert_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,3 +1615,68 @@ func TestInsertBigScientificNotation(t *testing.T) {
16151615
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1264 Out of range value for column 'a' at row 1"))
16161616
tk.MustQuery("select id, a from t1 order by id asc").Check(testkit.Rows("1 2147483647", "2 -2147483648"))
16171617
}
1618+
1619+
func TestMySQLInsertID(t *testing.T) {
1620+
// mysql_insert_id() differs from LAST_INSERT_ID()
1621+
// See https://github.com/pingcap/tidb/issues/55965
1622+
// mysql_insert_id() is got from tk.Session().LastInsertID()
1623+
store := testkit.CreateMockStore(t)
1624+
tk := testkit.NewTestKit(t, store)
1625+
tk.MustExec(`use test`)
1626+
tk.MustExec("drop table if exists tb")
1627+
tk.MustExec("create table tb(pk int primary key auto_increment, a int, b int, unique(a))")
1628+
defer tk.MustExec("drop table if exists tb")
1629+
1630+
tk.MustExec("insert into tb (a, b) values (1, 1) on duplicate key update b = values(b)")
1631+
require.Equal(t, tk.Session().LastInsertID(), uint64(1))
1632+
1633+
tk.MustExec("insert into tb (a, b) values (2, 2) on duplicate key update b = values(b)")
1634+
require.Equal(t, tk.Session().LastInsertID(), uint64(2))
1635+
1636+
// If there is an AUTO_INCREMENT column in the table and there were some explicit successfully
1637+
// inserted values or some updated values, return the last of the inserted or updated values.
1638+
// Ref https://dev.mysql.com/doc/c-api/5.7/en/mysql-insert-id.html#:~:text=When%20called%20after%20an%20INSERT%20...%20ON,of%20the%20inserted%20or%20updated%20values
1639+
tk.MustExec("insert into tb (a, b) values (1, 2) on duplicate key update b = values(b)")
1640+
require.Equal(t, tk.Session().LastInsertID(), uint64(1))
1641+
tk.MustQuery("select LAST_INSERT_ID()").Check(testkit.Rows("2"))
1642+
1643+
tk.MustQuery("select * from tb").Sort().Check(testkit.Rows("1 1 2", "2 2 2"))
1644+
1645+
// When the new row and the old row are exactly the same (no inserted or updated values), mysql_insert_id() is 0
1646+
tk.MustExec("insert into tb (a, b) values (1, 2) on duplicate key update b = 2")
1647+
require.Equal(t, tk.Session().LastInsertID(), uint64(0))
1648+
tk.MustQuery("select LAST_INSERT_ID()").Check(testkit.Rows("2"))
1649+
1650+
// When the value of auto increment column is assigned explicitly, LAST_INSERT_ID() is unchanged.
1651+
// mysql_insert_id() is set to the explicit assigned value.
1652+
tk.MustExec("insert into tb values (6, 6, 6)")
1653+
require.Equal(t, tk.Session().LastInsertID(), uint64(6))
1654+
tk.MustQuery("select LAST_INSERT_ID()").Check(testkit.Rows("2"))
1655+
1656+
// Update statement touches neigher mysql_insert_id() nor LAST_INSERT_ID()
1657+
tk.MustExec("update tb set b = 7, pk = pk + 1 where b = 6")
1658+
require.Equal(t, tk.Session().LastInsertID(), uint64(0))
1659+
tk.MustQuery("select LAST_INSERT_ID()").Check(testkit.Rows("2"))
1660+
1661+
// How to distinguish LAST_INSERT_ID() and mysql_insert_id()?
1662+
// In a word, LAST_INSERT_ID() is always get from auto allocated value, while mysql_insert_id() can be
1663+
// auto allocated or explicited specified.
1664+
1665+
// Another scenario mentioned by @lcwangcao
1666+
// What's the behaviour when transaction conflict involved?
1667+
tk.MustExec("truncate table tb")
1668+
tk.MustExec("insert into tb (a, b) values (1, 1), (2, 2)")
1669+
1670+
tk1 := testkit.NewTestKit(t, store)
1671+
tk1.MustExec("use test")
1672+
tk1.MustExec("begin")
1673+
tk1.MustExec("update tb set b = 2 where a = 1")
1674+
go func() {
1675+
time.Sleep(100 * time.Millisecond)
1676+
tk1.MustExec("commit")
1677+
}()
1678+
// The first time this will update one row.
1679+
// Then transaction conflict and retry, in the second time it modify nothing.
1680+
tk.MustExec("insert into tb(a, b) values(1,2) on duplicate key update b = 2;")
1681+
require.Equal(t, tk.Session().LastInsertID(), uint64(0))
1682+
}

0 commit comments

Comments
 (0)