Commit 759a3c1e authored by guilhem@mysql.com's avatar guilhem@mysql.com

2 minor edits, plus

fix for BUG#1113 "INSERT into non-trans table SELECT ; ROLLBACK" does not send warning"
and
fix for BUG#873 "In transaction, INSERT to non-trans table is written too early to binlog".
Now we don't always write the non-trans update immediately to the binlog;
if there is something in the binlog cache we write it to the binlog cache
(because the non-trans update could depend on a trans table which was modified
earlier in the transaction); then in case of ROLLBACK, we write the binlog
cache to the binlog, wrapped with BEGIN/ROLLBACK.
This guarantees that the slave does the same updates.
For ROLLBACK TO SAVEPOINT: when we execute a SAVEPOINT command we write it
to the binlog cache. At ROLLBACK TO SAVEPOINT, if some non-trans table was updated,
we write ROLLBACK TO SAVEPOINT to the binlog cache; when the transaction
terminates (COMMIT/ROLLBACK), the binlog cache will be flushed to the binlog
(because of the non-trans update) so we'll have SAVEPOINT and ROLLBACK TO
SAVEPOINT in the binlog.

Apart from this rare case of updates of mixed table types in transaction, the
usual way is still clear the binlog cache at ROLLBACK, or chop it at
ROLLBACK TO SAVEPOINT (meaning the SAVEPOINT command is also chopped, which
is fine).
Note that BUG#873 encompasses subbugs 1) and 2) of BUG#333 "3 binlogging bugs when doing INSERT with mixed InnoDB/MyISAM".
parent 165dc895
......@@ -1459,9 +1459,8 @@ int main(int argc, char **argv)
fprintf(md_result_file,
"\n--\n-- Position to start replication from\n--\n\n");
fprintf(md_result_file,
"CHANGE MASTER TO MASTER_LOG_FILE='%s' ;\n", row[0]);
fprintf(md_result_file, "CHANGE MASTER TO MASTER_LOG_POS=%s ;\n",
row[1]);
"CHANGE MASTER TO MASTER_LOG_FILE='%s', \
MASTER_LOG_POS=%s ;\n",row[0],row[1]);
}
mysql_free_result(master);
}
......
drop table if exists ti, tm;
create table ti (a int) type=innodb;
create table tm (a int) type=myisam;
reset master;
begin;
insert into ti values(1);
insert into tm select * from ti;
commit;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(1)
master-bin.001 178 Query 1 79 use test; insert into tm select * from ti
master-bin.001 244 Query 1 244 use test; COMMIT
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(2);
insert into tm select * from ti;
rollback;
Warning: Some non-transactional changed tables couldn't be rolled back
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(2)
master-bin.001 178 Query 1 79 use test; insert into tm select * from ti
master-bin.001 244 Query 1 244 use test; ROLLBACK
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(3);
savepoint my_savepoint;
insert into ti values(4);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
Warning: Some non-transactional changed tables couldn't be rolled back
commit;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(3)
master-bin.001 178 Query 1 79 use test; savepoint my_savepoint
master-bin.001 235 Query 1 79 use test; insert into ti values(4)
master-bin.001 294 Query 1 79 use test; insert into tm select * from ti
master-bin.001 360 Query 1 79 use test; rollback to savepoint my_savepoint
master-bin.001 429 Query 1 429 use test; COMMIT
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(5);
savepoint my_savepoint;
insert into ti values(6);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
Warning: Some non-transactional changed tables couldn't be rolled back
insert into ti values(7);
commit;
select a from ti order by a;
a
5
7
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(5)
master-bin.001 178 Query 1 79 use test; savepoint my_savepoint
master-bin.001 235 Query 1 79 use test; insert into ti values(6)
master-bin.001 294 Query 1 79 use test; insert into tm select * from ti
master-bin.001 360 Query 1 79 use test; rollback to savepoint my_savepoint
master-bin.001 429 Query 1 79 use test; insert into ti values(7)
master-bin.001 488 Query 1 488 use test; COMMIT
delete from ti;
delete from tm;
reset master;
select get_lock("a",10);
get_lock("a",10)
1
begin;
insert into ti values(8);
insert into tm select * from ti;
select get_lock("a",10);
get_lock("a",10)
1
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(8)
master-bin.001 178 Query 1 79 use test; insert into tm select * from ti
master-bin.001 244 Query 1 244 use test; ROLLBACK
delete from ti;
delete from tm;
reset master;
insert into ti values(9);
insert into tm select * from ti;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; insert into ti values(9)
master-bin.001 138 Query 1 138 use test; insert into tm select * from ti
delete from ti;
delete from tm;
reset master;
insert into ti values(10);
begin;
insert into tm select * from ti;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; insert into ti values(10)
master-bin.001 139 Query 1 139 use test; insert into tm select * from ti
insert into ti values(11);
commit;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; insert into ti values(10)
master-bin.001 139 Query 1 139 use test; insert into tm select * from ti
master-bin.001 205 Query 1 205 use test; BEGIN
master-bin.001 245 Query 1 205 use test; insert into ti values(11)
master-bin.001 305 Query 1 305 use test; COMMIT
alter table tm type=INNODB;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(12);
insert into tm select * from ti;
commit;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(12)
master-bin.001 179 Query 1 79 use test; insert into tm select * from ti
master-bin.001 245 Query 1 245 use test; COMMIT
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(13);
insert into tm select * from ti;
rollback;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(14);
savepoint my_savepoint;
insert into ti values(15);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
commit;
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(14)
master-bin.001 179 Query 1 179 use test; COMMIT
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(16);
savepoint my_savepoint;
insert into ti values(17);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
insert into ti values(18);
commit;
select a from ti order by a;
a
16
18
show binlog events from 79;
Log_name Pos Event_type Server_id Orig_log_pos Info
master-bin.001 79 Query 1 79 use test; BEGIN
master-bin.001 119 Query 1 79 use test; insert into ti values(16)
master-bin.001 179 Query 1 79 use test; insert into ti values(18)
master-bin.001 239 Query 1 239 use test; COMMIT
drop table ti,tm;
......@@ -20,9 +20,9 @@ day id category name
2003-02-22 2461 b a a a @ %  ' " a
2003-03-22 2161 c asdf
2003-04-22 2416 a bbbbb
show binlog events from 898;
Log_name Pos Event_type Server_id Orig_log_pos Info
slave-bin.001 898 Query 1 898 use test; insert into t3 select * from t2
show master status;
File Position Binlog_do_db Binlog_ignore_db
slave-bin.001 964
drop table t1;
drop table t2;
drop table t3;
......
# Check that binlog is ok when a transaction mixes updates to InnoDB and
# MyISAM. It would be nice to make this a replication test, but in 4.0 the slave
# is always with --skip-innodb in the testsuite. I (Guilhem) however did some
# tests manually on a slave; tables are replicated fine and Exec_master_log_pos
# advances as expected.
-- source include/have_innodb.inc
connect (con1,localhost,root,,);
connect (con2,localhost,root,,);
connection con1;
drop table if exists ti, tm;
create table ti (a int) type=innodb;
create table tm (a int) type=myisam;
reset master;
begin;
insert into ti values(1);
insert into tm select * from ti;
commit;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(2);
insert into tm select * from ti;
# should say some changes to non-transactional tables couldn't be rolled back
--error 1196
rollback;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(3);
savepoint my_savepoint;
insert into ti values(4);
insert into tm select * from ti;
--error 1196
rollback to savepoint my_savepoint;
commit;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(5);
savepoint my_savepoint;
insert into ti values(6);
insert into tm select * from ti;
--error 1196
rollback to savepoint my_savepoint;
insert into ti values(7);
commit;
select a from ti order by a; # check that savepoints work :)
show binlog events from 79;
# and when ROLLBACK is not explicit?
delete from ti;
delete from tm;
reset master;
select get_lock("a",10);
begin;
insert into ti values(8);
insert into tm select * from ti;
disconnect con1;
connection con2;
# We want to SHOW BINLOG EVENTS, to know what was logged. But there is no
# guarantee that logging of the terminated con1 has been done yet (it may not
# even be started, so con1 may have not even attempted to lock the binlog yet;
# so SHOW BINLOG EVENTS may come before con1 does the loggin. To be sure that
# logging has been done, we use a user lock.
select get_lock("a",10);
show binlog events from 79;
# and when not in a transaction?
delete from ti;
delete from tm;
reset master;
insert into ti values(9);
insert into tm select * from ti;
show binlog events from 79;
# Check that when the query updating the MyISAM table is the first in the
# transaction, we log it immediately.
delete from ti;
delete from tm;
reset master;
insert into ti values(10); # first make ti non-empty
begin;
insert into tm select * from ti;
show binlog events from 79;
insert into ti values(11);
commit;
show binlog events from 79;
# Check that things work like before this BEGIN/ROLLBACK code was added, when tm
# is INNODB
alter table tm type=INNODB;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(12);
insert into tm select * from ti;
commit;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(13);
insert into tm select * from ti;
rollback;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(14);
savepoint my_savepoint;
insert into ti values(15);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
commit;
show binlog events from 79;
delete from ti;
delete from tm;
reset master;
begin;
insert into ti values(16);
savepoint my_savepoint;
insert into ti values(17);
insert into tm select * from ti;
rollback to savepoint my_savepoint;
insert into ti values(18);
commit;
select a from ti order by a; # check that savepoints work :)
show binlog events from 79;
drop table ti,tm;
......@@ -32,15 +32,13 @@ sync_with_master;
select * from t1;
select * from t3;
# We want to be sure that LOAD DATA is in the slave's binlog.
# But we can't simply read this binlog, because the file_id is uncertain (would
# cause test failures). So instead, we test if the binlog looks long enough to
# But we can't simply read this binlog, because as the slave has not been
# restarted for this test, the file_id is uncertain (would cause test
# failures). So instead, we test if the binlog looks long enough to
# contain LOAD DATA. That is, I (Guilhem) have done SHOW BINLOG EVENTS on my
# machine, saw that the last event is 'create table t3' and is at position 898
# when things go fine. If LOAD DATA was not logged, the binlog would be shorter
# than 898 bytes and there would be an error in SHOW BINLOG EVENTS. Of course,
# if someone changes the content of '../../std_data/rpl_loaddata2.dat', 898 will
# have to be changed too.
show binlog events from 898;
# machine, saw that the binlog is of size 964 when things go fine.
# If LOAD DATA was not logged, the binlog would be shorter.
show master status;
connection master;
......
......@@ -349,7 +349,7 @@ int ha_commit_trans(THD *thd, THD_TRANS* trans)
if (trans == &thd->transaction.all && mysql_bin_log.is_open() &&
my_b_tell(&thd->transaction.trans_log))
{
mysql_bin_log.write(thd, &thd->transaction.trans_log);
mysql_bin_log.write(thd, &thd->transaction.trans_log, 1);
reinit_io_cache(&thd->transaction.trans_log,
WRITE_CACHE, (my_off_t) 0, 0, 1);
thd->transaction.trans_log.end_of_file= max_binlog_cache_size;
......@@ -432,9 +432,21 @@ int ha_rollback_trans(THD *thd, THD_TRANS *trans)
}
#endif
if (trans == &thd->transaction.all)
{
/*
Update the binary log with a BEGIN/ROLLBACK block if we have cached some
queries and we updated some non-transactional table. Such cases should
be rare (updating a non-transactional table inside a transaction...).
*/
if (unlikely((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) &&
mysql_bin_log.is_open() &&
my_b_tell(&thd->transaction.trans_log)))
mysql_bin_log.write(thd, &thd->transaction.trans_log, 0);
/* Flushed or not, empty the binlog cache */
reinit_io_cache(&thd->transaction.trans_log,
WRITE_CACHE, (my_off_t) 0, 0, 1);
thd->transaction.trans_log.end_of_file= max_binlog_cache_size;
WRITE_CACHE, (my_off_t) 0, 0, 1);
thd->transaction.trans_log.end_of_file= max_binlog_cache_size;
}
thd->variables.tx_isolation=thd->session_tx_isolation;
if (operation_done)
{
......@@ -448,9 +460,27 @@ int ha_rollback_trans(THD *thd, THD_TRANS *trans)
/*
Rolls the current transaction back to a savepoint.
Return value: 0 if success, 1 if there was not a savepoint of the given
name.
Rolls the current transaction back to a savepoint.
Return value: 0 if success, 1 if there was not a savepoint of the given
name.
NOTE: how do we handle this (unlikely but legal) case:
[transaction] + [update to non-trans table] + [rollback to savepoint] ?
The problem occurs when a savepoint is before the update to the
non-transactional table. Then when there's a rollback to the savepoint, if we
simply truncate the binlog cache, we lose the part of the binlog cache where
the update is. If we want to not lose it, we need to write the SAVEPOINT
command and the ROLLBACK TO SAVEPOINT command to the binlog cache. The latter
is easy: it's just write at the end of the binlog cache, but the former should
be *inserted* to the place where the user called SAVEPOINT. The solution is
that when the user calls SAVEPOINT, we write it to the binlog cache (so no
need to later insert it). As transactions are never intermixed in the binary log
(i.e. they are serialized), we won't have conflicts with savepoint names when
using mysqlbinlog or in the slave SQL thread.
Then when ROLLBACK TO SAVEPOINT is called, if we updated some
non-transactional table, we don't truncate the binlog cache but instead write
ROLLBACK TO SAVEPOINT to it; otherwise we truncate the binlog cache (which
will chop the SAVEPOINT command from the binlog cache, which is good as in
that case there is no need to have it in the binlog).
*/
int ha_rollback_to_savepoint(THD *thd, char *savepoint_name)
......@@ -475,8 +505,24 @@ int ha_rollback_to_savepoint(THD *thd, char *savepoint_name)
error=1;
}
else
reinit_io_cache(&thd->transaction.trans_log, WRITE_CACHE,
binlog_cache_pos, 0, 0);
{
/*
Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some
non-transactional table. Otherwise, truncate the binlog cache starting
from the SAVEPOINT command.
*/
if (unlikely((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) &&
mysql_bin_log.is_open() &&
my_b_tell(&thd->transaction.trans_log)))
{
Query_log_event qinfo(thd, thd->query, thd->query_length, TRUE);
if (mysql_bin_log.write(&qinfo))
error= 1;
}
else
reinit_io_cache(&thd->transaction.trans_log, WRITE_CACHE,
binlog_cache_pos, 0, 0);
}
operation_done=1;
#endif
if (operation_done)
......@@ -505,6 +551,13 @@ int ha_savepoint(THD *thd, char *savepoint_name)
#ifdef HAVE_INNOBASE_DB
innobase_savepoint(thd,savepoint_name, binlog_cache_pos);
#endif
/* Write it to the binary log (see comments of ha_rollback_to_savepoint). */
if (mysql_bin_log.is_open())
{
Query_log_event qinfo(thd, thd->query, thd->query_length, TRUE);
if (mysql_bin_log.write(&qinfo))
error= 1;
}
}
#endif /* USING_TRANSACTIONS */
DBUG_RETURN(error);
......
......@@ -1062,7 +1062,17 @@ bool MYSQL_LOG::write(Log_event* event_info)
bool should_rotate = 0;
const char *local_db = event_info->get_db();
#ifdef USING_TRANSACTIONS
IO_CACHE *file = ((event_info->get_cache_stmt()) ?
/*
Should we write to the binlog cache or to the binlog on disk?
Write to the binlog cache if:
- it is already not empty (meaning we're in a transaction; note that the
present event could be about a non-transactional table, but still we need
to write to the binlog cache in that case to handle updates to mixed
trans/non-trans table types the best possible in binlogging)
- or if the event asks for it (cache_stmt == true).
*/
IO_CACHE *file = ((event_info->get_cache_stmt() ||
my_b_tell(&thd->transaction.trans_log)) ?
&thd->transaction.trans_log :
&log_file);
#else
......@@ -1258,6 +1268,13 @@ uint MYSQL_LOG::next_file_id()
/*
Write a cached log entry to the binary log
SYNOPSIS
write()
thd
cache The cache to copy to the binlog
commit_or_rollback If true, will write "COMMIT" in the end, if false will
write "ROLLBACK".
NOTE
- We only come here if there is something in the cache.
- The thing in the cache is always a complete transaction
......@@ -1265,10 +1282,13 @@ uint MYSQL_LOG::next_file_id()
IMPLEMENTATION
- To support transaction over replication, we wrap the transaction
with BEGIN/COMMIT in the binary log.
with BEGIN/COMMIT or BEGIN/ROLLBACK in the binary log.
We want to write a BEGIN/ROLLBACK block when a non-transactional table was
updated in a transaction which was rolled back. This is to ensure that the
same updates are run on the slave.
*/
bool MYSQL_LOG::write(THD *thd, IO_CACHE *cache)
bool MYSQL_LOG::write(THD *thd, IO_CACHE *cache, bool commit_or_rollback)
{
VOID(pthread_mutex_lock(&LOCK_log));
DBUG_ENTER("MYSQL_LOG::write(cache");
......@@ -1322,7 +1342,10 @@ bool MYSQL_LOG::write(THD *thd, IO_CACHE *cache)
*/
{
Query_log_event qinfo(thd, "COMMIT", 6, TRUE);
Query_log_event qinfo(thd,
commit_or_rollback ? "COMMIT" : "ROLLBACK",
commit_or_rollback ? 6 : 8,
TRUE);
qinfo.set_log_pos(this);
if (qinfo.write(&log_file) || flush_io_cache(&log_file))
goto err;
......
......@@ -1803,7 +1803,7 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli)
*/
if (!strcmp(thd->query,"BEGIN"))
rli->inside_transaction= opt_using_transactions;
else if (!strcmp(thd->query,"COMMIT"))
else if (!(strcmp(thd->query,"COMMIT") && strcmp(thd->query,"ROLLBACK")))
rli->inside_transaction=0;
/*
......
......@@ -140,7 +140,7 @@ public:
bool write(THD *thd, const char *query, uint query_length,
time_t query_start=0);
bool write(Log_event* event_info); // binary log write
bool write(THD *thd, IO_CACHE *cache);
bool write(THD *thd, IO_CACHE *cache, bool commit_or_rollback);
/*
v stands for vector
......
......@@ -1367,6 +1367,8 @@ void select_insert::send_error(uint errcode,const char *err)
table->file->has_transactions());
mysql_bin_log.write(&qinfo);
}
if (!table->tmp_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
}
ha_rollback_stmt(thd);
if (info.copied || info.deleted)
......@@ -1398,6 +1400,8 @@ bool select_insert::send_eof()
if (info.copied || info.deleted)
{
query_cache_invalidate3(thd, table, 1);
if (!(table->file->has_transactions() || table->tmp_table))
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
}
if (error)
{
......
......@@ -2553,7 +2553,16 @@ mysql_execute_command(void)
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
if (!ha_rollback(thd))
{
if (thd->options & OPTION_STATUS_NO_TRANS_UPDATE)
/*
If a non-transactional table was updated, warn; don't warn if this is a
slave thread (because when a slave thread executes a ROLLBACK, it has
been read from the binary log, so it's 100% sure and normal to produce
error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
slave SQL thread, it would not stop the thread but just be printed in
the error log; but we don't want users to wonder why they have this
message in the error log, so we don't send it.
*/
if ((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) && !thd->slave_thread)
send_warning(&thd->net,ER_WARNING_NOT_COMPLETE_ROLLBACK,0);
else
send_ok(&thd->net);
......@@ -2565,7 +2574,7 @@ mysql_execute_command(void)
case SQLCOM_ROLLBACK_TO_SAVEPOINT:
if (!ha_rollback_to_savepoint(thd, lex->savepoint_name))
{
if (thd->options & OPTION_STATUS_NO_TRANS_UPDATE)
if ((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) && !thd->slave_thread)
send_warning(&thd->net,ER_WARNING_NOT_COMPLETE_ROLLBACK,0);
else
send_ok(&thd->net);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment