mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
WL#3146 "less locking in auto_increment":
this is a cleanup patch for our current auto_increment handling: new names for auto_increment variables in THD, new methods to manipulate them (see sql_class.h), some move into handler::, causing less backup/restore work when executing substatements. This makes the logic hopefully clearer, less work is is needed in mysql_insert(). By cleaning up, using different variables for different purposes (instead of one for 3 things...), we fix those bugs, which someone may want to fix in 5.0 too: BUG#20339 "stored procedure using LAST_INSERT_ID() does not replicate statement-based" BUG#20341 "stored function inserting into one auto_increment puts bad data in slave" BUG#19243 "wrong LAST_INSERT_ID() after ON DUPLICATE KEY UPDATE" (now if a row is updated, LAST_INSERT_ID() will return its id) and re-fixes: BUG#6880 "LAST_INSERT_ID() value changes during multi-row INSERT" (already fixed differently by Ramil in 4.1) Test of documented behaviour of mysql_insert_id() (there was no test). The behaviour changes introduced are: - LAST_INSERT_ID() now returns "the first autogenerated auto_increment value successfully inserted", instead of "the first autogenerated auto_increment value if any row was successfully inserted", see auto_increment.test. Same for mysql_insert_id(), see mysql_client_test.c. - LAST_INSERT_ID() returns the id of the updated row if ON DUPLICATE KEY UPDATE, see auto_increment.test. Same for mysql_insert_id(), see mysql_client_test.c. - LAST_INSERT_ID() does not change if no autogenerated value was successfully inserted (it used to then be 0), see auto_increment.test. - if in INSERT SELECT no autogenerated value was successfully inserted, mysql_insert_id() now returns the id of the last inserted row (it already did this for INSERT VALUES), see mysql_client_test.c. - if INSERT SELECT uses LAST_INSERT_ID(X), mysql_insert_id() now returns X (it already did this for INSERT VALUES), see mysql_client_test.c. - NDB now behaves like other engines wrt SET INSERT_ID: with INSERT IGNORE, the id passed in SET INSERT_ID is re-used until a row succeeds; SET INSERT_ID influences not only the first row now. Additionally, when unlocking a table we check that the thread is not keeping a next_insert_id (as the table is unlocked that id is potentially out-of-date); forgetting about this next_insert_id is done in a new handler::ha_release_auto_increment(). Finally we prepare for engines capable of reserving finite-length intervals of auto_increment values: we store such intervals in THD. The next step (to be done by the replication team in 5.1) is to read those intervals from THD and actually store them in the statement-based binary log. NDB will be a good engine to test that.
This commit is contained in:
@ -411,7 +411,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
table->next_number_field=table->found_next_number_field;
|
||||
|
||||
error=0;
|
||||
id=0;
|
||||
thd->proc_info="update";
|
||||
if (duplic != DUP_ERROR || ignore)
|
||||
table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
|
||||
@ -517,16 +516,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
else
|
||||
#endif
|
||||
error=write_record(thd, table ,&info);
|
||||
/*
|
||||
If auto_increment values are used, save the first one
|
||||
for LAST_INSERT_ID() and for the update log.
|
||||
We can't use insert_id() as we don't want to touch the
|
||||
last_insert_id_used flag.
|
||||
*/
|
||||
if (! id && thd->insert_id_used)
|
||||
{ // Get auto increment value
|
||||
id= thd->last_insert_id;
|
||||
}
|
||||
if (error)
|
||||
break;
|
||||
thd->row_count++;
|
||||
@ -534,6 +523,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
|
||||
free_underlaid_joins(thd, &thd->lex->select_lex);
|
||||
joins_freed= TRUE;
|
||||
table->file->ha_release_auto_increment();
|
||||
|
||||
/*
|
||||
Now all rows are inserted. Time to update logs and sends response to
|
||||
@ -544,7 +534,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
id=0; // No auto_increment id
|
||||
info.copied=values_list.elements;
|
||||
end_delayed_insert(thd);
|
||||
}
|
||||
@ -558,11 +547,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
table->file->print_error(my_errno,MYF(0));
|
||||
error=1;
|
||||
}
|
||||
if (id && values_list.elements != 1)
|
||||
thd->insert_id(id); // For update log
|
||||
else if (table->next_number_field && info.copied)
|
||||
id=table->next_number_field->val_int(); // Return auto_increment value
|
||||
|
||||
transactional_table= table->file->has_transactions();
|
||||
|
||||
if ((changed= (info.copied || info.deleted || info.updated)))
|
||||
@ -611,18 +595,27 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
}
|
||||
}
|
||||
thd->proc_info="end";
|
||||
/*
|
||||
We'll report to the client this id:
|
||||
- if the table contains an autoincrement column and we successfully
|
||||
inserted an autogenerated value, the autogenerated value.
|
||||
- if the table contains no autoincrement column and LAST_INSERT_ID(X) was
|
||||
called, X.
|
||||
- if the table contains an autoincrement column, and some rows were
|
||||
inserted, the id of the last "inserted" row (if IGNORE, that value may not
|
||||
have been really inserted but ignored).
|
||||
*/
|
||||
id= (thd->first_successful_insert_id_in_cur_stmt > 0) ?
|
||||
thd->first_successful_insert_id_in_cur_stmt :
|
||||
(thd->arg_of_last_insert_id_function ?
|
||||
thd->first_successful_insert_id_in_prev_stmt :
|
||||
((table->next_number_field && info.copied) ?
|
||||
table->next_number_field->val_int() : 0));
|
||||
table->next_number_field=0;
|
||||
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
|
||||
thd->next_insert_id=0; // Reset this if wrongly used
|
||||
if (duplic != DUP_ERROR || ignore)
|
||||
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
||||
|
||||
/* Reset value of LAST_INSERT_ID if no rows where inserted */
|
||||
if (!info.copied && thd->insert_id_used)
|
||||
{
|
||||
thd->insert_id(0);
|
||||
id=0;
|
||||
}
|
||||
if (error)
|
||||
goto abort;
|
||||
if (values_list.elements == 1 && (!(thd->options & OPTION_WARNINGS) ||
|
||||
@ -644,8 +637,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
thd->row_count_func= info.copied+info.deleted+info.updated;
|
||||
::send_ok(thd, (ulong) thd->row_count_func, id, buff);
|
||||
}
|
||||
if (table != NULL)
|
||||
table->file->release_auto_increment();
|
||||
thd->abort_on_warning= 0;
|
||||
DBUG_RETURN(FALSE);
|
||||
|
||||
@ -655,7 +646,7 @@ abort:
|
||||
end_delayed_insert(thd);
|
||||
#endif
|
||||
if (table != NULL)
|
||||
table->file->release_auto_increment();
|
||||
table->file->ha_release_auto_increment();
|
||||
if (!joins_freed)
|
||||
free_underlaid_joins(thd, &thd->lex->select_lex);
|
||||
thd->abort_on_warning= 0;
|
||||
@ -964,6 +955,8 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
int error, trg_error= 0;
|
||||
char *key=0;
|
||||
MY_BITMAP *save_read_set, *save_write_set;
|
||||
ulonglong prev_insert_id= table->file->next_insert_id;
|
||||
ulonglong insert_id_for_cur_row= 0;
|
||||
DBUG_ENTER("write_record");
|
||||
|
||||
info->records++;
|
||||
@ -976,9 +969,19 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
while ((error=table->file->ha_write_row(table->record[0])))
|
||||
{
|
||||
uint key_nr;
|
||||
/*
|
||||
If we do more than one iteration of this loop, from the second one the
|
||||
row will have an explicit value in the autoinc field, which was set at
|
||||
the first call of handler::update_auto_increment(). So we must save
|
||||
the autogenerated value to avoid thd->insert_id_for_cur_row to become
|
||||
0.
|
||||
*/
|
||||
if (table->file->insert_id_for_cur_row > 0)
|
||||
insert_id_for_cur_row= table->file->insert_id_for_cur_row;
|
||||
else
|
||||
table->file->insert_id_for_cur_row= insert_id_for_cur_row;
|
||||
if (error != HA_WRITE_SKIP)
|
||||
goto err;
|
||||
table->file->restore_auto_increment(); // it's too early here! BUG#20188
|
||||
if ((int) (key_nr = table->file->get_dup_key(error)) < 0)
|
||||
{
|
||||
error=HA_WRITE_SKIP; /* Database can't find key */
|
||||
@ -994,7 +997,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
if (info->handle_duplicates == DUP_REPLACE &&
|
||||
table->next_number_field &&
|
||||
key_nr == table->s->next_number_index &&
|
||||
table->file->auto_increment_column_changed)
|
||||
(insert_id_for_cur_row > 0))
|
||||
goto err;
|
||||
if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
|
||||
{
|
||||
@ -1053,12 +1056,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
if (res == VIEW_CHECK_ERROR)
|
||||
goto before_trg_err;
|
||||
|
||||
if (thd->clear_next_insert_id)
|
||||
{
|
||||
/* Reset auto-increment cacheing if we do an update */
|
||||
thd->clear_next_insert_id= 0;
|
||||
thd->next_insert_id= 0;
|
||||
}
|
||||
if ((error=table->file->ha_update_row(table->record[1],
|
||||
table->record[0])))
|
||||
{
|
||||
@ -1067,7 +1064,14 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
goto err;
|
||||
}
|
||||
info->updated++;
|
||||
|
||||
/*
|
||||
If ON DUP KEY UPDATE updates a row instead of inserting one, and
|
||||
there is an auto_increment column, then SELECT LAST_INSERT_ID()
|
||||
returns the id of the updated row:
|
||||
*/
|
||||
if (table->next_number_field)
|
||||
thd->record_first_successful_insert_id_in_cur_stmt(table->next_number_field->val_int());
|
||||
|
||||
trg_error= (table->triggers &&
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER, TRUE));
|
||||
@ -1096,16 +1100,11 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH) &&
|
||||
(!table->triggers || !table->triggers->has_delete_triggers()))
|
||||
{
|
||||
if (thd->clear_next_insert_id)
|
||||
{
|
||||
/* Reset auto-increment cacheing if we do an update */
|
||||
thd->clear_next_insert_id= 0;
|
||||
thd->next_insert_id= 0;
|
||||
}
|
||||
if ((error=table->file->ha_update_row(table->record[1],
|
||||
table->record[0])))
|
||||
goto err;
|
||||
info->deleted++;
|
||||
thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
|
||||
/*
|
||||
Since we pretend that we have done insert we should call
|
||||
its after triggers.
|
||||
@ -1134,6 +1133,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
}
|
||||
}
|
||||
}
|
||||
thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
|
||||
/*
|
||||
Restore column maps if they where replaced during an duplicate key
|
||||
problem.
|
||||
@ -1147,12 +1147,13 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||
if (!info->ignore ||
|
||||
(error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE))
|
||||
goto err;
|
||||
table->file->restore_auto_increment();
|
||||
table->file->restore_auto_increment(prev_insert_id);
|
||||
goto ok_or_after_trg_err;
|
||||
}
|
||||
|
||||
after_trg_n_copied_inc:
|
||||
info->copied++;
|
||||
thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
|
||||
trg_error= (table->triggers &&
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
|
||||
TRG_ACTION_AFTER, TRUE));
|
||||
@ -1172,6 +1173,7 @@ err:
|
||||
table->file->print_error(error,MYF(0));
|
||||
|
||||
before_trg_err:
|
||||
table->file->restore_auto_increment(prev_insert_id);
|
||||
if (key)
|
||||
my_safe_afree(key, table->s->max_unique_length, MAX_KEY_LENGTH);
|
||||
table->column_bitmaps_set(save_read_set, save_write_set);
|
||||
@ -1234,8 +1236,9 @@ public:
|
||||
char *record;
|
||||
enum_duplicates dup;
|
||||
time_t start_time;
|
||||
bool query_start_used,last_insert_id_used,insert_id_used, ignore, log_query;
|
||||
ulonglong last_insert_id;
|
||||
bool query_start_used, ignore, log_query;
|
||||
bool stmt_depends_on_first_successful_insert_id_in_prev_stmt;
|
||||
ulonglong first_successful_insert_id_in_prev_stmt;
|
||||
timestamp_auto_set_type timestamp_field_type;
|
||||
|
||||
delayed_row(enum_duplicates dup_arg, bool ignore_arg, bool log_query_arg)
|
||||
@ -1639,9 +1642,16 @@ static int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic,
|
||||
di->set_query(query, query_length);
|
||||
row->start_time= thd->start_time;
|
||||
row->query_start_used= thd->query_start_used;
|
||||
row->last_insert_id_used= thd->last_insert_id_used;
|
||||
row->insert_id_used= thd->insert_id_used;
|
||||
row->last_insert_id= thd->last_insert_id;
|
||||
/*
|
||||
those are for the binlog: LAST_INSERT_ID() has been evaluated at this
|
||||
time, so record does not need it, but statement-based binlogging of the
|
||||
INSERT will need when the row is actually inserted.
|
||||
As for SET INSERT_ID, DELAYED does not honour it (BUG#20830).
|
||||
*/
|
||||
row->stmt_depends_on_first_successful_insert_id_in_prev_stmt=
|
||||
thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt;
|
||||
row->first_successful_insert_id_in_prev_stmt=
|
||||
thd->first_successful_insert_id_in_prev_stmt;
|
||||
row->timestamp_field_type= table->timestamp_field_type;
|
||||
|
||||
di->rows.push_back(row);
|
||||
@ -1895,6 +1905,7 @@ pthread_handler_t handle_delayed_insert(void *arg)
|
||||
MYSQL_LOCK *lock=thd->lock;
|
||||
thd->lock=0;
|
||||
pthread_mutex_unlock(&di->mutex);
|
||||
di->table->file->ha_release_auto_increment();
|
||||
mysql_unlock_tables(thd, lock);
|
||||
di->group_count=0;
|
||||
pthread_mutex_lock(&di->mutex);
|
||||
@ -2007,13 +2018,6 @@ bool delayed_insert::handle_inserts(void)
|
||||
table->file->extra(HA_EXTRA_WRITE_CACHE);
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
/* Reset auto-increment cacheing */
|
||||
if (thd.clear_next_insert_id)
|
||||
{
|
||||
thd.next_insert_id= 0;
|
||||
thd.clear_next_insert_id= 0;
|
||||
}
|
||||
|
||||
while ((row=rows.get()))
|
||||
{
|
||||
stacked_inserts--;
|
||||
@ -2022,9 +2026,12 @@ bool delayed_insert::handle_inserts(void)
|
||||
|
||||
thd.start_time=row->start_time;
|
||||
thd.query_start_used=row->query_start_used;
|
||||
thd.last_insert_id=row->last_insert_id;
|
||||
thd.last_insert_id_used=row->last_insert_id_used;
|
||||
thd.insert_id_used=row->insert_id_used;
|
||||
/* for the binlog, forget auto_increment ids generated by previous rows */
|
||||
thd.auto_inc_intervals_in_cur_stmt_for_binlog.empty();
|
||||
thd.first_successful_insert_id_in_prev_stmt=
|
||||
row->first_successful_insert_id_in_prev_stmt;
|
||||
thd.stmt_depends_on_first_successful_insert_id_in_prev_stmt=
|
||||
row->stmt_depends_on_first_successful_insert_id_in_prev_stmt;
|
||||
table->timestamp_field_type= row->timestamp_field_type;
|
||||
|
||||
info.ignore= row->ignore;
|
||||
@ -2187,7 +2194,7 @@ select_insert::select_insert(TABLE_LIST *table_list_par, TABLE *table_par,
|
||||
enum_duplicates duplic,
|
||||
bool ignore_check_option_errors)
|
||||
:table_list(table_list_par), table(table_par), fields(fields_par),
|
||||
last_insert_id(0),
|
||||
autoinc_value_of_last_inserted_row(0),
|
||||
insert_into_view(table_list_par && table_list_par->view != 0)
|
||||
{
|
||||
bzero((char*) &info,sizeof(info));
|
||||
@ -2395,16 +2402,21 @@ bool select_insert::send_data(List<Item> &values)
|
||||
}
|
||||
if (table->next_number_field)
|
||||
{
|
||||
/*
|
||||
If no value has been autogenerated so far, we need to remember the
|
||||
value we just saw, we may need to send it to client in the end.
|
||||
*/
|
||||
if (thd->first_successful_insert_id_in_cur_stmt == 0) // optimization
|
||||
autoinc_value_of_last_inserted_row=
|
||||
table->next_number_field->val_int();
|
||||
/*
|
||||
Clear auto-increment field for the next record, if triggers are used
|
||||
we will clear it twice, but this should be cheap.
|
||||
*/
|
||||
table->next_number_field->reset();
|
||||
if (!last_insert_id && thd->insert_id_used)
|
||||
last_insert_id= thd->insert_id();
|
||||
}
|
||||
}
|
||||
table->file->release_auto_increment();
|
||||
table->file->ha_release_auto_increment();
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
@ -2466,8 +2478,6 @@ void select_insert::send_error(uint errcode,const char *err)
|
||||
{
|
||||
if (!table->file->has_transactions())
|
||||
{
|
||||
if (last_insert_id)
|
||||
thd->insert_id(last_insert_id); // For binary log
|
||||
if (mysql_bin_log.is_open())
|
||||
{
|
||||
thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length,
|
||||
@ -2487,6 +2497,7 @@ void select_insert::send_error(uint errcode,const char *err)
|
||||
bool select_insert::send_eof()
|
||||
{
|
||||
int error,error2;
|
||||
ulonglong id;
|
||||
DBUG_ENTER("select_insert::send_eof");
|
||||
|
||||
error= (!thd->prelocked_mode) ? table->file->ha_end_bulk_insert():0;
|
||||
@ -2512,8 +2523,6 @@ bool select_insert::send_eof()
|
||||
thd->options|= OPTION_STATUS_NO_TRANS_UPDATE;
|
||||
}
|
||||
|
||||
if (last_insert_id)
|
||||
thd->insert_id(last_insert_id); // For binary log
|
||||
/*
|
||||
Write to binlog before commiting transaction. No statement will
|
||||
be written by the binlog_query() below in RBR mode. All the
|
||||
@ -2543,7 +2552,13 @@ bool select_insert::send_eof()
|
||||
sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records,
|
||||
(ulong) (info.deleted+info.updated), (ulong) thd->cuted_fields);
|
||||
thd->row_count_func= info.copied+info.deleted+info.updated;
|
||||
::send_ok(thd, (ulong) thd->row_count_func, last_insert_id, buff);
|
||||
|
||||
id= (thd->first_successful_insert_id_in_cur_stmt > 0) ?
|
||||
thd->first_successful_insert_id_in_cur_stmt :
|
||||
(thd->arg_of_last_insert_id_function ?
|
||||
thd->first_successful_insert_id_in_prev_stmt :
|
||||
(info.copied ? autoinc_value_of_last_inserted_row : 0));
|
||||
::send_ok(thd, (ulong) thd->row_count_func, id, buff);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user