mirror of
https://github.com/MariaDB/server.git
synced 2025-08-07 00:04:31 +03:00
This fix+comments was originally made by Alexey Kopytov
LP bug #1035225 / MySQL bug #66301: INSERT ... ON DUPLICATE KEY UPDATE + innodb_autoinc_lock_mode=1 is broken The problem was that when certain INSERT ... ON DUPLICATE KEY UPDATE were executed concurrently on a table containing an AUTO_INCREMENT column as a primary key, InnoDB would correctly reserve non-overlapping AUTO_INCREMENT intervals for each statement, but when the server encountered the first duplicate key error on the secondary key in one of the statements and performed an UPDATE, it also updated the internal AUTO_INCREMENT value to the one from the existing row that caused a duplicate key error, even though the AUTO_INCREMENT value was not specified explicitly in the UPDATE clause. It would then proceed with using AUTO_INCREMENT values the range reserved previously by another statement, causing duplicate key errors on the AUTO_INCREMENT column. Fixed by changing write_record() to ensure that in case of a duplicate key error the internal AUTO_INCREMENT counter is only updated when the AUTO_INCREMENT value was explicitly updated by the UPDATE clause. Otherwise it is restored to what it was before the duplicate key error, as that value is unused and can be reused for subsequent successfully inserted rows. sql/sql_insert.cc: Don't update next_insert_id to the value of a row found during ON DUPLICATE KEY UPDATE. sql/sql_parse.cc: Added DBUG_SYNC sql/table.h: Added next_number_field_updated flag to detect changing of auto increment fields. Moved fields a bit to get bool fields after each other (better alignment)
This commit is contained in:
@@ -329,7 +329,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
|
||||
|
||||
|
||||
/*
|
||||
Check update fields for the timestamp field.
|
||||
Check update fields for the timestamp and auto_increment fields.
|
||||
|
||||
SYNOPSIS
|
||||
check_update_fields()
|
||||
@@ -342,6 +342,9 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
|
||||
If the update fields include the timestamp field,
|
||||
remove TIMESTAMP_AUTO_SET_ON_UPDATE from table->timestamp_field_type.
|
||||
|
||||
If the update fields include an autoinc field, set the
|
||||
table->next_number_field_updated flag.
|
||||
|
||||
RETURN
|
||||
0 OK
|
||||
-1 Error
|
||||
@@ -353,7 +356,9 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
|
||||
{
|
||||
TABLE *table= insert_table_list->table;
|
||||
my_bool timestamp_mark;
|
||||
my_bool autoinc_mark;
|
||||
LINT_INIT(timestamp_mark);
|
||||
LINT_INIT(autoinc_mark);
|
||||
|
||||
if (table->timestamp_field)
|
||||
{
|
||||
@@ -365,6 +370,19 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
|
||||
table->timestamp_field->field_index);
|
||||
}
|
||||
|
||||
table->next_number_field_updated= FALSE;
|
||||
|
||||
if (table->found_next_number_field)
|
||||
{
|
||||
/*
|
||||
Unmark the auto_increment field so that we can check if this is modified
|
||||
by update_fields
|
||||
*/
|
||||
autoinc_mark= bitmap_test_and_clear(table->write_set,
|
||||
table->found_next_number_field->
|
||||
field_index);
|
||||
}
|
||||
|
||||
/* Check the fields we are going to modify */
|
||||
if (setup_fields(thd, 0, update_fields, MARK_COLUMNS_WRITE, 0, 0))
|
||||
return -1;
|
||||
@@ -386,6 +404,18 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
|
||||
bitmap_set_bit(table->write_set,
|
||||
table->timestamp_field->field_index);
|
||||
}
|
||||
|
||||
if (table->found_next_number_field)
|
||||
{
|
||||
if (bitmap_is_set(table->write_set,
|
||||
table->found_next_number_field->field_index))
|
||||
table->next_number_field_updated= TRUE;
|
||||
|
||||
if (autoinc_mark)
|
||||
bitmap_set_bit(table->write_set,
|
||||
table->found_next_number_field->field_index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1563,6 +1593,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
MY_BITMAP *save_read_set, *save_write_set;
|
||||
ulonglong prev_insert_id= table->file->next_insert_id;
|
||||
ulonglong insert_id_for_cur_row= 0;
|
||||
ulonglong prev_insert_id_for_cur_row= 0;
|
||||
DBUG_ENTER("write_record");
|
||||
|
||||
info->records++;
|
||||
@@ -1709,6 +1740,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
INSERT query, which is handled separately by
|
||||
THD::arg_of_last_insert_id_function.
|
||||
*/
|
||||
prev_insert_id_for_cur_row= table->file->insert_id_for_cur_row;
|
||||
insert_id_for_cur_row= table->file->insert_id_for_cur_row= 0;
|
||||
trg_error= (table->triggers &&
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||
@@ -1716,9 +1748,22 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
info->copied++;
|
||||
}
|
||||
|
||||
if (table->next_number_field)
|
||||
table->file->adjust_next_insert_id_after_explicit_value(
|
||||
table->next_number_field->val_int());
|
||||
/*
|
||||
Only update next_insert_id if the AUTO_INCREMENT value was explicitly
|
||||
updated, so we don't update next_insert_id with the value from the
|
||||
row being updated. Otherwise reset next_insert_id to what it was
|
||||
before the duplicate key error, since that value is unused.
|
||||
*/
|
||||
if (table->next_number_field_updated)
|
||||
{
|
||||
DBUG_ASSERT(table->next_number_field != NULL);
|
||||
|
||||
table->file->adjust_next_insert_id_after_explicit_value(table->next_number_field->val_int());
|
||||
}
|
||||
else
|
||||
{
|
||||
table->file->restore_auto_increment(prev_insert_id_for_cur_row);
|
||||
}
|
||||
goto ok_or_after_trg_err;
|
||||
}
|
||||
else /* DUP_REPLACE */
|
||||
|
Reference in New Issue
Block a user