mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +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:
166
sql/sql_class.h
166
sql/sql_class.h
@ -770,12 +770,14 @@ class Sub_statement_state
|
||||
{
|
||||
public:
|
||||
ulonglong options;
|
||||
ulonglong last_insert_id, next_insert_id, current_insert_id;
|
||||
ulonglong first_successful_insert_id_in_prev_stmt;
|
||||
ulonglong first_successful_insert_id_in_cur_stmt, insert_id_for_cur_row;
|
||||
Discrete_interval auto_inc_interval_for_cur_row;
|
||||
ulonglong limit_found_rows;
|
||||
ha_rows cuted_fields, sent_row_count, examined_row_count;
|
||||
ulong client_capabilities;
|
||||
uint in_sub_stmt;
|
||||
bool enable_slow_log, insert_id_used, clear_next_insert_id;
|
||||
bool enable_slow_log;
|
||||
bool last_insert_id_used;
|
||||
my_bool no_send_ok;
|
||||
SAVEPOINT *savepoints;
|
||||
@ -1071,24 +1073,136 @@ public:
|
||||
Note: in the parser, stmt_arena == thd, even for PS/SP.
|
||||
*/
|
||||
Query_arena *stmt_arena;
|
||||
/* Tells if LAST_INSERT_ID(#) was called for the current statement */
|
||||
bool arg_of_last_insert_id_function;
|
||||
/*
|
||||
next_insert_id is set on SET INSERT_ID= #. This is used as the next
|
||||
generated auto_increment value in handler.cc
|
||||
ALL OVER THIS FILE, "insert_id" means "*automatically generated* value for
|
||||
insertion into an auto_increment column".
|
||||
*/
|
||||
ulonglong next_insert_id;
|
||||
/* Remember last next_insert_id to reset it if something went wrong */
|
||||
ulonglong prev_insert_id;
|
||||
/*
|
||||
The insert_id used for the last statement or set by SET LAST_INSERT_ID=#
|
||||
or SELECT LAST_INSERT_ID(#). Used for binary log and returned by
|
||||
LAST_INSERT_ID()
|
||||
This is the first autogenerated insert id which was *successfully*
|
||||
inserted by the previous statement (exactly, if the previous statement
|
||||
didn't successfully insert an autogenerated insert id, then it's the one
|
||||
of the statement before, etc).
|
||||
It can also be set by SET LAST_INSERT_ID=# or SELECT LAST_INSERT_ID(#).
|
||||
It is returned by LAST_INSERT_ID().
|
||||
*/
|
||||
ulonglong last_insert_id;
|
||||
ulonglong first_successful_insert_id_in_prev_stmt;
|
||||
/*
|
||||
Set to the first value that LAST_INSERT_ID() returned for the last
|
||||
statement. When this is set, last_insert_id_used is set to true.
|
||||
Variant of the above, used for storing in statement-based binlog. The
|
||||
difference is that the one above can change as the execution of a stored
|
||||
function progresses, while the one below is set once and then does not
|
||||
change (which is the value which statement-based binlog needs).
|
||||
*/
|
||||
ulonglong current_insert_id;
|
||||
ulonglong first_successful_insert_id_in_prev_stmt_for_binlog;
|
||||
/*
|
||||
This is the first autogenerated insert id which was *successfully*
|
||||
inserted by the current statement. It is maintained only to set
|
||||
first_successful_insert_id_in_prev_stmt when statement ends.
|
||||
*/
|
||||
ulonglong first_successful_insert_id_in_cur_stmt;
|
||||
/*
|
||||
We follow this logic:
|
||||
- when stmt starts, first_successful_insert_id_in_prev_stmt contains the
|
||||
first insert id successfully inserted by the previous stmt.
|
||||
- as stmt makes progress, handler::insert_id_for_cur_row changes; every
|
||||
time get_auto_increment() is called, auto_inc_intervals_for_binlog is
|
||||
augmented with the reserved interval (if statement-based binlogging).
|
||||
- at first successful insertion of an autogenerated value,
|
||||
first_successful_insert_id_in_cur_stmt is set to
|
||||
handler::insert_id_for_cur_row.
|
||||
- when stmt goes to binlog, auto_inc_intervals_for_binlog is
|
||||
binlogged if non-empty.
|
||||
- when stmt ends, first_successful_insert_id_in_prev_stmt is set to
|
||||
first_successful_insert_id_in_cur_stmt.
|
||||
*/
|
||||
/*
|
||||
stmt_depends_on_first_successful_insert_id_in_prev_stmt is set when
|
||||
LAST_INSERT_ID() is used by a statement.
|
||||
If it is set, first_successful_insert_id_in_prev_stmt_for_binlog will be
|
||||
stored in the statement-based binlog.
|
||||
This variable is CUMULATIVE along the execution of a stored function or
|
||||
trigger: if one substatement sets it to 1 it will stay 1 until the
|
||||
function/trigger ends, thus making sure that
|
||||
first_successful_insert_id_in_prev_stmt_for_binlog does not change anymore
|
||||
and is propagated to the caller for binlogging.
|
||||
*/
|
||||
bool stmt_depends_on_first_successful_insert_id_in_prev_stmt;
|
||||
/*
|
||||
List of auto_increment intervals reserved by the thread so far, for
|
||||
storage in the statement-based binlog.
|
||||
Note that its minimum is not first_successful_insert_id_in_cur_stmt:
|
||||
assuming a table with an autoinc column, and this happens:
|
||||
INSERT INTO ... VALUES(3);
|
||||
SET INSERT_ID=3; INSERT IGNORE ... VALUES (NULL);
|
||||
then the latter INSERT will insert no rows
|
||||
(first_successful_insert_id_in_cur_stmt == 0), but storing "INSERT_ID=3"
|
||||
in the binlog is still needed; the list's minimum will contain 3.
|
||||
*/
|
||||
Discrete_intervals_list auto_inc_intervals_in_cur_stmt_for_binlog;
|
||||
/* Used by replication and SET INSERT_ID */
|
||||
Discrete_intervals_list auto_inc_intervals_forced;
|
||||
/*
|
||||
There is BUG#19630 where statement-based replication of stored
|
||||
functions/triggers with two auto_increment columns breaks.
|
||||
We however ensure that it works when there is 0 or 1 auto_increment
|
||||
column; our rules are
|
||||
a) on master, while executing a top statement involving substatements,
|
||||
first top- or sub- statement to generate auto_increment values wins the
|
||||
exclusive right to write them to binlog (so the losers won't write their
|
||||
values to binlog).
|
||||
b) on slave, while replicating a top statement involving substatements,
|
||||
first top- or sub- statement to need to read auto_increment values from
|
||||
the master's binlog wins the exclusive right to read them (so the losers
|
||||
won't read their values from binlog but instead generate on their own).
|
||||
a) implies that we mustn't backup/restore
|
||||
auto_inc_intervals_in_cur_stmt_for_binlog.
|
||||
b) implies that we mustn't backup/restore auto_inc_intervals_forced.
|
||||
|
||||
If there are more than 1 auto_increment columns, then intervals for
|
||||
different columns may mix into the
|
||||
auto_inc_intervals_in_cur_stmt_for_binlog list, which is logically wrong,
|
||||
but there is no point in preventing this mixing by preventing intervals
|
||||
from the secondly inserted column to come into the list, as such
|
||||
prevention would be wrong too.
|
||||
What will happen in the case of
|
||||
INSERT INTO t1 (auto_inc) VALUES(NULL);
|
||||
where t1 has a trigger which inserts into an auto_inc column of t2, is
|
||||
that in binlog we'll store the interval of t1 and the interval of t2 (when
|
||||
we store intervals, soon), then in slave, t1 will use both intervals, t2
|
||||
will use none; if t1 inserts the same number of rows as on master,
|
||||
normally the 2nd interval will not be used by t1, which is fine. t2's
|
||||
values will be wrong if t2's internal auto_increment counter is different
|
||||
from what it was on master (which is likely). In 5.1, in mixed binlogging
|
||||
mode, row-based binlogging is used for such cases where two
|
||||
auto_increment columns are inserted.
|
||||
*/
|
||||
inline void record_first_successful_insert_id_in_cur_stmt(ulonglong id)
|
||||
{
|
||||
if (first_successful_insert_id_in_cur_stmt == 0)
|
||||
first_successful_insert_id_in_cur_stmt= id;
|
||||
}
|
||||
inline ulonglong read_first_successful_insert_id_in_prev_stmt(void)
|
||||
{
|
||||
if (!stmt_depends_on_first_successful_insert_id_in_prev_stmt)
|
||||
{
|
||||
/* It's the first time we read it */
|
||||
first_successful_insert_id_in_prev_stmt_for_binlog=
|
||||
first_successful_insert_id_in_prev_stmt;
|
||||
stmt_depends_on_first_successful_insert_id_in_prev_stmt= 1;
|
||||
}
|
||||
return first_successful_insert_id_in_prev_stmt;
|
||||
}
|
||||
/*
|
||||
Used by Intvar_log_event::exec_event() and by "SET INSERT_ID=#"
|
||||
(mysqlbinlog). We'll soon add a variant which can take many intervals in
|
||||
argument.
|
||||
*/
|
||||
inline void force_one_auto_inc_interval(ulonglong next_id)
|
||||
{
|
||||
auto_inc_intervals_forced.append(next_id, ULONGLONG_MAX, 0);
|
||||
}
|
||||
|
||||
ulonglong limit_found_rows;
|
||||
ulonglong options; /* Bitmap of states */
|
||||
longlong row_count_func; /* For the ROW_COUNT() function */
|
||||
@ -1157,7 +1271,6 @@ public:
|
||||
bool last_cuted_field;
|
||||
bool no_errors, password, is_fatal_error;
|
||||
bool query_start_used, rand_used, time_zone_used;
|
||||
bool last_insert_id_used,insert_id_used, clear_next_insert_id;
|
||||
bool in_lock_tables;
|
||||
bool query_error, bootstrap, cleanup_done;
|
||||
bool tmp_table_used;
|
||||
@ -1185,9 +1298,10 @@ public:
|
||||
/* Used by the sys_var class to store temporary values */
|
||||
union
|
||||
{
|
||||
my_bool my_bool_value;
|
||||
long long_value;
|
||||
ulong ulong_value;
|
||||
my_bool my_bool_value;
|
||||
long long_value;
|
||||
ulong ulong_value;
|
||||
ulonglong ulonglong_value;
|
||||
} sys_var_tmp;
|
||||
|
||||
struct {
|
||||
@ -1288,20 +1402,6 @@ public:
|
||||
inline void end_time() { time(&start_time); }
|
||||
inline void set_time(time_t t) { time_after_lock=start_time=user_time=t; }
|
||||
inline void lock_time() { time(&time_after_lock); }
|
||||
inline void insert_id(ulonglong id_arg)
|
||||
{
|
||||
last_insert_id= id_arg;
|
||||
insert_id_used=1;
|
||||
}
|
||||
inline ulonglong insert_id(void)
|
||||
{
|
||||
if (!last_insert_id_used)
|
||||
{
|
||||
last_insert_id_used=1;
|
||||
current_insert_id=last_insert_id;
|
||||
}
|
||||
return last_insert_id;
|
||||
}
|
||||
inline ulonglong found_rows(void)
|
||||
{
|
||||
return limit_found_rows;
|
||||
@ -1589,7 +1689,7 @@ class select_insert :public select_result_interceptor {
|
||||
TABLE_LIST *table_list;
|
||||
TABLE *table;
|
||||
List<Item> *fields;
|
||||
ulonglong last_insert_id;
|
||||
ulonglong autoinc_value_of_last_inserted_row; // autogenerated or not
|
||||
COPY_INFO info;
|
||||
bool insert_into_view;
|
||||
|
||||
|
Reference in New Issue
Block a user