mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Fix for BUG#12637: Make SPs+user variables replication work:
* Allocate thd->user_var_events elements on appropriate mem_root * If several SP statements are binlogged as a single statement, collect all user var accesses they make (grep for StoredRoutinesBinlogging for details)
This commit is contained in:
@ -156,3 +156,60 @@ slave: 6
|
|||||||
drop procedure p1;
|
drop procedure p1;
|
||||||
drop function f1;
|
drop function f1;
|
||||||
drop table t1,t2;
|
drop table t1,t2;
|
||||||
|
create table t1 (a int);
|
||||||
|
create procedure p1()
|
||||||
|
begin
|
||||||
|
insert into t1 values(@x);
|
||||||
|
set @x=@x+1;
|
||||||
|
insert into t1 values(@x);
|
||||||
|
if (f2()) then
|
||||||
|
insert into t1 values(1243);
|
||||||
|
end if;
|
||||||
|
end//
|
||||||
|
create function f2() returns int
|
||||||
|
begin
|
||||||
|
insert into t1 values(@z);
|
||||||
|
set @z=@z+1;
|
||||||
|
insert into t1 values(@z);
|
||||||
|
return 0;
|
||||||
|
end//
|
||||||
|
create function f1() returns int
|
||||||
|
begin
|
||||||
|
insert into t1 values(@y);
|
||||||
|
call p1();
|
||||||
|
return 0;
|
||||||
|
end//
|
||||||
|
set @x=10;
|
||||||
|
set @y=20;
|
||||||
|
set @z=100;
|
||||||
|
select f1();
|
||||||
|
f1()
|
||||||
|
0
|
||||||
|
set @x=30;
|
||||||
|
call p1();
|
||||||
|
select 'master', a from t1;
|
||||||
|
master a
|
||||||
|
master 20
|
||||||
|
master 10
|
||||||
|
master 11
|
||||||
|
master 100
|
||||||
|
master 101
|
||||||
|
master 30
|
||||||
|
master 31
|
||||||
|
master 101
|
||||||
|
master 102
|
||||||
|
select 'slave', a from t1;
|
||||||
|
slave a
|
||||||
|
slave 20
|
||||||
|
slave 10
|
||||||
|
slave 11
|
||||||
|
slave 100
|
||||||
|
slave 101
|
||||||
|
slave 30
|
||||||
|
slave 31
|
||||||
|
slave 101
|
||||||
|
slave 102
|
||||||
|
drop table t1;
|
||||||
|
drop function f1;
|
||||||
|
drop function f2;
|
||||||
|
drop procedure p1;
|
||||||
|
@ -3085,6 +3085,19 @@ column_name bug10055(t.column_name)
|
|||||||
id id
|
id id
|
||||||
data data
|
data data
|
||||||
drop function bug10055|
|
drop function bug10055|
|
||||||
|
drop procedure if exists bug12297|
|
||||||
|
create procedure bug12297(lim int)
|
||||||
|
begin
|
||||||
|
set @x = 0;
|
||||||
|
repeat
|
||||||
|
insert into t1(id,data)
|
||||||
|
values('aa', @x);
|
||||||
|
set @x = @x + 1;
|
||||||
|
until @x >= lim
|
||||||
|
end repeat;
|
||||||
|
end|
|
||||||
|
call bug12297(10)|
|
||||||
|
drop procedure bug12297|
|
||||||
drop function if exists f_bug11247|
|
drop function if exists f_bug11247|
|
||||||
drop procedure if exists p_bug11247|
|
drop procedure if exists p_bug11247|
|
||||||
create function f_bug11247(param int)
|
create function f_bug11247(param int)
|
||||||
|
@ -152,4 +152,52 @@ drop procedure p1;
|
|||||||
drop function f1;
|
drop function f1;
|
||||||
drop table t1,t2;
|
drop table t1,t2;
|
||||||
|
|
||||||
|
# BUG#12637: User variables + SPs replication
|
||||||
|
create table t1 (a int);
|
||||||
|
delimiter //;
|
||||||
|
create procedure p1()
|
||||||
|
begin
|
||||||
|
insert into t1 values(@x);
|
||||||
|
set @x=@x+1;
|
||||||
|
insert into t1 values(@x);
|
||||||
|
if (f2()) then
|
||||||
|
insert into t1 values(1243);
|
||||||
|
end if;
|
||||||
|
end//
|
||||||
|
|
||||||
|
create function f2() returns int
|
||||||
|
begin
|
||||||
|
insert into t1 values(@z);
|
||||||
|
set @z=@z+1;
|
||||||
|
insert into t1 values(@z);
|
||||||
|
return 0;
|
||||||
|
end//
|
||||||
|
|
||||||
|
create function f1() returns int
|
||||||
|
begin
|
||||||
|
insert into t1 values(@y);
|
||||||
|
call p1();
|
||||||
|
return 0;
|
||||||
|
end//
|
||||||
|
|
||||||
|
delimiter ;//
|
||||||
|
|
||||||
|
set @x=10;
|
||||||
|
set @y=20;
|
||||||
|
set @z=100;
|
||||||
|
select f1();
|
||||||
|
|
||||||
|
set @x=30;
|
||||||
|
call p1();
|
||||||
|
|
||||||
|
select 'master', a from t1;
|
||||||
|
sync_slave_with_master;
|
||||||
|
connection slave;
|
||||||
|
select 'slave', a from t1;
|
||||||
|
|
||||||
|
connection master;
|
||||||
|
drop table t1;
|
||||||
|
drop function f1;
|
||||||
|
drop function f2;
|
||||||
|
drop procedure p1;
|
||||||
sync_slave_with_master;
|
sync_slave_with_master;
|
||||||
|
@ -3877,29 +3877,23 @@ drop function bug10055|
|
|||||||
# consumption by passing large input parameter.
|
# consumption by passing large input parameter.
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
|
||||||
# Note: the test is currenly disabled because of the
|
|
||||||
# Bug #12637: SP crashes the server if it has update query with user var
|
|
||||||
# & binlog is enabled.
|
|
||||||
#
|
|
||||||
|
|
||||||
--disable_warnings
|
--disable_warnings
|
||||||
#drop procedure if exists bug12297|
|
drop procedure if exists bug12297|
|
||||||
--enable_warnings
|
--enable_warnings
|
||||||
|
|
||||||
#create procedure bug12297(lim int)
|
create procedure bug12297(lim int)
|
||||||
#begin
|
begin
|
||||||
# set @x = 0;
|
set @x = 0;
|
||||||
# repeat
|
repeat
|
||||||
# insert into t1(id,data)
|
insert into t1(id,data)
|
||||||
# values('aa', @x);
|
values('aa', @x);
|
||||||
# set @x = @x + 1;
|
set @x = @x + 1;
|
||||||
# until @x >= lim
|
until @x >= lim
|
||||||
# end repeat;
|
end repeat;
|
||||||
#end|
|
end|
|
||||||
|
|
||||||
#call bug12297(10)|
|
call bug12297(10)|
|
||||||
#drop procedure bug12297|
|
drop procedure bug12297|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bug #11247 "Stored procedures: Function calls in long loops leak memory"
|
# Bug #11247 "Stored procedures: Function calls in long loops leak memory"
|
||||||
|
@ -3879,7 +3879,8 @@ int get_var_with_binlog(THD *thd, enum_sql_command sql_command,
|
|||||||
if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
|
if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
else if (var_entry->used_query_id == thd->query_id)
|
else if (var_entry->used_query_id == thd->query_id ||
|
||||||
|
mysql_bin_log.is_query_in_union(thd, var_entry->used_query_id))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
If this variable was already stored in user_var_events by this query
|
If this variable was already stored in user_var_events by this query
|
||||||
@ -3896,10 +3897,16 @@ int get_var_with_binlog(THD *thd, enum_sql_command sql_command,
|
|||||||
appears:
|
appears:
|
||||||
> set @a:=1;
|
> set @a:=1;
|
||||||
> insert into t1 values (@a), (@a:=@a+1), (@a:=@a+1);
|
> insert into t1 values (@a), (@a:=@a+1), (@a:=@a+1);
|
||||||
We have to write to binlog value @a= 1;
|
We have to write to binlog value @a= 1.
|
||||||
|
|
||||||
|
We allocate the user_var_event on user_var_events_alloc pool, not on
|
||||||
|
the this-statement-execution pool because in SPs user_var_event objects
|
||||||
|
may need to be valid after current [SP] statement execution pool is
|
||||||
|
destroyed.
|
||||||
*/
|
*/
|
||||||
size= ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)) + var_entry->length;
|
size= ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)) + var_entry->length;
|
||||||
if (!(user_var_event= (BINLOG_USER_VAR_EVENT *) thd->alloc(size)))
|
if (!(user_var_event= (BINLOG_USER_VAR_EVENT *)
|
||||||
|
alloc_root(thd->user_var_events_alloc, size)))
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
user_var_event->value= (char*) user_var_event +
|
user_var_event->value= (char*) user_var_event +
|
||||||
|
@ -1559,6 +1559,7 @@ void MYSQL_LOG::start_union_events(THD *thd)
|
|||||||
thd->binlog_evt_union.do_union= TRUE;
|
thd->binlog_evt_union.do_union= TRUE;
|
||||||
thd->binlog_evt_union.unioned_events= FALSE;
|
thd->binlog_evt_union.unioned_events= FALSE;
|
||||||
thd->binlog_evt_union.unioned_events_trans= FALSE;
|
thd->binlog_evt_union.unioned_events_trans= FALSE;
|
||||||
|
thd->binlog_evt_union.first_query_id= thd->query_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MYSQL_LOG::stop_union_events(THD *thd)
|
void MYSQL_LOG::stop_union_events(THD *thd)
|
||||||
@ -1567,6 +1568,12 @@ void MYSQL_LOG::stop_union_events(THD *thd)
|
|||||||
thd->binlog_evt_union.do_union= FALSE;
|
thd->binlog_evt_union.do_union= FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MYSQL_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
|
||||||
|
{
|
||||||
|
return (thd->binlog_evt_union.do_union &&
|
||||||
|
query_id_param >= thd->binlog_evt_union.first_query_id);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Write an event to the binary log
|
Write an event to the binary log
|
||||||
*/
|
*/
|
||||||
|
@ -678,10 +678,35 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b)
|
|||||||
* If this function invocation is done from a statement that is written
|
* If this function invocation is done from a statement that is written
|
||||||
into the binary log.
|
into the binary log.
|
||||||
* If there were any attempts to write events to the binary log during
|
* If there were any attempts to write events to the binary log during
|
||||||
function execution.
|
function execution (grep for start_union_events and stop_union_events)
|
||||||
|
|
||||||
If the answers are No and Yes, we write the function call into the binary
|
If the answers are No and Yes, we write the function call into the binary
|
||||||
log as "DO spfunc(<param1value>, <param2value>, ...)"
|
log as "DO spfunc(<param1value>, <param2value>, ...)"
|
||||||
|
|
||||||
|
|
||||||
|
4. Miscellaneous issues.
|
||||||
|
|
||||||
|
4.1 User variables.
|
||||||
|
|
||||||
|
When we call mysql_bin_log.write() for an SP statement, thd->user_var_events
|
||||||
|
must hold set<{var_name, value}> pairs for all user variables used during
|
||||||
|
the statement execution.
|
||||||
|
This set is produced by tracking user variable reads during statement
|
||||||
|
execution.
|
||||||
|
|
||||||
|
Fo SPs, this has the following implications:
|
||||||
|
1) thd->user_var_events may contain events from several SP statements and
|
||||||
|
needs to be valid after exection of these statements was finished. In
|
||||||
|
order to achieve that, we
|
||||||
|
* Allocate user_var_events array elements on appropriate mem_root (grep
|
||||||
|
for user_var_events_alloc).
|
||||||
|
* Use is_query_in_union() to determine if user_var_event is created.
|
||||||
|
|
||||||
|
2) We need to empty thd->user_var_events after we have wrote a function
|
||||||
|
call. This is currently done by making
|
||||||
|
reset_dynamic(&thd->user_var_events);
|
||||||
|
calls in several different places. (TODO cosider moving this into
|
||||||
|
mysql_bin_log.write() function)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -897,6 +922,7 @@ int sp_head::execute(THD *thd)
|
|||||||
/* Don't change NOW() in FUNCTION or TRIGGER */
|
/* Don't change NOW() in FUNCTION or TRIGGER */
|
||||||
if (!thd->in_sub_stmt)
|
if (!thd->in_sub_stmt)
|
||||||
thd->set_time(); // Make current_time() et al work
|
thd->set_time(); // Make current_time() et al work
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We have to set thd->stmt_arena before executing the instruction
|
We have to set thd->stmt_arena before executing the instruction
|
||||||
to store in the instruction free_list all new items, created
|
to store in the instruction free_list all new items, created
|
||||||
@ -904,6 +930,13 @@ int sp_head::execute(THD *thd)
|
|||||||
items made during other permanent subquery transformations).
|
items made during other permanent subquery transformations).
|
||||||
*/
|
*/
|
||||||
thd->stmt_arena= i;
|
thd->stmt_arena= i;
|
||||||
|
|
||||||
|
/* will binlog this separately */
|
||||||
|
if (thd->prelocked_mode == NON_PRELOCKED) //TODO: change to event union?
|
||||||
|
{
|
||||||
|
thd->user_var_events_alloc= thd->mem_root;
|
||||||
|
}
|
||||||
|
|
||||||
ret= i->execute(thd, &ip);
|
ret= i->execute(thd, &ip);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -918,15 +951,6 @@ int sp_head::execute(THD *thd)
|
|||||||
|
|
||||||
/* we should cleanup free_list and memroot, used by instruction */
|
/* we should cleanup free_list and memroot, used by instruction */
|
||||||
thd->free_items();
|
thd->free_items();
|
||||||
/*
|
|
||||||
FIXME: we must free user var events only if the routine is executed
|
|
||||||
in non-prelocked mode and statement-by-statement replication is used.
|
|
||||||
But if we don't free them now, the server crashes because user var
|
|
||||||
events are allocated in execute_mem_root. This is Bug#12637, and when
|
|
||||||
it's fixed, please add if (thd->options & OPTION_BIN_LOG) here.
|
|
||||||
*/
|
|
||||||
if (opt_bin_log)
|
|
||||||
reset_dynamic(&thd->user_var_events);
|
|
||||||
free_root(&execute_mem_root, MYF(0));
|
free_root(&execute_mem_root, MYF(0));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1084,7 +1108,10 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
|
|||||||
binlog_save_options= thd->options;
|
binlog_save_options= thd->options;
|
||||||
need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG);
|
need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG);
|
||||||
if (need_binlog_call)
|
if (need_binlog_call)
|
||||||
|
{
|
||||||
|
reset_dynamic(&thd->user_var_events);
|
||||||
mysql_bin_log.start_union_events(thd);
|
mysql_bin_log.start_union_events(thd);
|
||||||
|
}
|
||||||
|
|
||||||
thd->options&= ~OPTION_BIN_LOG;
|
thd->options&= ~OPTION_BIN_LOG;
|
||||||
ret= execute(thd);
|
ret= execute(thd);
|
||||||
@ -1118,6 +1145,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
|
|||||||
"Invoked ROUTINE modified a transactional table but MySQL "
|
"Invoked ROUTINE modified a transactional table but MySQL "
|
||||||
"failed to reflect this change in the binary log");
|
"failed to reflect this change in the binary log");
|
||||||
}
|
}
|
||||||
|
reset_dynamic(&thd->user_var_events);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_type == TYPE_ENUM_FUNCTION && ret == 0)
|
if (m_type == TYPE_ENUM_FUNCTION && ret == 0)
|
||||||
|
@ -108,13 +108,14 @@ class sp_head :private Query_arena
|
|||||||
MEM_ROOT main_mem_root;
|
MEM_ROOT main_mem_root;
|
||||||
public:
|
public:
|
||||||
/* Possible values of m_flags */
|
/* Possible values of m_flags */
|
||||||
const static int
|
enum {
|
||||||
HAS_RETURN= 1, // For FUNCTIONs only: is set if has RETURN
|
HAS_RETURN= 1, // For FUNCTIONs only: is set if has RETURN
|
||||||
IN_SIMPLE_CASE= 2, // Is set if parsing a simple CASE
|
IN_SIMPLE_CASE= 2, // Is set if parsing a simple CASE
|
||||||
IN_HANDLER= 4, // Is set if the parser is in a handler body
|
IN_HANDLER= 4, // Is set if the parser is in a handler body
|
||||||
MULTI_RESULTS= 8, // Is set if a procedure with SELECT(s)
|
MULTI_RESULTS= 8, // Is set if a procedure with SELECT(s)
|
||||||
CONTAINS_DYNAMIC_SQL= 16, // Is set if a procedure with PREPARE/EXECUTE
|
CONTAINS_DYNAMIC_SQL= 16, // Is set if a procedure with PREPARE/EXECUTE
|
||||||
IS_INVOKED= 32; // Is set if this sp_head is being used.
|
IS_INVOKED= 32 // Is set if this sp_head is being used.
|
||||||
|
};
|
||||||
|
|
||||||
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
|
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
|
||||||
uint m_flags; // Boolean attributes of a stored routine
|
uint m_flags; // Boolean attributes of a stored routine
|
||||||
|
@ -313,6 +313,7 @@ public:
|
|||||||
|
|
||||||
void start_union_events(THD *thd);
|
void start_union_events(THD *thd);
|
||||||
void stop_union_events(THD *thd);
|
void stop_union_events(THD *thd);
|
||||||
|
bool is_query_in_union(THD *thd, query_id_t query_id_param);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
v stands for vector
|
v stands for vector
|
||||||
@ -1303,8 +1304,9 @@ public:
|
|||||||
/* variables.transaction_isolation is reset to this after each commit */
|
/* variables.transaction_isolation is reset to this after each commit */
|
||||||
enum_tx_isolation session_tx_isolation;
|
enum_tx_isolation session_tx_isolation;
|
||||||
enum_check_fields count_cuted_fields;
|
enum_check_fields count_cuted_fields;
|
||||||
/* for user variables replication*/
|
|
||||||
DYNAMIC_ARRAY user_var_events;
|
DYNAMIC_ARRAY user_var_events; /* For user variables replication */
|
||||||
|
MEM_ROOT *user_var_events_alloc; /* Allocate above array elements here */
|
||||||
|
|
||||||
enum killed_state { NOT_KILLED=0, KILL_BAD_DATA=1, KILL_CONNECTION=ER_SERVER_SHUTDOWN, KILL_QUERY=ER_QUERY_INTERRUPTED };
|
enum killed_state { NOT_KILLED=0, KILL_BAD_DATA=1, KILL_CONNECTION=ER_SERVER_SHUTDOWN, KILL_QUERY=ER_QUERY_INTERRUPTED };
|
||||||
killed_state volatile killed;
|
killed_state volatile killed;
|
||||||
@ -1366,6 +1368,12 @@ public:
|
|||||||
mysql_bin_log.start_union_events() call.
|
mysql_bin_log.start_union_events() call.
|
||||||
*/
|
*/
|
||||||
bool unioned_events_trans;
|
bool unioned_events_trans;
|
||||||
|
|
||||||
|
/*
|
||||||
|
'queries' (actually SP statements) that run under inside this binlog
|
||||||
|
union have thd->query_id >= first_query_id.
|
||||||
|
*/
|
||||||
|
query_id_t first_query_id;
|
||||||
} binlog_evt_union;
|
} binlog_evt_union;
|
||||||
|
|
||||||
THD();
|
THD();
|
||||||
|
@ -5162,7 +5162,10 @@ void mysql_reset_thd_for_next_command(THD *thd)
|
|||||||
if (!thd->in_sub_stmt)
|
if (!thd->in_sub_stmt)
|
||||||
{
|
{
|
||||||
if (opt_bin_log)
|
if (opt_bin_log)
|
||||||
|
{
|
||||||
reset_dynamic(&thd->user_var_events);
|
reset_dynamic(&thd->user_var_events);
|
||||||
|
thd->user_var_events_alloc= thd->mem_root;
|
||||||
|
}
|
||||||
thd->clear_error();
|
thd->clear_error();
|
||||||
thd->total_warn_count=0; // Warnings for this query
|
thd->total_warn_count=0; // Warnings for this query
|
||||||
thd->rand_used= 0;
|
thd->rand_used= 0;
|
||||||
|
Reference in New Issue
Block a user