From 0394cf203042eb6e408b9c88802c93444f226af9 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Sep 2010 15:20:15 +0200 Subject: [PATCH 01/35] MWL#116: Efficient group commit for binary log Preliminary commit for testing --- mysql-test/r/group_commit.result | 63 + mysql-test/suite/binlog/r/binlog_ioerr.result | 28 + mysql-test/suite/binlog/t/binlog_ioerr.test | 29 + mysql-test/t/group_commit.test | 115 ++ sql/handler.cc | 224 +-- sql/handler.h | 89 +- sql/log.cc | 1292 +++++++++++++---- sql/log.h | 209 ++- sql/log_event.h | 5 +- sql/mysqld.cc | 3 + sql/sql_class.cc | 6 +- sql/sql_class.h | 4 + sql/sql_load.cc | 2 - sql/table.cc | 10 +- sql/table.h | 1 - storage/xtradb/handler/ha_innodb.cc | 208 ++- 16 files changed, 1821 insertions(+), 467 deletions(-) create mode 100644 mysql-test/r/group_commit.result create mode 100644 mysql-test/suite/binlog/r/binlog_ioerr.result create mode 100644 mysql-test/suite/binlog/t/binlog_ioerr.test create mode 100644 mysql-test/t/group_commit.test diff --git a/mysql-test/r/group_commit.result b/mysql-test/r/group_commit.result new file mode 100644 index 00000000000..c7993227f8f --- /dev/null +++ b/mysql-test/r/group_commit.result @@ -0,0 +1,63 @@ +CREATE TABLE t1 (a VARCHAR(10) PRIMARY KEY) ENGINE=innodb; +SELECT variable_value INTO @commits FROM information_schema.global_status +WHERE variable_name = 'binlog_commits'; +SELECT variable_value INTO @group_commits FROM information_schema.global_status +WHERE variable_name = 'binlog_group_commits'; +SET DEBUG_SYNC= "commit_after_group_log_xid SIGNAL group1_running WAIT_FOR group2_queued"; +INSERT INTO t1 VALUES ("con1"); +set DEBUG_SYNC= "now WAIT_FOR group1_running"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; +SET DEBUG_SYNC= "commit_after_release_LOCK_group_commit WAIT_FOR group3_committed"; +SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; +INSERT INTO t1 VALUES ("con2"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con2"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con3"; +INSERT INTO t1 VALUES ("con3"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con3"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con4"; +INSERT INTO t1 VALUES ("con4"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con4"; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM t1 ORDER BY a; +a +SET DEBUG_SYNC= "now SIGNAL group2_queued"; +SELECT * FROM t1 ORDER BY a; +a +con1 +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; +SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con5_leader WAIT_FOR con6_queued"; +INSERT INTO t1 VALUES ("con5"); +SET DEBUG_SYNC= "now WAIT_FOR con5_leader"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con6_queued"; +INSERT INTO t1 VALUES ("con6"); +SET DEBUG_SYNC= "now WAIT_FOR group3_con5"; +SELECT * FROM t1 ORDER BY a; +a +con1 +SET DEBUG_SYNC= "now SIGNAL group3_committed"; +SET DEBUG_SYNC= "now WAIT_FOR group2_visible"; +SELECT * FROM t1 ORDER BY a; +a +con1 +con2 +con3 +con4 +SET DEBUG_SYNC= "now SIGNAL group2_checked"; +SELECT * FROM t1 ORDER BY a; +a +con1 +con2 +con3 +con4 +con5 +con6 +SELECT variable_value - @commits FROM information_schema.global_status +WHERE variable_name = 'binlog_commits'; +variable_value - @commits +6 +SELECT variable_value - @group_commits FROM information_schema.global_status +WHERE variable_name = 'binlog_group_commits'; +variable_value - @group_commits +3 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/suite/binlog/r/binlog_ioerr.result b/mysql-test/suite/binlog/r/binlog_ioerr.result new file mode 100644 index 00000000000..2300f3c5f82 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_ioerr.result @@ -0,0 +1,28 @@ +CALL mtr.add_suppression("Error writing file 'master-bin'"); +RESET MASTER; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES(0); +SET SESSION debug='+d,fail_binlog_write_1'; +INSERT INTO t1 VALUES(1); +ERROR HY000: Error writing file 'master-bin' (errno: 22) +INSERT INTO t1 VALUES(2); +ERROR HY000: Error writing file 'master-bin' (errno: 22) +SET SESSION debug=''; +INSERT INTO t1 VALUES(3); +SELECT * FROM t1; +a +0 +3 +SHOW BINLOG EVENTS; +Log_name Pos Event_type Server_id End_log_pos Info +BINLOG POS Format_desc 1 ENDPOS Server ver: #, Binlog ver: # +BINLOG POS Query 1 ENDPOS use `test`; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb +BINLOG POS Query 1 ENDPOS BEGIN +BINLOG POS Query 1 ENDPOS use `test`; INSERT INTO t1 VALUES(0) +BINLOG POS Xid 1 ENDPOS COMMIT /* XID */ +BINLOG POS Query 1 ENDPOS BEGIN +BINLOG POS Query 1 ENDPOS BEGIN +BINLOG POS Query 1 ENDPOS BEGIN +BINLOG POS Query 1 ENDPOS use `test`; INSERT INTO t1 VALUES(3) +BINLOG POS Xid 1 ENDPOS COMMIT /* XID */ +DROP TABLE t1; diff --git a/mysql-test/suite/binlog/t/binlog_ioerr.test b/mysql-test/suite/binlog/t/binlog_ioerr.test new file mode 100644 index 00000000000..e58cd93361a --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_ioerr.test @@ -0,0 +1,29 @@ +source include/have_debug.inc; +source include/have_innodb.inc; +source include/have_log_bin.inc; +source include/have_binlog_format_mixed_or_statement.inc; + +CALL mtr.add_suppression("Error writing file 'master-bin'"); + +RESET MASTER; + +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES(0); +SET SESSION debug='+d,fail_binlog_write_1'; +--error ER_ERROR_ON_WRITE +INSERT INTO t1 VALUES(1); +--error ER_ERROR_ON_WRITE +INSERT INTO t1 VALUES(2); +SET SESSION debug=''; +INSERT INTO t1 VALUES(3); +SELECT * FROM t1; + +# Actually the output from this currently shows a bug. +# The injected IO error leaves partially written transactions in the binlog in +# the form of stray "BEGIN" events. +# These should disappear from the output if binlog error handling is improved. +--replace_regex /\/\* xid=.* \*\//\/* XID *\// /Server ver: .*, Binlog ver: .*/Server ver: #, Binlog ver: #/ /table_id: [0-9]+/table_id: #/ +--replace_column 1 BINLOG 2 POS 5 ENDPOS +SHOW BINLOG EVENTS; + +DROP TABLE t1; diff --git a/mysql-test/t/group_commit.test b/mysql-test/t/group_commit.test new file mode 100644 index 00000000000..df4ea6654d4 --- /dev/null +++ b/mysql-test/t/group_commit.test @@ -0,0 +1,115 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc + +# Test some group commit code paths by using debug_sync to do controlled +# commits of 6 transactions: first 1 alone, then 3 as a group, then 2 as a +# group. +# +# Group 3 is allowed to race as far as possible ahead before group 2 finishes +# to check some edge case for concurrency control. + +CREATE TABLE t1 (a VARCHAR(10) PRIMARY KEY) ENGINE=innodb; + +SELECT variable_value INTO @commits FROM information_schema.global_status + WHERE variable_name = 'binlog_commits'; +SELECT variable_value INTO @group_commits FROM information_schema.global_status + WHERE variable_name = 'binlog_group_commits'; + +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); +connect(con3,localhost,root,,); +connect(con4,localhost,root,,); +connect(con5,localhost,root,,); +connect(con6,localhost,root,,); + +# Start group1 (with one thread) doing commit, waiting for +# group2 to queue up before finishing. + +connection con1; +SET DEBUG_SYNC= "commit_after_group_log_xid SIGNAL group1_running WAIT_FOR group2_queued"; +send INSERT INTO t1 VALUES ("con1"); + +# Make group2 (with three threads) queue up. +# Make sure con2 is the group commit leader for group2. +# Make group2 wait with running commit_ordered() until group3 has committed. + +connection con2; +set DEBUG_SYNC= "now WAIT_FOR group1_running"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; +SET DEBUG_SYNC= "commit_after_release_LOCK_group_commit WAIT_FOR group3_committed"; +SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; +send INSERT INTO t1 VALUES ("con2"); +connection con3; +SET DEBUG_SYNC= "now WAIT_FOR group2_con2"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con3"; +send INSERT INTO t1 VALUES ("con3"); +connection con4; +SET DEBUG_SYNC= "now WAIT_FOR group2_con3"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con4"; +send INSERT INTO t1 VALUES ("con4"); + +# When group2 is queued, let group1 continue and queue group3. + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR group2_con4"; + +# At this point, trasaction 1 is still not visible as commit_ordered() has not +# been called yet. +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM t1 ORDER BY a; + +SET DEBUG_SYNC= "now SIGNAL group2_queued"; +connection con1; +reap; + +# Now transaction 1 is visible. +connection default; +SELECT * FROM t1 ORDER BY a; + +connection con5; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; +SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con5_leader WAIT_FOR con6_queued"; +send INSERT INTO t1 VALUES ("con5"); + +connection con6; +SET DEBUG_SYNC= "now WAIT_FOR con5_leader"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con6_queued"; +send INSERT INTO t1 VALUES ("con6"); + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR group3_con5"; +# Still only transaction 1 visible, as group2 have not yet run commit_ordered(). +SELECT * FROM t1 ORDER BY a; +SET DEBUG_SYNC= "now SIGNAL group3_committed"; +SET DEBUG_SYNC= "now WAIT_FOR group2_visible"; +# Now transactions 1-4 visible. +SELECT * FROM t1 ORDER BY a; +SET DEBUG_SYNC= "now SIGNAL group2_checked"; + +connection con2; +reap; + +connection con3; +reap; + +connection con4; +reap; + +connection con5; +reap; + +connection con6; +reap; + +connection default; +# Check all transactions finally visible. +SELECT * FROM t1 ORDER BY a; + +SELECT variable_value - @commits FROM information_schema.global_status + WHERE variable_name = 'binlog_commits'; +SELECT variable_value - @group_commits FROM information_schema.global_status + WHERE variable_name = 'binlog_group_commits'; + +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/sql/handler.cc b/sql/handler.cc index b817673ed23..0bce67596fa 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -76,6 +76,8 @@ TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"", static TYPELIB known_extensions= {0,"known_exts", NULL, NULL}; uint known_extensions_id= 0; +static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, + bool is_real_trans); static plugin_ref ha_default_plugin(THD *thd) @@ -1070,7 +1072,7 @@ ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list, */ int ha_commit_trans(THD *thd, bool all) { - int error= 0, cookie= 0; + int error= 0, cookie; /* 'all' means that this is either an explicit commit issued by user, or an implicit commit issued by a DDL. @@ -1085,7 +1087,8 @@ int ha_commit_trans(THD *thd, bool all) */ bool is_real_trans= all || thd->transaction.all.ha_list == 0; Ha_trx_info *ha_info= trans->ha_list; - my_xid xid= thd->transaction.xid_state.xid.get_my_xid(); + bool need_prepare_ordered, need_commit_ordered; + my_xid xid; DBUG_ENTER("ha_commit_trans"); /* @@ -1118,85 +1121,112 @@ int ha_commit_trans(THD *thd, bool all) DBUG_RETURN(2); } #ifdef USING_TRANSACTIONS - if (ha_info) + if (!ha_info) { - uint rw_ha_count; - bool rw_trans; - - DBUG_EXECUTE_IF("crash_commit_before", abort();); - - /* Close all cursors that can not survive COMMIT */ - if (is_real_trans) /* not a statement commit */ - thd->stmt_map.close_transient_cursors(); - - rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all); - /* rw_trans is TRUE when we in a transaction changing data */ - rw_trans= is_real_trans && (rw_ha_count > 0); - - if (rw_trans && - wait_if_global_read_lock(thd, 0, 0)) - { - ha_rollback_trans(thd, all); - DBUG_RETURN(1); - } - - if (rw_trans && - opt_readonly && - !(thd->security_ctx->master_access & SUPER_ACL) && - !thd->slave_thread) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); - ha_rollback_trans(thd, all); - error= 1; - goto end; - } - - if (!trans->no_2pc && (rw_ha_count > 1)) - { - for (; ha_info && !error; ha_info= ha_info->next()) - { - int err; - handlerton *ht= ha_info->ht(); - /* - Do not call two-phase commit if this particular - transaction is read-only. This allows for simpler - implementation in engines that are always read-only. - */ - if (! ha_info->is_trx_read_write()) - continue; - /* - Sic: we know that prepare() is not NULL since otherwise - trans->no_2pc would have been set. - */ - if ((err= ht->prepare(ht, thd, all))) - { - my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); - error= 1; - } - status_var_increment(thd->status_var.ha_prepare_count); - } - DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_ABORT();); - if (error || (is_real_trans && xid && - (error= !(cookie= tc_log->log_xid(thd, xid))))) - { - ha_rollback_trans(thd, all); - error= 1; - goto end; - } - DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_ABORT();); - } - error=ha_commit_one_phase(thd, all) ? (cookie ? 2 : 1) : 0; - DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_ABORT();); - if (cookie) - tc_log->unlog(cookie, xid); - DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); -end: - if (rw_trans) - start_waiting_global_read_lock(thd); + /* Free resources and perform other cleanup even for 'empty' transactions. */ + if (is_real_trans) + thd->transaction.cleanup(); + DBUG_RETURN(0); } - /* Free resources and perform other cleanup even for 'empty' transactions. */ - else if (is_real_trans) - thd->transaction.cleanup(); + + DBUG_EXECUTE_IF("crash_commit_before", abort();); + + /* Close all cursors that can not survive COMMIT */ + if (is_real_trans) /* not a statement commit */ + thd->stmt_map.close_transient_cursors(); + + uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all); + /* rw_trans is TRUE when we in a transaction changing data */ + bool rw_trans= is_real_trans && (rw_ha_count > 0); + + if (rw_trans && + wait_if_global_read_lock(thd, 0, 0)) + { + ha_rollback_trans(thd, all); + DBUG_RETURN(1); + } + + if (rw_trans && + opt_readonly && + !(thd->security_ctx->master_access & SUPER_ACL) && + !thd->slave_thread) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); + goto err; + } + + if (trans->no_2pc || (rw_ha_count <= 1)) + { + error= ha_commit_one_phase(thd, all); + DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); + goto end; + } + + need_prepare_ordered= FALSE; + need_commit_ordered= FALSE; + xid= thd->transaction.xid_state.xid.get_my_xid(); + + for (Ha_trx_info *hi= ha_info; hi; hi= hi->next()) + { + int err; + handlerton *ht= hi->ht(); + /* + Do not call two-phase commit if this particular + transaction is read-only. This allows for simpler + implementation in engines that are always read-only. + */ + if (! hi->is_trx_read_write()) + continue; + /* + Sic: we know that prepare() is not NULL since otherwise + trans->no_2pc would have been set. + */ + if ((err= ht->prepare(ht, thd, all))) + my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); + status_var_increment(thd->status_var.ha_prepare_count); + + if (err) + goto err; + + if (ht->prepare_ordered) + need_prepare_ordered= TRUE; + if (ht->commit_ordered) + need_commit_ordered= TRUE; + } + DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_ABORT();); + + if (!is_real_trans) + { + error= commit_one_phase_2(thd, all, trans, is_real_trans); + DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); + goto end; + } + + cookie= tc_log->log_and_order(thd, xid, all, need_prepare_ordered, + need_commit_ordered); + if (!cookie) + goto err; + + DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_ABORT();); + + error= commit_one_phase_2(thd, all, trans, is_real_trans) ? 2 : 0; + DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); + + DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_ABORT();); + tc_log->unlog(cookie, xid); + + DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); + goto end; + + /* Come here if error and we need to rollback. */ +err: + if (!error) + error= 1; + ha_rollback_trans(thd, all); + +end: + if (rw_trans) + start_waiting_global_read_lock(thd); #endif /* USING_TRANSACTIONS */ DBUG_RETURN(error); } @@ -1207,7 +1237,6 @@ end: */ int ha_commit_one_phase(THD *thd, bool all) { - int error=0; THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt; /* "real" is a nick name for a transaction for which a commit will @@ -1217,8 +1246,41 @@ int ha_commit_one_phase(THD *thd, bool all) enclosing 'all' transaction is rolled back. */ bool is_real_trans=all || thd->transaction.all.ha_list == 0; - Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; + Ha_trx_info *ha_info= trans->ha_list; DBUG_ENTER("ha_commit_one_phase"); +#ifdef USING_TRANSACTIONS + if (ha_info) + { + if (is_real_trans) + { + bool locked= false; + for (; ha_info; ha_info= ha_info->next()) + { + handlerton *ht= ha_info->ht(); + if (ht->commit_ordered) + { + if (ha_info->is_trx_read_write() && !locked) + { + pthread_mutex_lock(&LOCK_commit_ordered); + locked= 1; + } + ht->commit_ordered(ht, thd, all); + } + } + if (locked) + pthread_mutex_unlock(&LOCK_commit_ordered); + } + } +#endif /* USING_TRANSACTIONS */ + DBUG_RETURN(commit_one_phase_2(thd, all, trans, is_real_trans)); +} + +static int +commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) +{ + int error= 0; + Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; + DBUG_ENTER("commit_one_phase_2"); #ifdef USING_TRANSACTIONS if (ha_info) { diff --git a/sql/handler.h b/sql/handler.h index d03264a23db..17dcc294099 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -656,9 +656,96 @@ struct handlerton NOTE 'all' is also false in auto-commit mode where 'end of statement' and 'real commit' mean the same event. */ - int (*commit)(handlerton *hton, THD *thd, bool all); + int (*commit)(handlerton *hton, THD *thd, bool all); + /* + The commit_ordered() method is called prior to the commit() method, after + the transaction manager has decided to commit (not rollback) the + transaction. Unlike commit(), commit_ordered() is called only when the + full transaction is committed, not for each commit of statement + transaction in a multi-statement transaction. + + The calls to commit_ordered() in multiple parallel transactions is + guaranteed to happen in the same order in every participating + handler. This can be used to ensure the same commit order among multiple + handlers (eg. in table handler and binlog). So if transaction T1 calls + into commit_ordered() of handler A before T2, then T1 will also call + commit_ordered() of handler B before T2. + + Engines that implement this method should during this call make the + transaction visible to other transactions, thereby making the order of + transaction commits be defined by the order of commit_ordered() calls. + + The intension is that commit_ordered() should do the minimal amount of + work that needs to happen in consistent commit order among handlers. To + preserve ordering, calls need to be serialised on a global mutex, so + doing any time-consuming or blocking operations in commit_ordered() will + limit scalability. + + Handlers can rely on commit_ordered() calls for transactions that updated + data to be serialised (no two calls can run in parallel, so no extra + locking on the handler part is required to ensure this). However, calls + for SELECT-only transactions are not serialised, so can occur in parallel + with each other and with at most one write-transaction. + + Note that commit_ordered() can be called from a different thread than the + one handling the transaction! So it can not do anything that depends on + thread local storage, in particular it can not call my_error() and + friends (instead it can store the error code and delay the call of + my_error() to the commit() method). + + Similarly, since commit_ordered() returns void, any return error code + must be saved and returned from the commit() method instead. + + The commit_ordered method is optional, and can be left unset if not + needed in a particular handler. + */ + void (*commit_ordered)(handlerton *hton, THD *thd, bool all); int (*rollback)(handlerton *hton, THD *thd, bool all); int (*prepare)(handlerton *hton, THD *thd, bool all); + /* + The prepare_ordered method is optional. If set, it will be called after + successful prepare() in all handlers participating in 2-phase + commit. Like commit_ordered(), it is called only when the full + transaction is committed, not for each commit of statement transaction. + + The calls to prepare_ordered() among multiple parallel transactions are + ordered consistently with calls to commit_ordered(). This means that + calls to prepare_ordered() effectively define the commit order, and that + each handler will see the same sequence of transactions calling into + prepare_ordered() and commit_ordered(). + + Thus, prepare_ordered() can be used to define commit order for handlers + that need to do this in the prepare step (like binlog). It can also be + used to release transaction's locks early in an order consistent with the + order transactions will be eventually committed. + + Like commit_ordered(), prepare_ordered() calls are serialised to maintain + ordering, so the intension is that they should execute fast, with only + the minimal amount of work needed to define commit order. Handlers can + rely on this serialisation, and do not need to do any extra locking to + avoid two prepare_ordered() calls running in parallel. + + Like commit_ordered(), prepare_ordered() is not guaranteed to be called + in the context of the thread handling the rest of the transaction. So it + cannot invoke code that relies on thread local storage, in particular it + cannot call my_error(). + + When prepare_ordered() is called, the transaction coordinator has already + decided to commit (not rollback) the transaction. So prepare_ordered() + cannot cause a rollback by returning an error, all possible errors must + be handled in prepare() (the prepare_ordered() method returns void). In + case of some fatal error, a record of the error must be made internally + by the engine and returned from commit() later. + + Note that for user-level XA SQL commands, no consistent ordering among + prepare_ordered() and commit_ordered() is guaranteed (as that would + require blocking all other commits for an indefinite time). + + When 2-phase commit is not used (eg. only one engine (and no binlog) in + transaction), prepare() is not called and in such cases prepare_ordered() + also is not called. + */ + void (*prepare_ordered)(handlerton *hton, THD *thd, bool all); int (*recover)(handlerton *hton, XID *xid_list, uint len); int (*commit_by_xid)(handlerton *hton, XID *xid); int (*rollback_by_xid)(handlerton *hton, XID *xid); diff --git a/sql/log.cc b/sql/log.cc index f52e68dd1b9..8440a835158 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -38,6 +38,7 @@ #endif #include +#include "debug_sync.h" /* max size of the log message */ #define MAX_LOG_BUFFER_SIZE 1024 @@ -154,9 +155,12 @@ class binlog_trx_data { public: binlog_trx_data() : at_least_one_stmt_committed(0), incident(FALSE), m_pending(0), - before_stmt_pos(MY_OFF_T_UNDEF) + before_stmt_pos(MY_OFF_T_UNDEF), using_xa(0) { trans_log.end_of_file= max_binlog_cache_size; + (void) my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, + "LOCK_group_commit", MYF(0)); + (void) pthread_cond_init(&COND_group_commit, 0); } ~binlog_trx_data() @@ -208,11 +212,12 @@ public: completely. */ void reset() { - if (!empty()) + if (trans_log.type != WRITE_CACHE || !empty()) truncate(0); before_stmt_pos= MY_OFF_T_UNDEF; incident= FALSE; trans_log.end_of_file= max_binlog_cache_size; + using_xa= FALSE; DBUG_ASSERT(empty()); } @@ -257,6 +262,41 @@ public: Binlog position before the start of the current statement. */ my_off_t before_stmt_pos; + + /* 0 or error when writing to binlog; set during group commit. */ + int error; + /* If error != 0, value of errno (for my_error() reporting). */ + int commit_errno; + /* Link for queueing transactions up for group commit to binlog. */ + binlog_trx_data *next; + /* + Flag set true when group commit for this transaction is finished; used + with pthread_cond_wait() to wait until commit is done. + This flag is protected by LOCK_group_commit. + */ + bool done; + /* + Flag set if this transaction is the group commit leader that will handle + the actual writing to the binlog. + This flag is protected by LOCK_group_commit. + */ + bool group_commit_leader; + /* + Flag set true if this transaction is committed with log_xid() as part of + XA, false if not. + */ + bool using_xa; + /* + Extra events (BEGIN, COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be + written during group commit. The incident_event is only valid if + has_incident() is true. + */ + Log_event *begin_event; + Log_event *end_event; + Log_event *incident_event; + /* Mutex and condition for wakeup after group commit. */ + pthread_mutex_t LOCK_group_commit; + pthread_cond_t COND_group_commit; }; handlerton *binlog_hton; @@ -1391,117 +1431,188 @@ static int binlog_close_connection(handlerton *hton, THD *thd) return 0; } +/* Helper functions for binlog_flush_trx_cache(). */ +static int +binlog_flush_trx_cache_prepare(THD *thd) +{ + if (thd->binlog_flush_pending_rows_event(TRUE)) + return 1; + return 0; +} + +static void +binlog_flush_trx_cache_finish(THD *thd, binlog_trx_data *trx_data) +{ + IO_CACHE *trans_log= &trx_data->trans_log; + + trx_data->reset(); + + statistic_increment(binlog_cache_use, &LOCK_status); + if (trans_log->disk_writes != 0) + { + statistic_increment(binlog_cache_disk_use, &LOCK_status); + trans_log->disk_writes= 0; + } +} + /* - End a transaction. + End a transaction, writing events to the binary log. SYNOPSIS - binlog_end_trans() + binlog_flush_trx_cache() thd The thread whose transaction should be ended trx_data Pointer to the transaction data to use - end_ev The end event to use, or NULL - all True if the entire transaction should be ended, false if - only the statement transaction should be ended. + end_ev The end event to use (COMMIT, ROLLBACK, or commit XID) DESCRIPTION End the currently open transaction. The transaction can be either - a real transaction (if 'all' is true) or a statement transaction - (if 'all' is false). + a real transaction or a statement transaction. - If 'end_ev' is NULL, the transaction is a rollback of only - transactional tables, so the transaction cache will be truncated - to either just before the last opened statement transaction (if - 'all' is false), or reset completely (if 'all' is true). + This can be to commit a transaction, with a COMMIT query event or an XA + commit XID event. But it can also be to rollback a transaction with a + ROLLBACK query event, used for rolling back transactions which also + contain updates to non-transactional tables. */ static int -binlog_end_trans(THD *thd, binlog_trx_data *trx_data, - Log_event *end_ev, bool all) +binlog_flush_trx_cache(THD *thd, binlog_trx_data *trx_data, + Log_event *end_ev) { - DBUG_ENTER("binlog_end_trans"); - int error=0; - IO_CACHE *trans_log= &trx_data->trans_log; - DBUG_PRINT("enter", ("transaction: %s end_ev: 0x%lx", - all ? "all" : "stmt", (long) end_ev)); + DBUG_ENTER("binlog_flush_trx_cache"); DBUG_PRINT("info", ("thd->options={ %s%s}", FLAGSTR(thd->options, OPTION_NOT_AUTOCOMMIT), FLAGSTR(thd->options, OPTION_BEGIN))); + if (binlog_flush_trx_cache_prepare(thd)) + DBUG_RETURN(1); + /* - NULL denotes ROLLBACK with nothing to replicate: i.e., rollback of - only transactional tables. If the transaction contain changes to - any non-transactiona tables, we need write the transaction and log - a ROLLBACK last. - */ - if (end_ev != NULL) - { - if (thd->binlog_flush_pending_rows_event(TRUE)) - DBUG_RETURN(1); - /* - Doing a commit or a rollback including non-transactional tables, - i.e., ending a transaction where we might write the transaction - cache to the binary log. + Doing a commit or a rollback including non-transactional tables, + i.e., ending a transaction where we might write the transaction + cache to the binary log. - We can always end the statement when ending a transaction since - transactions are not allowed inside stored functions. If they - were, we would have to ensure that we're not ending a statement - inside a stored function. - */ - error= mysql_bin_log.write(thd, &trx_data->trans_log, end_ev, - trx_data->has_incident()); - trx_data->reset(); + We can always end the statement when ending a transaction since + transactions are not allowed inside stored functions. If they + were, we would have to ensure that we're not ending a statement + inside a stored function. + */ + int error= mysql_bin_log.write_transaction_to_binlog(thd, trx_data, end_ev); - /* - We need to step the table map version after writing the - transaction cache to disk. - */ - mysql_bin_log.update_table_map_version(); - statistic_increment(binlog_cache_use, &LOCK_status); - if (trans_log->disk_writes != 0) - { - statistic_increment(binlog_cache_disk_use, &LOCK_status); - trans_log->disk_writes= 0; - } - } - else - { - /* - If rolling back an entire transaction or a single statement not - inside a transaction, we reset the transaction cache. - - If rolling back a statement in a transaction, we truncate the - transaction cache to remove the statement. - */ - thd->binlog_remove_pending_rows_event(TRUE); - if (all || !(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) - { - if (trx_data->has_incident()) - error= mysql_bin_log.write_incident(thd, TRUE); - trx_data->reset(); - } - else // ...statement - trx_data->truncate(trx_data->before_stmt_pos); - - /* - We need to step the table map version on a rollback to ensure - that a new table map event is generated instead of the one that - was written to the thrown-away transaction cache. - */ - mysql_bin_log.update_table_map_version(); - } + binlog_flush_trx_cache_finish(thd, trx_data); DBUG_ASSERT(thd->binlog_get_pending_rows_event() == NULL); DBUG_RETURN(error); } +/* + Discard a transaction, ie. ROLLBACK with only transactional table updates. + + SYNOPSIS + binlog_truncate_trx_cache() + + thd The thread whose transaction should be ended + trx_data Pointer to the transaction data to use + all True if the entire transaction should be ended, false if + only the statement transaction should be ended. + + DESCRIPTION + + Rollback (and end) a transaction that only modifies transactional + tables. The transaction can be either a real transaction (if 'all' is + true) or a statement transaction (if 'all' is false). + + The transaction cache will be truncated to either just before the last + opened statement transaction (if 'all' is false), or reset completely (if + 'all' is true). + */ +static int +binlog_truncate_trx_cache(THD *thd, binlog_trx_data *trx_data, bool all) +{ + DBUG_ENTER("binlog_truncate_trx_cache"); + int error= 0; + DBUG_PRINT("enter", ("transaction: %s", all ? "all" : "stmt")); + DBUG_PRINT("info", ("thd->options={ %s%s}", + FLAGSTR(thd->options, OPTION_NOT_AUTOCOMMIT), + FLAGSTR(thd->options, OPTION_BEGIN))); + + /* + ROLLBACK with nothing to replicate: i.e., rollback of only transactional + tables. + */ + + /* + If rolling back an entire transaction or a single statement not + inside a transaction, we reset the transaction cache. + + If rolling back a statement in a transaction, we truncate the + transaction cache to remove the statement. + */ + thd->binlog_remove_pending_rows_event(TRUE); + if (all || !(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) + { + if (trx_data->has_incident()) + error= mysql_bin_log.write_incident(thd); + trx_data->reset(); + } + else // ...statement + trx_data->truncate(trx_data->before_stmt_pos); + + DBUG_ASSERT(thd->binlog_get_pending_rows_event() == NULL); + DBUG_RETURN(error); +} + +static LEX_STRING const write_error_msg= + { C_STRING_WITH_LEN("error writing to the binary log") }; + static int binlog_prepare(handlerton *hton, THD *thd, bool all) { /* - do nothing. - just pretend we can do 2pc, so that MySQL won't - switch to 1pc. - real work will be done in MYSQL_BIN_LOG::log_xid() + If this prepare is for a single statement in the middle of a transactions, + not the actual transaction commit, then we do nothing. The real work is + only done later, in the prepare for making persistent changes. */ + if (!all && (thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) + return 0; + + binlog_trx_data *trx_data= + (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + + trx_data->using_xa= TRUE; + + if (binlog_flush_trx_cache_prepare(thd)) + return 1; + + my_xid xid= thd->transaction.xid_state.xid.get_my_xid(); + if (!xid) + { + /* Skip logging this transaction, marked by setting end_event to NULL. */ + trx_data->end_event= NULL; + return 0; + } + + /* + Allocate the extra events that will be logged to the binlog in binlog group + commit. Use placement new to allocate them on the THD memroot, as they need + to remain live until log_xid() returns. + */ + size_t needed_size= sizeof(Query_log_event) + sizeof(Xid_log_event); + if (trx_data->has_incident()) + needed_size+= sizeof(Incident_log_event); + uchar *mem= (uchar *)thd->alloc(needed_size); + if (!mem) + return 1; + + trx_data->begin_event= new ((void *)mem) + Query_log_event(thd, STRING_WITH_LEN("BEGIN"), TRUE, TRUE, 0); + mem+= sizeof(Query_log_event); + + trx_data->end_event= new ((void *)mem) Xid_log_event(thd, xid); + + if (trx_data->has_incident()) + trx_data->incident_event= new ((void *)(mem + sizeof(Xid_log_event))) + Incident_log_event(thd, INCIDENT_LOST_EVENTS, write_error_msg); + return 0; } @@ -1525,11 +1636,11 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) binlog_trx_data *const trx_data= (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); - if (trx_data->empty()) + if (trx_data->using_xa) { // we're here because trans_log was flushed in MYSQL_BIN_LOG::log_xid() - trx_data->reset(); - DBUG_RETURN(0); + binlog_flush_trx_cache_finish(thd, trx_data); + DBUG_RETURN(error); } /* @@ -1556,8 +1667,8 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) !stmt_has_updated_trans_table(thd) && thd->transaction.stmt.modified_non_trans_table)) { - Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, 0); - error= binlog_end_trans(thd, trx_data, &qev, all); + Query_log_event end_ev(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, 0); + error= binlog_flush_trx_cache(thd, trx_data, &end_ev); } trx_data->at_least_one_stmt_committed = my_b_tell(&trx_data->trans_log) > 0; @@ -1621,7 +1732,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) (thd->options & OPTION_KEEP_LOG)) && mysql_bin_log.check_write_error(thd)) trx_data->set_incident(); - error= binlog_end_trans(thd, trx_data, 0, all); + error= binlog_truncate_trx_cache(thd, trx_data, all); } else { @@ -1641,8 +1752,8 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) thd->current_stmt_binlog_row_based) || ((thd->options & OPTION_KEEP_LOG))) { - Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, TRUE, 0); - error= binlog_end_trans(thd, trx_data, &qev, all); + Query_log_event end_ev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, TRUE, 0); + error= binlog_flush_trx_cache(thd, trx_data, &end_ev); } /* Otherwise, we simply truncate the cache as there is no change on @@ -1650,7 +1761,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) */ else if ((all && !thd->transaction.all.modified_non_trans_table) || (!all && !thd->transaction.stmt.modified_non_trans_table)) - error= binlog_end_trans(thd, trx_data, 0, all); + error= binlog_truncate_trx_cache(thd, trx_data, all); } if (!all) trx_data->before_stmt_pos = MY_OFF_T_UNDEF; // part of the stmt rollback @@ -2464,7 +2575,7 @@ const char *MYSQL_LOG::generate_name(const char *log_name, MYSQL_BIN_LOG::MYSQL_BIN_LOG() :bytes_written(0), prepared_xids(0), file_id(1), open_count(1), - need_start_event(TRUE), m_table_map_version(0), + need_start_event(TRUE), is_relay_log(0), description_event_for_exec(0), description_event_for_queue(0) { @@ -2492,6 +2603,7 @@ void MYSQL_BIN_LOG::cleanup() delete description_event_for_exec; (void) pthread_mutex_destroy(&LOCK_log); (void) pthread_mutex_destroy(&LOCK_index); + (void) pthread_mutex_destroy(&LOCK_queue); (void) pthread_cond_destroy(&update_cond); } DBUG_VOID_RETURN; @@ -2520,6 +2632,8 @@ void MYSQL_BIN_LOG::init_pthread_objects() */ (void) my_pthread_mutex_init(&LOCK_index, MY_MUTEX_INIT_SLOW, "LOCK_index", MYF_NO_DEADLOCK_DETECTION); + (void) my_pthread_mutex_init(&LOCK_queue, MY_MUTEX_INIT_FAST, "LOCK_queue", + MYF(0)); (void) pthread_cond_init(&update_cond, 0); } @@ -3943,6 +4057,10 @@ err: } +#ifndef DBUG_OFF +static ulong opt_binlog_dbug_fsync_sleep= 0; +#endif + bool MYSQL_BIN_LOG::flush_and_sync() { int err=0, fd=log_file.file; @@ -3953,6 +4071,11 @@ bool MYSQL_BIN_LOG::flush_and_sync() { sync_binlog_counter= 0; err=my_sync(fd, MYF(MY_WME)); +#ifndef DBUG_OFF + ulong usec_sleep= opt_binlog_dbug_fsync_sleep; + if (usec_sleep > 0) + my_sleep(usec_sleep); +#endif } return err; } @@ -4113,7 +4236,6 @@ int THD::binlog_write_table_map(TABLE *table, bool is_trans) DBUG_RETURN(error); binlog_table_maps++; - table->s->table_map_version= mysql_bin_log.table_map_version(); DBUG_RETURN(0); } @@ -4194,64 +4316,41 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, if (Rows_log_event* pending= trx_data->pending()) { - IO_CACHE *file= &log_file; - /* Decide if we should write to the log file directly or to the transaction log. */ if (pending->get_cache_stmt() || my_b_tell(&trx_data->trans_log)) - file= &trx_data->trans_log; - - /* - If we are writing to the log file directly, we could avoid - locking the log. This does not work since we need to step the - m_table_map_version below, and that change has to be protected - by the LOCK_log mutex. - */ - pthread_mutex_lock(&LOCK_log); - - /* - Write pending event to log file or transaction cache - */ - if (pending->write(file)) { - pthread_mutex_unlock(&LOCK_log); - set_write_error(thd); - DBUG_RETURN(1); + /* Write to transaction log/cache. */ + if (pending->write(&trx_data->trans_log)) + { + set_write_error(thd); + DBUG_RETURN(1); + } } - - /* - We step the table map version if we are writing an event - representing the end of a statement. We do this regardless of - wheather we write to the transaction cache or to directly to the - file. - - In an ideal world, we could avoid stepping the table map version - if we were writing to a transaction cache, since we could then - reuse the table map that was written earlier in the transaction - cache. This does not work since STMT_END_F implies closing all - table mappings on the slave side. - - TODO: Find a solution so that table maps does not have to be - written several times within a transaction. - */ - if (pending->get_flags(Rows_log_event::STMT_END_F)) - ++m_table_map_version; - - delete pending; - - if (file == &log_file) + else { + /* Write directly to log file. */ + pthread_mutex_lock(&LOCK_log); + if (pending->write(&log_file)) + { + pthread_mutex_unlock(&LOCK_log); + set_write_error(thd); + DBUG_RETURN(1); + } + error= flush_and_sync(); if (!error) { signal_update(); rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + + pthread_mutex_unlock(&LOCK_log); } - pthread_mutex_unlock(&LOCK_log); + delete pending; } thd->binlog_set_pending_rows_event(event); @@ -4450,9 +4549,6 @@ err: set_write_error(thd); } - if (event_info->flags & LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F) - ++m_table_map_version; - pthread_mutex_unlock(&LOCK_log); DBUG_RETURN(error); } @@ -4575,18 +4671,14 @@ uint MYSQL_BIN_LOG::next_file_id() SYNOPSIS write_cache() cache Cache to write to the binary log - lock_log True if the LOCK_log mutex should be aquired, false otherwise - sync_log True if the log should be flushed and sync:ed DESCRIPTION Write the contents of the cache to the binary log. The cache will be reset as a READ_CACHE to be able to read the contents from it. */ -int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache, bool lock_log, bool sync_log) +int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache) { - Mutex_sentry sentry(lock_log ? &LOCK_log : NULL); - if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) return ER_ERROR_ON_WRITE; uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs; @@ -4697,6 +4789,7 @@ int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache, bool lock_log, bool sync_log) } /* Write data to the binary log file */ + DBUG_EXECUTE_IF("fail_binlog_write_1", return ER_ERROR_ON_WRITE;); if (my_b_write(&log_file, cache->read_pos, length)) return ER_ERROR_ON_WRITE; cache->read_pos=cache->read_end; // Mark buffer used up @@ -4704,9 +4797,6 @@ int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache, bool lock_log, bool sync_log) DBUG_ASSERT(carry == 0); - if (sync_log) - flush_and_sync(); - return 0; // All OK } @@ -4739,26 +4829,22 @@ int query_error_code(THD *thd, bool not_killed) return error; } -bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock) +bool MYSQL_BIN_LOG::write_incident(THD *thd) { uint error= 0; DBUG_ENTER("MYSQL_BIN_LOG::write_incident"); - LEX_STRING const write_error_msg= - { C_STRING_WITH_LEN("error writing to the binary log") }; Incident incident= INCIDENT_LOST_EVENTS; Incident_log_event ev(thd, incident, write_error_msg); - if (lock) - pthread_mutex_lock(&LOCK_log); + + pthread_mutex_lock(&LOCK_log); error= ev.write(&log_file); - if (lock) + if (!error && !(error= flush_and_sync())) { - if (!error && !(error= flush_and_sync())) - { - signal_update(); - rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); - } - pthread_mutex_unlock(&LOCK_log); + signal_update(); + rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + pthread_mutex_unlock(&LOCK_log); + DBUG_RETURN(error); } @@ -4786,103 +4872,366 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock) 'cache' needs to be reinitialized after this functions returns. */ -bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event, - bool incident) +bool +MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, + Log_event *end_ev) { - DBUG_ENTER("MYSQL_BIN_LOG::write(THD *, IO_CACHE *, Log_event *)"); + DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_to_binlog"); + + /* + Create the necessary events here, where we have the correct THD (and + thread context). + + Due to group commit the actual writing to binlog may happen in a different + thread. + */ + Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), TRUE, TRUE, 0); + trx_data->begin_event= &qinfo; + trx_data->end_event= end_ev; + if (trx_data->has_incident()) + { + Incident_log_event inc_ev(thd, INCIDENT_LOST_EVENTS, write_error_msg); + trx_data->incident_event= &inc_ev; + DBUG_RETURN(write_transaction_to_binlog_events(trx_data)); + } + else + { + trx_data->incident_event= NULL; + DBUG_RETURN(write_transaction_to_binlog_events(trx_data)); + } +} + +bool +MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) +{ + /* + To facilitate group commit for the binlog, we first queue up ourselves in + the group commit queue. Then the first thread to enter the queue waits for + the LOCK_log mutex, and commits for everyone in the queue once it gets the + lock. Any other threads in the queue just wait for the first one to finish + the commit and wake them up. + */ + + pthread_mutex_lock(&trx_data->LOCK_group_commit); + const binlog_trx_data *orig_queue= atomic_enqueue_trx(trx_data); + + if (orig_queue != NULL) + { + trx_data->group_commit_leader= FALSE; + trx_data->done= FALSE; + trx_group_commit_participant(trx_data); + } + else + { + trx_data->group_commit_leader= TRUE; + pthread_mutex_unlock(&trx_data->LOCK_group_commit); + trx_group_commit_leader(NULL); + } + + return trx_group_commit_finish(trx_data); +} + +/* + Participate as secondary transaction in group commit. + + Another thread is already waiting to obtain the LOCK_log, and should include + this thread in the group commit once the log is obtained. So here we put + ourself in the queue and wait to be signalled that the group commit is done. + + Note that this function must be called with the trs_data->LOCK_group_commit + locked; the mutex will be released before return. +*/ +void +MYSQL_BIN_LOG::trx_group_commit_participant(binlog_trx_data *trx_data) +{ + safe_mutex_assert_owner(&trx_data->LOCK_group_commit); + + /* Wait until trx_data.done == true and woken up by the leader. */ + while (!trx_data->done) + pthread_cond_wait(&trx_data->COND_group_commit, + &trx_data->LOCK_group_commit); + pthread_mutex_unlock(&trx_data->LOCK_group_commit); +} + +bool +MYSQL_BIN_LOG::trx_group_commit_finish(binlog_trx_data *trx_data) +{ + DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_finish"); + DBUG_PRINT("info", ("trx_data->error=%d\n", trx_data->error)); + if (trx_data->error) + { + switch (trx_data->error) + { + case ER_ERROR_ON_WRITE: + my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, trx_data->commit_errno); + break; + case ER_ERROR_ON_READ: + my_error(ER_ERROR_ON_READ, MYF(ME_NOREFRESH), + trx_data->trans_log.file_name, trx_data->commit_errno); + break; + default: + /* + There are not (and should not be) any errors thrown not covered above. + But just in case one is added later without updating the above switch + statement, include a catch-all. + */ + my_printf_error(trx_data->error, + "Error writing transaction to binary log: %d", + MYF(ME_NOREFRESH), trx_data->error); + } + + /* + Since we return error, this transaction XID will not be committed, so + we need to mark it as not needed for recovery (unlog() is not called + for a transaction if log_xid() fails). + */ + if (trx_data->end_event->get_type_code() == XID_EVENT) + mark_xid_done(); + + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + +/* + Do binlog group commit as the lead thread. + + This must be called when this thread/transaction is queued at the start of + the group_commit_queue. It will wait to obtain the LOCK_log mutex, then group + commit all the transactions in the queue (more may have entered while waiting + for LOCK_log). After commit is done, all other threads in the queue will be + signalled. + + */ +void +MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) +{ + uint xid_count= 0; + uint write_count= 0; + + /* First, put anything from group_log_xid into the queue. */ + binlog_trx_data *full_queue= NULL; + binlog_trx_data **next_ptr= &full_queue; + for (TC_group_commit_entry *entry= first; entry; entry= entry->next) + { + binlog_trx_data *const trx_data= + (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); + + /* Skip log_xid for transactions without xid, marked by NULL end_event. */ + if (!trx_data->end_event) + continue; + + trx_data->error= 0; + *next_ptr= trx_data; + next_ptr= &(trx_data->next); + } + + /* + Next, lock the LOCK_log(), and once we get it, add any additional writes + that queued up while we were waiting. + + Note that if some writer not going through log_xid() comes in and gets the + LOCK_log before us, they will not be able to include us in their group + commit (and they are not able to handle ensuring same commit order between + us and participating transactional storage engines anyway). + + On the other hand, when we get the LOCK_log, we will be able to include + any non-trasactional writes that queued up in our group commit. This + should hopefully not be too big of a problem, as group commit is most + important for the transactional case anyway when durability (fsync) is + enabled. + */ VOID(pthread_mutex_lock(&LOCK_log)); - /* NULL would represent nothing to replicate after ROLLBACK */ - DBUG_ASSERT(commit_event != NULL); + /* + As the queue is in reverse order of entering, reverse the queue as we add + it to the existing one. Note that there is no ordering defined between + transactional and non-transactional commits. + */ + binlog_trx_data *current= atomic_grab_trx_queue(); + binlog_trx_data *xtra_queue= NULL; + while (current) + { + current->error= 0; + binlog_trx_data *next= current->next; + current->next= xtra_queue; + xtra_queue= current; + current= next; + } + *next_ptr= xtra_queue; + /* + Now we have in full_queue the list of transactions to be committed in + order. + */ DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { /* - We only bother to write to the binary log if there is anything - to write. - */ - if (my_b_tell(cache) > 0) + Commit every transaction in the queue. + + Note that we are doing this in a different thread than the one running + the transaction! So we are limited in the operations we can do. In + particular, we cannot call my_error() on behalf of a transaction, as + that obtains the THD from thread local storage. Instead, we must set + current->error and let the thread do the error reporting itself once + we wake it up. + */ + for (current= full_queue; current != NULL; current= current->next) { - /* - Log "BEGIN" at the beginning of every transaction. Here, a - transaction is either a BEGIN..COMMIT block or a single - statement in autocommit mode. - */ - Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), TRUE, TRUE, 0); + IO_CACHE *cache= ¤t->trans_log; /* - Now this Query_log_event has artificial log_pos 0. It must be - adjusted to reflect the real position in the log. Not doing it - would confuse the slave: it would prevent this one from - knowing where he is in the master's binlog, which would result - in wrong positions being shown to the user, MASTER_POS_WAIT - undue waiting etc. + We only bother to write to the binary log if there is anything + to write. */ - if (qinfo.write(&log_file)) - goto err; - - DBUG_EXECUTE_IF("crash_before_writing_xid", - { - if ((write_error= write_cache(cache, false, true))) - DBUG_PRINT("info", ("error writing binlog cache: %d", - write_error)); - DBUG_PRINT("info", ("crashing before writing xid")); - abort(); - }); - - if ((write_error= write_cache(cache, false, false))) - goto err; - - if (commit_event && commit_event->write(&log_file)) - goto err; - - if (incident && write_incident(thd, FALSE)) - goto err; - - if (flush_and_sync()) - goto err; - DBUG_EXECUTE_IF("half_binlogged_transaction", DBUG_ABORT();); - if (cache->error) // Error on read + if (my_b_tell(cache) > 0) { - sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno); - write_error=1; // Don't give more errors - goto err; + current->error= write_transaction(current); + if (current->error) + current->commit_errno= errno; + + write_count++; + } + + if (current->end_event->get_type_code() == XID_EVENT) + xid_count++; + } + + if (write_count > 0) + { + if (flush_and_sync()) + { + for (current= full_queue; current != NULL; current= current->next) + { + if (!current->error) + { + current->error= ER_ERROR_ON_WRITE; + current->commit_errno= errno; + } + } + } + else + { + signal_update(); } - signal_update(); } /* - if commit_event is Xid_log_event, increase the number of + if any commit_events are Xid_log_event, increase the number of prepared_xids (it's decreasd in ::unlog()). Binlog cannot be rotated if there're prepared xids in it - see the comment in new_file() for an explanation. - If the commit_event is not Xid_log_event (then it's a Query_log_event) - rotate binlog, if necessary. + If no Xid_log_events (then it's all Query_log_event) rotate binlog, + if necessary. */ - if (commit_event && commit_event->get_type_code() == XID_EVENT) + if (xid_count > 0) { - pthread_mutex_lock(&LOCK_prep_xids); - prepared_xids++; - pthread_mutex_unlock(&LOCK_prep_xids); + mark_xids_active(xid_count); } else rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + VOID(pthread_mutex_unlock(&LOCK_log)); - DBUG_RETURN(0); + /* + Signal those that are not part of group_log_xid, and are not group leaders + running the queue. -err: - if (!write_error) + Since a group leader runs the queue itself if a group_log_xid does not get + to do it forst, such leader threads do not need wait or wakeup. + */ + for (current= xtra_queue; current != NULL; current= current->next) { - write_error= 1; - sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno); + /* + Note that we need to take LOCK_group_commit even in the case of a leader! + + Otherwise there is a race between setting and testing the + group_commit_leader flag. + */ + pthread_mutex_lock(¤t->LOCK_group_commit); + if (!current->group_commit_leader) + { + current->done= true; + pthread_cond_signal(¤t->COND_group_commit); + } + pthread_mutex_unlock(¤t->LOCK_group_commit); } - VOID(pthread_mutex_unlock(&LOCK_log)); - DBUG_RETURN(1); } +int +MYSQL_BIN_LOG::write_transaction(binlog_trx_data *trx_data) +{ + IO_CACHE *cache= &trx_data->trans_log; + /* + Log "BEGIN" at the beginning of every transaction. Here, a transaction is + either a BEGIN..COMMIT block or a single statement in autocommit mode. The + event was constructed in write_transaction_to_binlog(), in the thread + running the transaction. + + Now this Query_log_event has artificial log_pos 0. It must be + adjusted to reflect the real position in the log. Not doing it + would confuse the slave: it would prevent this one from + knowing where he is in the master's binlog, which would result + in wrong positions being shown to the user, MASTER_POS_WAIT + undue waiting etc. + */ + if (trx_data->begin_event->write(&log_file)) + return ER_ERROR_ON_WRITE; + + DBUG_EXECUTE_IF("crash_before_writing_xid", + { + if ((write_cache(cache))) + DBUG_PRINT("info", ("error writing binlog cache")); + else + flush_and_sync(); + + DBUG_PRINT("info", ("crashing before writing xid")); + abort(); + }); + + if (write_cache(cache)) + return ER_ERROR_ON_WRITE; + + if (trx_data->end_event->write(&log_file)) + return ER_ERROR_ON_WRITE; + + if (trx_data->has_incident() && trx_data->incident_event->write(&log_file)) + return ER_ERROR_ON_WRITE; + + if (cache->error) // Error on read + return ER_ERROR_ON_READ; + + return 0; +} + +binlog_trx_data * +MYSQL_BIN_LOG::atomic_enqueue_trx(binlog_trx_data *trx_data) +{ + my_atomic_rwlock_wrlock(&LOCK_queue); + trx_data->next= group_commit_queue; + while (!my_atomic_casptr((void **)(&group_commit_queue), + (void **)(&trx_data->next), + trx_data)) + ; + my_atomic_rwlock_wrunlock(&LOCK_queue); + return trx_data->next; +} + +binlog_trx_data * +MYSQL_BIN_LOG::atomic_grab_trx_queue() +{ + my_atomic_rwlock_wrlock(&LOCK_queue); + binlog_trx_data *queue= group_commit_queue; + while (!my_atomic_casptr((void **)(&group_commit_queue), + (void **)(&queue), + NULL)) + ; + my_atomic_rwlock_wrunlock(&LOCK_queue); + return queue; +} /** Wait until we get a signal that the binary log has been updated. @@ -5276,6 +5625,344 @@ void sql_print_information(const char *format, ...) } +static my_bool mutexes_inited; +pthread_mutex_t LOCK_prepare_ordered; +pthread_mutex_t LOCK_commit_ordered; + +void +TC_init() +{ + my_pthread_mutex_init(&LOCK_prepare_ordered, MY_MUTEX_INIT_SLOW, + "LOCK_prepare_ordered", MYF(0)); + my_pthread_mutex_init(&LOCK_commit_ordered, MY_MUTEX_INIT_SLOW, + "LOCK_commit_ordered", MYF(0)); + mutexes_inited= TRUE; +} + +void +TC_destroy() +{ + if (mutexes_inited) + { + pthread_mutex_destroy(&LOCK_prepare_ordered); + pthread_mutex_destroy(&LOCK_commit_ordered); + mutexes_inited= FALSE; + } +} + +void +TC_LOG::run_prepare_ordered(THD *thd, bool all) +{ + Ha_trx_info *ha_info= + all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list; + + for (; ha_info; ha_info= ha_info->next()) + { + handlerton *ht= ha_info->ht(); + if (!ht->prepare_ordered) + continue; + safe_mutex_assert_owner(&LOCK_prepare_ordered); + ht->prepare_ordered(ht, thd, all); + } +} + +void +TC_LOG::run_commit_ordered(THD *thd, bool all) +{ + Ha_trx_info *ha_info= + all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list; + + for (; ha_info; ha_info= ha_info->next()) + { + handlerton *ht= ha_info->ht(); + if (!ht->commit_ordered) + continue; + safe_mutex_assert_owner(&LOCK_commit_ordered); + ht->commit_ordered(ht, thd, all); + DEBUG_SYNC(thd, "commit_after_run_commit_ordered"); + } +} + +TC_LOG_queued::TC_LOG_queued() : group_commit_queue(NULL) +{ +} + +TC_LOG_queued::~TC_LOG_queued() +{ +} + +TC_LOG_queued::TC_group_commit_entry * +TC_LOG_queued::reverse_queue(TC_LOG_queued::TC_group_commit_entry *queue) +{ + TC_group_commit_entry *entry= queue; + TC_group_commit_entry *prev= NULL; + while (entry) + { + TC_group_commit_entry *next= entry->next; + entry->next= prev; + prev= entry; + entry= next; + } + + return prev; +} + +void +TC_LOG_queued::group_commit_wait_for_wakeup(TC_group_commit_entry *entry) +{ + THD *thd= entry->thd; + pthread_mutex_lock(&thd->LOCK_commit_ordered); + while (!entry->group_commit_ready) + pthread_cond_wait(&thd->COND_commit_ordered, + &thd->LOCK_commit_ordered); + pthread_mutex_unlock(&thd->LOCK_commit_ordered); +} + +void +TC_LOG_queued::group_commit_wakeup_other(TC_group_commit_entry *other) +{ + THD *thd= other->thd; + pthread_mutex_lock(&thd->LOCK_commit_ordered); + other->group_commit_ready= TRUE; + pthread_cond_signal(&thd->COND_commit_ordered); + pthread_mutex_unlock(&thd->LOCK_commit_ordered); +} + +TC_LOG_unordered::TC_LOG_unordered() : group_commit_queue_busy(0) +{ + pthread_cond_init(&COND_queue_busy, 0); +} + +TC_LOG_unordered::~TC_LOG_unordered() +{ + pthread_cond_destroy(&COND_queue_busy); +} + +int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, + bool need_commit_ordered) +{ + int cookie; + struct TC_group_commit_entry entry; + bool is_group_commit_leader; + LINT_INIT(is_group_commit_leader); + + if (need_prepare_ordered) + { + pthread_mutex_lock(&LOCK_prepare_ordered); + run_prepare_ordered(thd, all); + if (need_commit_ordered) + { + /* + Must put us in queue so we can run_commit_ordered() in same sequence + as we did run_prepare_ordered(). + */ + entry.thd= thd; + entry.group_commit_ready= false; + TC_group_commit_entry *previous_queue= group_commit_queue; + entry.next= previous_queue; + group_commit_queue= &entry; + is_group_commit_leader= (previous_queue == NULL); + } + pthread_mutex_unlock(&LOCK_prepare_ordered); + } + + if (xid) + cookie= log_xid(thd, xid); + else + cookie= 0; + + if (need_commit_ordered) + { + if (need_prepare_ordered) + { + /* + We did the run_prepare_ordered() serialised, then ran the log_xid() in + parallel. Now we have to do run_commit_ordered() serialised in the + same sequence as run_prepare_ordered(). + + We do this starting from the head of the queue, each thread doing + run_commit_ordered() and signalling the next in queue. + */ + if (is_group_commit_leader) + { + /* The first in queue starts the ball rolling. */ + pthread_mutex_lock(&LOCK_prepare_ordered); + while (group_commit_queue_busy) + pthread_cond_wait(&COND_queue_busy, &LOCK_prepare_ordered); + TC_group_commit_entry *queue= group_commit_queue; + group_commit_queue= NULL; + /* + Mark the queue busy while we bounce it from one thread to the + next. + */ + group_commit_queue_busy= TRUE; + pthread_mutex_unlock(&LOCK_prepare_ordered); + + queue= reverse_queue(queue); + DBUG_ASSERT(queue == &entry && queue->thd == thd); + } + else + { + /* Not first in queue; just wait until previous thread wakes us up. */ + group_commit_wait_for_wakeup(&entry); + } + } + + /* Only run commit_ordered() if log_xid was successful. */ + if (cookie) + { + pthread_mutex_lock(&LOCK_commit_ordered); + run_commit_ordered(thd, all); + pthread_mutex_unlock(&LOCK_commit_ordered); + } + + if (need_prepare_ordered) + { + TC_group_commit_entry *next= entry.next; + if (next) + { + group_commit_wakeup_other(next); + } + else + { + pthread_mutex_lock(&LOCK_prepare_ordered); + group_commit_queue_busy= FALSE; + pthread_cond_signal(&COND_queue_busy); + pthread_mutex_unlock(&LOCK_prepare_ordered); + } + } + } + + return cookie; +} + + +TC_LOG_group_commit::TC_LOG_group_commit() + : num_commits(0), num_group_commits(0) +{ + my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, + "LOCK_group_commit", MYF(0)); +} + +TC_LOG_group_commit::~TC_LOG_group_commit() +{ + pthread_mutex_destroy(&LOCK_group_commit); +} + +int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, + bool need_commit_ordered) +{ + IF_DBUG(int err;) + int cookie; + struct TC_group_commit_entry entry; + bool is_group_commit_leader; + + entry.thd= thd; + entry.all= all; + entry.group_commit_ready= false; + entry.xid_error= 0; + + pthread_mutex_lock(&LOCK_prepare_ordered); + TC_group_commit_entry *previous_queue= group_commit_queue; + entry.next= previous_queue; + group_commit_queue= &entry; + + DEBUG_SYNC(thd, "commit_before_prepare_ordered"); + run_prepare_ordered(thd, all); + DEBUG_SYNC(thd, "commit_after_prepare_ordered"); + pthread_mutex_unlock(&LOCK_prepare_ordered); + + is_group_commit_leader= (previous_queue == NULL); + + if (is_group_commit_leader) + { + TC_group_commit_entry *current; + + pthread_mutex_lock(&LOCK_group_commit); + DEBUG_SYNC(thd, "commit_after_get_LOCK_group_commit"); + + pthread_mutex_lock(&LOCK_prepare_ordered); + TC_group_commit_entry *queue= group_commit_queue; + group_commit_queue= NULL; + pthread_mutex_unlock(&LOCK_prepare_ordered); + + /* + Since we enqueue at the head, the queue is actually in reverse order. + So reverse it back into correct commit order before returning. + */ + queue= reverse_queue(queue); + + /* The first in the queue is the leader. */ + DBUG_ASSERT(queue == &entry && queue->thd == thd); + + DEBUG_SYNC(thd, "commit_before_group_log_xid"); + /* This will set individual error codes in each thd->xid_error. */ + group_log_xid(queue); + DEBUG_SYNC(thd, "commit_after_group_log_xid"); + + /* + Call commit_ordered methods for all transactions in the queue + (that did not get an error in group_log_xid()). + + We do this under an additional global LOCK_commit_ordered; this is + so that transactions that do not need 2-phase commit do not have + to wait for the potentially long duration of LOCK_group_commit. + */ + current= queue; + + DEBUG_SYNC(thd, "commit_before_get_LOCK_commit_ordered"); + pthread_mutex_lock(&LOCK_commit_ordered); + /* + We cannot unlock LOCK_group_commit until we have locked + LOCK_commit_ordered; otherwise scheduling could allow the next + group commit to run ahead of us, messing up the order of + commit_ordered() calls. But as soon as LOCK_commit_ordered is + obtained, we can let the next group commit start. + */ + pthread_mutex_unlock(&LOCK_group_commit); + DEBUG_SYNC(thd, "commit_after_release_LOCK_group_commit"); + + ++num_group_commits; + do + { + ++num_commits; + if (!current->xid_error) + run_commit_ordered(current->thd, current->all); + + /* + Careful not to access current->next_commit_ordered after waking up + the other thread! As it may change immediately after wakeup. + */ + TC_group_commit_entry *next= current->next; + if (current != &entry) // Don't wake up ourself + group_commit_wakeup_other(current); + current= next; + } while (current != NULL); + DEBUG_SYNC(thd, "commit_after_group_run_commit_ordered"); + + pthread_mutex_unlock(&LOCK_commit_ordered); + } + else + { + /* If not leader, just wait until leader wakes us up. */ + group_commit_wait_for_wakeup(&entry); + } + + /* + Now that we're back in our own thread context, do any delayed processing + and error reporting. + */ + IF_DBUG(err= entry.xid_error;) + cookie= xid_log_after(&entry); + /* The cookie must be non-zero in the non-error case. */ + DBUG_ASSERT(err || cookie); + + return cookie; +} + + /********* transaction coordinator log for 2pc - mmap() based solution *******/ /* @@ -5878,30 +6565,68 @@ void TC_LOG_BINLOG::close() pthread_cond_destroy (&COND_prep_xids); } -/** - @todo - group commit - - @retval - 0 error - @retval - 1 success +/* + Do a binlog log_xid() for a group of transactions, linked through + thd->next_commit_ordered. */ -int TC_LOG_BINLOG::log_xid(THD *thd, my_xid xid) +void +TC_LOG_BINLOG::group_log_xid(TC_group_commit_entry *first) { - DBUG_ENTER("TC_LOG_BINLOG::log"); - Xid_log_event xle(thd, xid); - binlog_trx_data *trx_data= - (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); - /* - We always commit the entire transaction when writing an XID. Also - note that the return value is inverted. - */ - DBUG_RETURN(!binlog_end_trans(thd, trx_data, &xle, TRUE)); + DBUG_ENTER("TC_LOG_BINLOG::group_log_xid"); + trx_group_commit_leader(first); + for (TC_group_commit_entry *entry= first; entry; entry= entry->next) + { + binlog_trx_data *const trx_data= + (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); + entry->xid_error= trx_data->error; + } + DBUG_VOID_RETURN; } -void TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid) +int +TC_LOG_BINLOG::xid_log_after(TC_group_commit_entry *entry) { + binlog_trx_data *const trx_data= + (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); + if (trx_group_commit_finish(trx_data)) + return 0; // Returning zero cookie signals error + else + return 1; +} + +/* + After an XID is logged, we need to hold on to the current binlog file until + it is fully committed in the storage engine. The reason is that crash + recovery only looks at the latest binlog, so we must make sure there are no + outstanding prepared (but not committed) transactions before rotating the + binlog. + + To handle this, we keep a count of outstanding XIDs. This function is used + to increase this count when committing one or more transactions to the + binary log. +*/ +void +TC_LOG_BINLOG::mark_xids_active(uint xid_count) +{ + DBUG_ENTER("TC_LOG_BINLOG::mark_xids_active"); + DBUG_PRINT("info", ("xid_count=%u", xid_count)); + pthread_mutex_lock(&LOCK_prep_xids); + prepared_xids+= xid_count; + pthread_mutex_unlock(&LOCK_prep_xids); + DBUG_VOID_RETURN; +} + +/* + Once an XID is committed, it is safe to rotate the binary log, as it can no + longer be needed during crash recovery. + + This function is called to mark an XID this way. It needs to decrease the + count of pending XIDs, and signal the log rotator thread when it reaches zero. +*/ +void +TC_LOG_BINLOG::mark_xid_done() +{ + DBUG_ENTER("TC_LOG_BINLOG::mark_xid_done"); pthread_mutex_lock(&LOCK_prep_xids); DBUG_ASSERT(prepared_xids > 0); if (--prepared_xids == 0) { @@ -5909,7 +6634,16 @@ void TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid) pthread_cond_signal(&COND_prep_xids); } pthread_mutex_unlock(&LOCK_prep_xids); - rotate_and_purge(0); // as ::write() did not rotate + DBUG_VOID_RETURN; +} + +void TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid) +{ + DBUG_ENTER("TC_LOG_BINLOG::unlog"); + if (xid) + mark_xid_done(); + rotate_and_purge(0); // as ::write_transaction_to_binlog() did not rotate + DBUG_VOID_RETURN; } int TC_LOG_BINLOG::recover(IO_CACHE *log, Format_description_log_event *fdle) @@ -5981,6 +6715,72 @@ ulonglong mysql_bin_log_file_pos(void) #endif /* INNODB_COMPATIBILITY_HOOKS */ +static ulonglong binlog_status_var_num_commits; +static ulonglong binlog_status_var_num_group_commits; + +static SHOW_VAR binlog_status_vars_detail[]= +{ + {"commits", + (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, + {"group_commits", + (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, + {NullS, NullS, SHOW_LONG} +}; + +static int show_binlog_vars(THD *thd, SHOW_VAR *var, char *buff) +{ + mysql_bin_log.set_status_variables(); + var->type= SHOW_ARRAY; + var->value= (char *)&binlog_status_vars_detail; + return 0; +} + +static SHOW_VAR binlog_status_vars_top[]= { + {"binlog", (char *) &show_binlog_vars, SHOW_FUNC}, + {NullS, NullS, SHOW_LONG} +}; + +#ifndef DBUG_OFF +static MYSQL_SYSVAR_ULONG( + dbug_fsync_sleep, + opt_binlog_dbug_fsync_sleep, + PLUGIN_VAR_RQCMDARG, + "Extra sleep (in microseconds) to add to binlog fsync(), for debugging", + NULL, + NULL, + 0, + 0, + ULONG_MAX, + 0); + +static struct st_mysql_sys_var *binlog_sys_vars[]= +{ + MYSQL_SYSVAR(dbug_fsync_sleep), + NULL +}; +#endif + + +/* + Copy out current values of status variables, for SHOW STATUS or + information_schema.global_status. + + This is called only under LOCK_status, so we can fill in a static array. +*/ +void +TC_LOG_BINLOG::set_status_variables() +{ + ulonglong num_commits, num_group_commits; + + pthread_mutex_lock(&LOCK_commit_ordered); + num_commits= this->num_commits; + num_group_commits= this->num_group_commits; + pthread_mutex_unlock(&LOCK_commit_ordered); + + binlog_status_var_num_commits= num_commits; + binlog_status_var_num_group_commits= num_group_commits; +} + struct st_mysql_storage_engine binlog_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; @@ -5995,8 +6795,12 @@ mysql_declare_plugin(binlog) binlog_init, /* Plugin Init */ NULL, /* Plugin Deinit */ 0x0100 /* 1.0 */, - NULL, /* status variables */ + binlog_status_vars_top, /* status variables */ +#ifndef DBUG_OFF + binlog_sys_vars, /* system variables */ +#else NULL, /* system variables */ +#endif NULL /* config options */ } mysql_declare_plugin_end; diff --git a/sql/log.h b/sql/log.h index 8b5dfcb3935..ac0ebea6db4 100644 --- a/sql/log.h +++ b/sql/log.h @@ -33,11 +33,173 @@ class TC_LOG virtual int open(const char *opt_name)=0; virtual void close()=0; - virtual int log_xid(THD *thd, my_xid xid)=0; + virtual int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, + bool need_commit_ordered) = 0; virtual void unlog(ulong cookie, my_xid xid)=0; + +protected: + /* + These methods are meant to be invoked from log_and_order() implementations + to run any prepare_ordered() respectively commit_ordered() methods in + participating handlers. + + They must be called using suitable thread syncronisation to ensure that + they are each called in the correct commit order among all + transactions. However, it is only necessary to call them if the + corresponding flag passed to log_and_order is set (it is safe, but not + required, to call them when the flag is false). + + The caller must be holding LOCK_prepare_ordered respectively + LOCK_commit_ordered when calling these methods. + */ + void run_prepare_ordered(THD *thd, bool all); + void run_commit_ordered(THD *thd, bool all); }; -class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging +/* + Locks used to ensure serialised execution of TC_LOG::run_prepare_ordered() + and TC_LOG::run_commit_ordered(), or any other code that calls handler + prepare_ordered() or commit_ordered() methods. +*/ +extern pthread_mutex_t LOCK_prepare_ordered; +extern pthread_mutex_t LOCK_commit_ordered; + +extern void TC_init(); +extern void TC_destroy(); + +/* + Base class for two TC implementations TC_LOG_unordered and + TC_LOG_group_commit that both use a queue of threads waiting for group + commit. +*/ +class TC_LOG_queued: public TC_LOG +{ +protected: + TC_LOG_queued(); + ~TC_LOG_queued(); + + /* Structure used to link list of THDs waiting for group commit. */ + struct TC_group_commit_entry + { + struct TC_group_commit_entry *next; + THD *thd; + /* This is the `all' parameter for ha_commit_trans() etc. */ + bool all; + /* + Flag set true when it is time for this thread to wake up after group + commit. Used with THD::LOCK_commit_ordered and THD::COND_commit_ordered. + */ + bool group_commit_ready; + /* + Set by TC_LOG_group_commit::group_log_xid(), to return per-thd error and + cookie. + */ + int xid_error; + }; + + TC_group_commit_entry * reverse_queue(TC_group_commit_entry *queue); + + void group_commit_wait_for_wakeup(TC_group_commit_entry *entry); + void group_commit_wakeup_other(TC_group_commit_entry *other); + + /* + This is a queue of threads waiting for being allowed to commit. + Access to the queue must be protected by LOCK_prepare_ordered. + */ + TC_group_commit_entry *group_commit_queue; +}; + +class TC_LOG_unordered: public TC_LOG_queued +{ +public: + TC_LOG_unordered(); + ~TC_LOG_unordered(); + + int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, bool need_commit_ordered); + +protected: + virtual int log_xid(THD *thd, my_xid xid)=0; + +private: + /* + This flag and condition is used to reserve the queue while threads in it + each run the commit_ordered() methods one after the other. Only once the + last commit_ordered() in the queue is done can we start on a new queue + run. + + Since we start this process in the first thread in the queue and finish in + the last (and possibly different) thread, we need a condition variable for + this (we cannot unlock a mutex in a different thread than the one who + locked it). + + The condition is used together with the LOCK_prepare_ordered mutex. + */ + my_bool group_commit_queue_busy; + pthread_cond_t COND_queue_busy; +}; + +class TC_LOG_group_commit: public TC_LOG_queued +{ +public: + TC_LOG_group_commit(); + ~TC_LOG_group_commit(); + + int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, bool need_commit_ordered); + +protected: + /* Total number of committed transactions. */ + ulonglong num_commits; + /* Number of group commits done. */ + ulonglong num_group_commits; + + /* + When using this class, this method is used instead of log_xid() to do + logging of a group of transactions all at once. + + The transactions will be linked through THD::next_commit_ordered. + + Additionally, when this method is used instead of log_xid(), the order in + which handler->prepare_ordered() and handler->commit_ordered() are called + is guaranteed to be the same as the order of calls and THD list elements + for group_log_xid(). + + This can be used to efficiently implement group commit that at the same + time preserves the order of commits among handlers and TC (eg. to get same + commit order in InnoDB and binary log). + + For TCs that do not need this, it can be preferable to use plain log_xid() + with class TC_LOG_unordered instead, as it allows threads to run log_xid() + in parallel with each other. In contrast, group_log_xid() runs under a + global mutex, so it is guaranteed that only once call into it will be + active at once. + + Since this call handles multiple threads/THDs at once, my_error() (and + other code that relies on thread local storage) cannot be used in this + method. Instead, the implementation must record any error and report it as + the return value from xid_log_after(), which will be invoked individually + for each thread. + + In the success case, this method must set thd->xid_cookie for each thread + to the cookie that is normally returned from log_xid() (which must be + non-zero in the non-error case). + */ + virtual void group_log_xid(TC_group_commit_entry *first) = 0; + /* + Called for each transaction (in corrent thread context) after + group_log_xid() has finished, but with no guarantee on ordering among + threads. + Can be used to do error reporting etc. */ + virtual int xid_log_after(TC_group_commit_entry *entry) = 0; + +private: + /* Mutex used to serialise calls to group_log_xid(). */ + pthread_mutex_t LOCK_group_commit; +}; + +class TC_LOG_DUMMY: public TC_LOG_unordered // use it to disable the logging { public: TC_LOG_DUMMY() {} @@ -48,7 +210,7 @@ public: }; #ifdef HAVE_MMAP -class TC_LOG_MMAP: public TC_LOG +class TC_LOG_MMAP: public TC_LOG_unordered { public: // only to keep Sun Forte on sol9x86 happy typedef enum { @@ -227,12 +389,19 @@ private: time_t last_time; }; -class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG +class binlog_trx_data; +class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG { private: /* LOCK_log and LOCK_index are inited by init_pthread_objects() */ pthread_mutex_t LOCK_index; pthread_mutex_t LOCK_prep_xids; + /* + Mutex to protect the queue of transactions waiting to participate in group + commit. (Only used on platforms without native atomic operations). + */ + pthread_mutex_t LOCK_queue; + pthread_cond_t COND_prep_xids; pthread_cond_t update_cond; ulonglong bytes_written; @@ -271,8 +440,8 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG In 5.0 it's 0 for relay logs too! */ bool no_auto_events; - - ulonglong m_table_map_version; + /* Queue of transactions queued up to participate in group commit. */ + binlog_trx_data *group_commit_queue; int write_to_file(IO_CACHE *cache); /* @@ -282,6 +451,14 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG */ void new_file_without_locking(); void new_file_impl(bool need_lock); + int write_transaction(binlog_trx_data *trx_data); + bool write_transaction_to_binlog_events(binlog_trx_data *trx_data); + void trx_group_commit_participant(binlog_trx_data *trx_data); + void trx_group_commit_leader(TC_group_commit_entry *first); + binlog_trx_data *atomic_enqueue_trx(binlog_trx_data *trx_data); + binlog_trx_data *atomic_grab_trx_queue(); + void mark_xid_done(); + void mark_xids_active(uint xid_count); public: MYSQL_LOG::generate_name; @@ -310,18 +487,11 @@ public: int open(const char *opt_name); void close(); - int log_xid(THD *thd, my_xid xid); + void group_log_xid(TC_group_commit_entry *first); + int xid_log_after(TC_group_commit_entry *entry); void unlog(ulong cookie, my_xid xid); int recover(IO_CACHE *log, Format_description_log_event *fdle); #if !defined(MYSQL_CLIENT) - bool is_table_mapped(TABLE *table) const - { - return table->s->table_map_version == table_map_version(); - } - - ulonglong table_map_version() const { return m_table_map_version; } - void update_table_map_version() { ++m_table_map_version; } - int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event); int remove_pending_rows_event(THD *thd); @@ -362,10 +532,12 @@ public: void new_file(); bool write(Log_event* event_info); // binary log write - bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident); - bool write_incident(THD *thd, bool lock); + bool write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, + Log_event *end_ev); + bool trx_group_commit_finish(binlog_trx_data *trx_data); + bool write_incident(THD *thd); - int write_cache(IO_CACHE *cache, bool lock_log, bool flush_and_sync); + int write_cache(IO_CACHE *cache); void set_write_error(THD *thd); bool check_write_error(THD *thd); @@ -420,6 +592,7 @@ public: inline void unlock_index() { pthread_mutex_unlock(&LOCK_index);} inline IO_CACHE *get_index_file() { return &index_file;} inline uint32 get_open_count() { return open_count; } + void set_status_variables(); }; class Log_event_handler diff --git a/sql/log_event.h b/sql/log_event.h index 36715b1d151..46d02f5d2c5 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -463,10 +463,9 @@ struct sql_ex_info #define LOG_EVENT_SUPPRESS_USE_F 0x8 /* - The table map version internal to the log should be increased after - the event has been written to the binary log. + This used to be LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F, but is now unused. */ -#define LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F 0x10 +#define LOG_EVENT_UNUSED1_F 0x10 /** @def LOG_EVENT_ARTIFICIAL_F diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 645b7498042..fd39b979f4c 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1333,6 +1333,7 @@ void clean_up(bool print_message) ha_end(); if (tc_log) tc_log->close(); + TC_destroy(); xid_cache_free(); wt_end(); delete_elements(&key_caches, (void (*)(const char*, uchar*)) free_key_cache); @@ -4124,6 +4125,8 @@ a file name for --log-bin-index option", opt_binlog_index_name); if (!errmesg[0][0]) unireg_abort(1); + TC_init(); + /* We have to initialize the storage engines before CSV logging */ if (ha_init()) { diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 580fe8057cd..8dbba6b2ec5 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -673,6 +673,8 @@ THD::THD() active_vio = 0; #endif pthread_mutex_init(&LOCK_thd_data, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_commit_ordered, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_commit_ordered, 0); /* Variables with default values */ proc_info="login"; @@ -999,6 +1001,8 @@ THD::~THD() free_root(&transaction.mem_root,MYF(0)); #endif mysys_var=0; // Safety (shouldn't be needed) + pthread_cond_destroy(&COND_commit_ordered); + pthread_mutex_destroy(&LOCK_commit_ordered); pthread_mutex_destroy(&LOCK_thd_data); #ifndef DBUG_OFF dbug_sentry= THD_SENTRY_GONE; @@ -3773,7 +3777,6 @@ int THD::binlog_flush_pending_rows_event(bool stmt_end) if (stmt_end) { pending->set_flags(Rows_log_event::STMT_END_F); - pending->flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; binlog_table_maps= 0; } @@ -3901,7 +3904,6 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, { Query_log_event qinfo(this, query_arg, query_len, is_trans, suppress_use, errcode); - qinfo.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; /* Binlog table maps will be irrelevant after a Query_log_event (they are just removed on the slave side) so after the query diff --git a/sql/sql_class.h b/sql/sql_class.h index aa39ddb2b15..aa2933e4070 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1438,6 +1438,10 @@ public: /* container for handler's private per-connection data */ Ha_data ha_data[MAX_HA]; + /* Mutex and condition for waking up threads after group commit. */ + pthread_mutex_t LOCK_commit_ordered; + pthread_cond_t COND_commit_ordered; + #ifndef MYSQL_CLIENT int binlog_setup_trx_data(); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 82cc8f81b4a..441fe93aaef 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -516,7 +516,6 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, else { Delete_file_log_event d(thd, db, transactional_table); - d.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; (void) mysql_bin_log.write(&d); } } @@ -698,7 +697,6 @@ static bool write_execute_load_query_log_event(THD *thd, sql_exchange* ex, (duplicates == DUP_REPLACE) ? LOAD_DUP_REPLACE : (ignore ? LOAD_DUP_IGNORE : LOAD_DUP_ERROR), transactional_table, FALSE, errcode); - e.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; return mysql_bin_log.write(&e); } diff --git a/sql/table.cc b/sql/table.cc index 733aa3e6887..2ddde40778d 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -296,13 +296,6 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->version= refresh_version; - /* - This constant is used to mark that no table map version has been - assigned. No arithmetic is done on the value: it will be - overwritten with a value taken from MYSQL_BIN_LOG. - */ - share->table_map_version= ~(ulonglong)0; - /* Since alloc_table_share() can be called without any locking (for example, ha_create_table... functions), we do not assign a table @@ -367,10 +360,9 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, share->frm_version= FRM_VER_TRUE_VARCHAR; /* - Temporary tables are not replicated, but we set up these fields + Temporary tables are not replicated, but we set up this fields anyway to be able to catch errors. */ - share->table_map_version= ~(ulonglong)0; share->cached_row_logging_check= -1; /* diff --git a/sql/table.h b/sql/table.h index a24e79e26cf..efc48090b3b 100644 --- a/sql/table.h +++ b/sql/table.h @@ -433,7 +433,6 @@ typedef struct st_table_share bool waiting_on_cond; /* Protection against free */ bool deleting; /* going to delete this table */ ulong table_map_id; /* for row-based replication */ - ulonglong table_map_version; /* Cache for row-based replication table share checks that does not diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index c28e0c57d2b..e19ca7206fd 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -138,8 +138,6 @@ bool check_global_access(THD *thd, ulong want_access); /** to protect innobase_open_files */ static pthread_mutex_t innobase_share_mutex; -/** to force correct commit order in binlog */ -static pthread_mutex_t prepare_commit_mutex; static ulong commit_threads = 0; static pthread_mutex_t commit_threads_m; static pthread_cond_t commit_cond; @@ -239,6 +237,7 @@ static const char* innobase_change_buffering_values[IBUF_USE_COUNT] = { static INNOBASE_SHARE *get_share(const char *table_name); static void free_share(INNOBASE_SHARE *share); static int innobase_close_connection(handlerton *hton, THD* thd); +static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all); static int innobase_commit(handlerton *hton, THD* thd, bool all); static int innobase_rollback(handlerton *hton, THD* thd, bool all); static int innobase_rollback_to_savepoint(handlerton *hton, THD* thd, @@ -1356,7 +1355,6 @@ innobase_trx_init( trx_t* trx) /*!< in/out: InnoDB transaction handle */ { DBUG_ENTER("innobase_trx_init"); - DBUG_ASSERT(EQ_CURRENT_THD(thd)); DBUG_ASSERT(thd == trx->mysql_thd); trx->check_foreigns = !thd_test_options( @@ -1416,8 +1414,6 @@ check_trx_exists( { trx_t*& trx = thd_to_trx(thd); - ut_ad(EQ_CURRENT_THD(thd)); - if (trx == NULL) { trx = innobase_trx_allocate(thd); } else if (UNIV_UNLIKELY(trx->magic_n != TRX_MAGIC_N)) { @@ -2024,6 +2020,7 @@ innobase_init( innobase_hton->savepoint_set=innobase_savepoint; innobase_hton->savepoint_rollback=innobase_rollback_to_savepoint; innobase_hton->savepoint_release=innobase_release_savepoint; + innobase_hton->commit_ordered=innobase_commit_ordered; innobase_hton->commit=innobase_commit; innobase_hton->rollback=innobase_rollback; innobase_hton->prepare=innobase_xa_prepare; @@ -2492,7 +2489,6 @@ skip_overwrite: innobase_open_tables = hash_create(200); pthread_mutex_init(&innobase_share_mutex, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&prepare_commit_mutex, MY_MUTEX_INIT_FAST); pthread_mutex_init(&commit_threads_m, MY_MUTEX_INIT_FAST); pthread_mutex_init(&commit_cond_m, MY_MUTEX_INIT_FAST); pthread_mutex_init(&analyze_mutex, MY_MUTEX_INIT_FAST); @@ -2547,7 +2543,6 @@ innobase_end( my_free(internal_innobase_data_file_path, MYF(MY_ALLOW_ZERO_PTR)); pthread_mutex_destroy(&innobase_share_mutex); - pthread_mutex_destroy(&prepare_commit_mutex); pthread_mutex_destroy(&commit_threads_m); pthread_mutex_destroy(&commit_cond_m); pthread_mutex_destroy(&analyze_mutex); @@ -2680,6 +2675,101 @@ innobase_start_trx_and_assign_read_view( DBUG_RETURN(0); } +/*****************************************************************//** +Perform the first, fast part of InnoDB commit. + +Doing it in this call ensures that we get the same commit order here +as in binlog and any other participating transactional storage engines. + +Note that we want to do as little as really needed here, as we run +under a global mutex. The expensive fsync() is done later, in +innobase_commit(), without a lock so group commit can take place. + +Note also that this method can be called from a different thread than +the one handling the rest of the transaction. */ +static +void +innobase_commit_ordered( +/*============*/ + handlerton *hton, /*!< in: Innodb handlerton */ + THD* thd, /*!< in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /*!< in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + DBUG_ENTER("innobase_commit_ordered"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + if (trx->active_trans == 0 + && trx->conc_state != TRX_NOT_STARTED) { + /* We cannot throw error here; instead we will catch this error + again in innobase_commit() and report it from there. */ + DBUG_VOID_RETURN; + } + /* Since we will reserve the kernel mutex, we have to release + the search system latch first to obey the latching order. */ + + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + /* commit_ordered is only called when committing the whole transaction + (or an SQL statement when autocommit is on). */ + DBUG_ASSERT(all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); + + /* We need current binlog position for ibbackup to work. + Note, the position is current because commit_ordered is guaranteed + to be called in same sequenece as writing to binlog. */ + +retry: + if (innobase_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads++; + + if (commit_threads > innobase_commit_concurrency) { + commit_threads--; + pthread_cond_wait(&commit_cond, + &commit_cond_m); + pthread_mutex_unlock(&commit_cond_m); + goto retry; + } + else { + pthread_mutex_unlock(&commit_cond_m); + } + } + + /* The following calls to read the MySQL binary log + file name and the position return consistent results: + 1) We use commit_ordered() to get same commit order + in InnoDB as in binary log. + 2) A MySQL log file rotation cannot happen because + MySQL protects against this by having a counter of + transactions in prepared state and it only allows + a rotation when the counter drops to zero. See + LOCK_prep_xids and COND_prep_xids in log.cc. */ + trx->mysql_log_file_name = mysql_bin_log_file_name(); + trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos(); + + /* Don't do write + flush right now. For group commit + to work we want to do the flush in the innobase_commit() + method, which runs without holding any locks. */ + trx->flush_log_later = TRUE; + innobase_commit_low(trx); + trx->flush_log_later = FALSE; + + if (innobase_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads--; + pthread_cond_signal(&commit_cond); + pthread_mutex_unlock(&commit_cond_m); + } + + DBUG_VOID_RETURN; +} + /*****************************************************************//** Commits a transaction in an InnoDB database or marks an SQL statement ended. @@ -2702,13 +2792,6 @@ innobase_commit( trx = check_trx_exists(thd); - /* Since we will reserve the kernel mutex, we have to release - the search system latch first to obey the latching order. */ - - if (trx->has_search_latch) { - trx_search_latch_release_if_reserved(trx); - } - /* The flag trx->active_trans is set to 1 in 1. ::external_lock(), @@ -2736,62 +2819,8 @@ innobase_commit( /* We were instructed to commit the whole transaction, or this is an SQL statement end and autocommit is on */ - /* We need current binlog position for ibbackup to work. - Note, the position is current because of - prepare_commit_mutex */ -retry: - if (innobase_commit_concurrency > 0) { - pthread_mutex_lock(&commit_cond_m); - commit_threads++; - - if (commit_threads > innobase_commit_concurrency) { - commit_threads--; - pthread_cond_wait(&commit_cond, - &commit_cond_m); - pthread_mutex_unlock(&commit_cond_m); - goto retry; - } - else { - pthread_mutex_unlock(&commit_cond_m); - } - } - - /* The following calls to read the MySQL binary log - file name and the position return consistent results: - 1) Other InnoDB transactions cannot intervene between - these calls as we are holding prepare_commit_mutex. - 2) Binary logging of other engines is not relevant - to InnoDB as all InnoDB requires is that committing - InnoDB transactions appear in the same order in the - MySQL binary log as they appear in InnoDB logs. - 3) A MySQL log file rotation cannot happen because - MySQL protects against this by having a counter of - transactions in prepared state and it only allows - a rotation when the counter drops to zero. See - LOCK_prep_xids and COND_prep_xids in log.cc. */ - trx->mysql_log_file_name = mysql_bin_log_file_name(); - trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos(); - - /* Don't do write + flush right now. For group commit - to work we want to do the flush after releasing the - prepare_commit_mutex. */ - trx->flush_log_later = TRUE; - innobase_commit_low(trx); - trx->flush_log_later = FALSE; - - if (innobase_commit_concurrency > 0) { - pthread_mutex_lock(&commit_cond_m); - commit_threads--; - pthread_cond_signal(&commit_cond); - pthread_mutex_unlock(&commit_cond_m); - } - - if (trx->active_trans == 2) { - - pthread_mutex_unlock(&prepare_commit_mutex); - } - - /* Now do a write + flush of logs. */ + /* We did the first part already in innobase_commit_ordered(), + Now finish by doing a write + flush of logs. */ trx_commit_complete_for_mysql(trx); trx->active_trans = 0; @@ -4621,6 +4650,7 @@ no_commit: no need to re-acquire locks on it. */ /* Altering to InnoDB format */ + innobase_commit_ordered(ht, user_thd, 1); innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ prebuilt->trx->active_trans = 1; @@ -4637,6 +4667,7 @@ no_commit: /* Commit the transaction. This will release the table locks, so they have to be acquired again. */ + innobase_commit_ordered(ht, user_thd, 1); innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ prebuilt->trx->active_trans = 1; @@ -8339,6 +8370,7 @@ ha_innobase::external_lock( if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { if (trx->active_trans != 0) { + innobase_commit_ordered(ht, thd, TRUE); innobase_commit(ht, thd, TRUE); } } else { @@ -9448,36 +9480,6 @@ innobase_xa_prepare( srv_active_wake_master_thread(); - if (thd_sql_command(thd) != SQLCOM_XA_PREPARE && - (all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) - { - if (srv_enable_unsafe_group_commit && !THDVAR(thd, support_xa)) { - /* choose group commit rather than binlog order */ - return(error); - } - - /* For ibbackup to work the order of transactions in binlog - and InnoDB must be the same. Consider the situation - - thread1> prepare; write to binlog; ... - - thread2> prepare; write to binlog; commit - thread1> ... commit - - To ensure this will not happen we're taking the mutex on - prepare, and releasing it on commit. - - Note: only do it for normal commits, done via ha_commit_trans. - If 2pc protocol is executed by external transaction - coordinator, it will be just a regular MySQL client - executing XA PREPARE and XA COMMIT commands. - In this case we cannot know how many minutes or hours - will be between XA PREPARE and XA COMMIT, and we don't want - to block for undefined period of time. */ - pthread_mutex_lock(&prepare_commit_mutex); - trx->active_trans = 2; - } - return(error); } @@ -10669,11 +10671,6 @@ static MYSQL_SYSVAR_ENUM(adaptive_checkpoint, srv_adaptive_checkpoint, "Enable/Disable flushing along modified age. (none, reflex, [estimate])", NULL, innodb_adaptive_checkpoint_update, 2, &adaptive_checkpoint_typelib); -static MYSQL_SYSVAR_ULONG(enable_unsafe_group_commit, srv_enable_unsafe_group_commit, - PLUGIN_VAR_RQCMDARG, - "Enable/Disable unsafe group commit when support_xa=OFF and use with binlog or other XA storage engine.", - NULL, NULL, 0, 0, 1, 0); - static MYSQL_SYSVAR_ULONG(expand_import, srv_expand_import, PLUGIN_VAR_RQCMDARG, "Enable/Disable converting automatically *.ibd files when import tablespace.", @@ -10763,7 +10760,6 @@ static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(flush_neighbor_pages), MYSQL_SYSVAR(read_ahead), MYSQL_SYSVAR(adaptive_checkpoint), - MYSQL_SYSVAR(enable_unsafe_group_commit), MYSQL_SYSVAR(expand_import), MYSQL_SYSVAR(extra_rsegments), MYSQL_SYSVAR(dict_size_limit), From 8bc445360ee9f21560b5613e0bdc8b363f34d5b6 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Oct 2010 20:40:31 +0200 Subject: [PATCH 02/35] MWL#116: Efficient group commit Tweak the commit_ordered() semantics. Now it is only called for transactions that go through 2-phase commit. This avoids forcing engines to make commits visible before they are durable. Also take LOCK_commit_ordered() around START TRANSACTION WITH CONSISTENT SNAPSHOT, to get a truly consistent snapshot. --- sql/handler.cc | 31 ++---- sql/handler.h | 16 +-- storage/xtradb/handler/ha_innodb.cc | 157 ++++++++++++++++------------ storage/xtradb/include/trx0trx.h | 11 +- 4 files changed, 117 insertions(+), 98 deletions(-) diff --git a/sql/handler.cc b/sql/handler.cc index 4975b4a1230..6503516a2f8 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1251,32 +1251,7 @@ int ha_commit_one_phase(THD *thd, bool all) enclosing 'all' transaction is rolled back. */ bool is_real_trans=all || thd->transaction.all.ha_list == 0; - Ha_trx_info *ha_info= trans->ha_list; DBUG_ENTER("ha_commit_one_phase"); -#ifdef USING_TRANSACTIONS - if (ha_info) - { - if (is_real_trans) - { - bool locked= false; - for (; ha_info; ha_info= ha_info->next()) - { - handlerton *ht= ha_info->ht(); - if (ht->commit_ordered) - { - if (ha_info->is_trx_read_write() && !locked) - { - pthread_mutex_lock(&LOCK_commit_ordered); - locked= 1; - } - ht->commit_ordered(ht, thd, all); - } - } - if (locked) - pthread_mutex_unlock(&LOCK_commit_ordered); - } - } -#endif /* USING_TRANSACTIONS */ DBUG_RETURN(commit_one_phase_2(thd, all, trans, is_real_trans)); } @@ -1901,7 +1876,13 @@ int ha_start_consistent_snapshot(THD *thd) { bool warn= true; + /* + Holding the LOCK_commit_ordered mutex ensures that for any transaction + we either see it committed in all engines, or in none. + */ + pthread_mutex_lock(&LOCK_commit_ordered); plugin_foreach(thd, snapshot_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &warn); + pthread_mutex_unlock(&LOCK_commit_ordered); /* Same idea as when one wants to CREATE TABLE in one engine which does not diff --git a/sql/handler.h b/sql/handler.h index a5c4af533be..82926dc08c0 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -667,6 +667,11 @@ struct handlerton full transaction is committed, not for each commit of statement transaction in a multi-statement transaction. + Not that like prepare(), commit_ordered() is only called when 2-phase + commit takes place. Ie. when no binary log and only a single engine + participates in a transaction, one commit() is called, no + commit_orderd(). So engines must be prepared for this. + The calls to commit_ordered() in multiple parallel transactions is guaranteed to happen in the same order in every participating handler. This can be used to ensure the same commit order among multiple @@ -684,11 +689,9 @@ struct handlerton doing any time-consuming or blocking operations in commit_ordered() will limit scalability. - Handlers can rely on commit_ordered() calls for transactions that updated - data to be serialised (no two calls can run in parallel, so no extra - locking on the handler part is required to ensure this). However, calls - for SELECT-only transactions are not serialised, so can occur in parallel - with each other and with at most one write-transaction. + Handlers can rely on commit_ordered() calls to be serialised (no two + calls can run in parallel, so no extra locking on the handler part is + required to ensure this). Note that commit_ordered() can be called from a different thread than the one handling the transaction! So it can not do anything that depends on @@ -700,7 +703,8 @@ struct handlerton must be saved and returned from the commit() method instead. The commit_ordered method is optional, and can be left unset if not - needed in a particular handler. + needed in a particular handler (then there will be no ordering guarantees + wrt. other engines and binary log). */ void (*commit_ordered)(handlerton *hton, THD *thd, bool all); int (*rollback)(handlerton *hton, THD *thd, bool all); diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 7b1f3bc01dc..d9abb5ae032 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -1700,10 +1700,10 @@ innobase_query_caching_of_table_permitted( /* The call of row_search_.. will start a new transaction if it is not yet started */ - if (trx->active_trans == 0) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(innodb_hton_ptr, thd); - trx->active_trans = 1; + trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } if (row_search_check_if_query_cache_permitted(trx, norm_name)) { @@ -1973,11 +1973,11 @@ ha_innobase::init_table_handle_for_HANDLER(void) /* Set the MySQL flag to mark that there is an active transaction */ - if (prebuilt->trx->active_trans == 0) { + if ((prebuilt->trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, user_thd); - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } /* We did the necessary inits in this function, no need to repeat them @@ -2704,58 +2704,21 @@ innobase_start_trx_and_assign_read_view( /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(hton, thd); - trx->active_trans = 1; + trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } DBUG_RETURN(0); } -/*****************************************************************//** -Perform the first, fast part of InnoDB commit. - -Doing it in this call ensures that we get the same commit order here -as in binlog and any other participating transactional storage engines. - -Note that we want to do as little as really needed here, as we run -under a global mutex. The expensive fsync() is done later, in -innobase_commit(), without a lock so group commit can take place. - -Note also that this method can be called from a different thread than -the one handling the rest of the transaction. */ static void -innobase_commit_ordered( +innobase_commit_ordered_2( /*============*/ - handlerton *hton, /*!< in: Innodb handlerton */ - THD* thd, /*!< in: MySQL thread handle of the user for whom - the transaction should be committed */ - bool all) /*!< in: TRUE - commit transaction - FALSE - the current SQL statement ended */ + trx_t* trx) /*!< in: Innodb transaction */ { - trx_t* trx; DBUG_ENTER("innobase_commit_ordered"); - DBUG_ASSERT(hton == innodb_hton_ptr); - - trx = check_trx_exists(thd); - - if (trx->active_trans == 0 - && trx->conc_state != TRX_NOT_STARTED) { - /* We cannot throw error here; instead we will catch this error - again in innobase_commit() and report it from there. */ - DBUG_VOID_RETURN; - } - /* Since we will reserve the kernel mutex, we have to release - the search system latch first to obey the latching order. */ - - if (trx->has_search_latch) { - trx_search_latch_release_if_reserved(trx); - } - - /* commit_ordered is only called when committing the whole transaction - (or an SQL statement when autocommit is on). */ - DBUG_ASSERT(all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); /* We need current binlog position for ibbackup to work. Note, the position is current because commit_ordered is guaranteed @@ -2807,6 +2770,60 @@ retry: DBUG_VOID_RETURN; } +/*****************************************************************//** +Perform the first, fast part of InnoDB commit. + +Doing it in this call ensures that we get the same commit order here +as in binlog and any other participating transactional storage engines. + +Note that we want to do as little as really needed here, as we run +under a global mutex. The expensive fsync() is done later, in +innobase_commit(), without a lock so group commit can take place. + +Note also that this method can be called from a different thread than +the one handling the rest of the transaction. */ +static +void +innobase_commit_ordered( +/*============*/ + handlerton *hton, /*!< in: Innodb handlerton */ + THD* thd, /*!< in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /*!< in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + DBUG_ENTER("innobase_commit_ordered"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + /* Since we will reserve the kernel mutex, we have to release + the search system latch first to obey the latching order. */ + + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 + && trx->conc_state != TRX_NOT_STARTED) { + /* We cannot throw error here; instead we will catch this error + again in innobase_commit() and report it from there. */ + DBUG_VOID_RETURN; + } + + /* commit_ordered is only called when committing the whole transaction + (or an SQL statement when autocommit is on). */ + DBUG_ASSERT(all || + (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); + + innobase_commit_ordered_2(trx); + + trx->active_trans |= TRX_ACTIVE_COMMIT_ORDERED; + + DBUG_VOID_RETURN; +} + /*****************************************************************//** Commits a transaction in an InnoDB database or marks an SQL statement ended. @@ -2829,7 +2846,15 @@ innobase_commit( trx = check_trx_exists(thd); - /* The flag trx->active_trans is set to 1 in + /* Since we will reserve the kernel mutex, we have to release + the search system latch first to obey the latching order. */ + + if (trx->has_search_latch && + (trx->active_trans & TRX_ACTIVE_COMMIT_ORDERED) == 0) { + trx_search_latch_release_if_reserved(trx); + } + + /* The flag TRX_ACTIVE_IN_MYSQL in trx->active_trans is set in 1. ::external_lock(), 2. ::start_stmt(), @@ -2839,20 +2864,26 @@ innobase_commit( 6. innobase_start_trx_and_assign_read_view(), 7. ::transactional_table_lock() - and it is only set to 0 in a commit or a rollback. If it is 0 we know + and it is only cleared in a commit or a rollback. If it is unset we know there cannot be resources to be freed and we could return immediately. For the time being, we play safe and do the cleanup though there should be nothing to clean up. */ - if (trx->active_trans == 0 + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { sql_print_error("trx->active_trans == 0, but" " trx->conc_state != TRX_NOT_STARTED"); } + if (all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + /* Run the fast part of commit if we did not already. */ + if ((trx->active_trans & TRX_ACTIVE_COMMIT_ORDERED) == 0) { + innobase_commit_ordered_2(trx); + } + /* We were instructed to commit the whole transaction, or this is an SQL statement end and autocommit is on */ @@ -3076,7 +3107,7 @@ innobase_savepoint( innobase_release_stat_resources(trx); /* cannot happen outside of transaction */ - DBUG_ASSERT(trx->active_trans); + DBUG_ASSERT(trx->active_trans & TRX_ACTIVE_IN_MYSQL); /* TODO: use provided savepoint data area to store savepoint data */ char name[64]; @@ -3106,7 +3137,7 @@ innobase_close_connection( ut_a(trx); - if (trx->active_trans == 0 + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { sql_print_error("trx->active_trans == 0, but" @@ -5021,10 +5052,9 @@ no_commit: no need to re-acquire locks on it. */ /* Altering to InnoDB format */ - innobase_commit_ordered(ht, user_thd, 1); innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; /* We will need an IX lock on the destination table. */ prebuilt->sql_stat_start = TRUE; } else { @@ -5038,10 +5068,9 @@ no_commit: /* Commit the transaction. This will release the table locks, so they have to be acquired again. */ - innobase_commit_ordered(ht, user_thd, 1); innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; /* Re-acquire the table lock on the source table. */ row_lock_table_for_mysql(prebuilt, src_table, mode); /* We will need an IX lock on the destination table. */ @@ -8929,10 +8958,10 @@ ha_innobase::start_stmt( trx->detailed_error[0] = '\0'; /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } else { innobase_register_stmt(ht, thd); } @@ -9030,10 +9059,10 @@ ha_innobase::external_lock( /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } else if (trx->n_mysql_tables_in_use == 0) { innobase_register_stmt(ht, thd); } @@ -9131,8 +9160,7 @@ ha_innobase::external_lock( prebuilt->used_in_HANDLER = FALSE; if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { - if (trx->active_trans != 0) { - innobase_commit_ordered(ht, thd, TRUE); + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) != 0) { innobase_commit(ht, thd, TRUE); } } else { @@ -9217,10 +9245,10 @@ ha_innobase::transactional_table_lock( /* MySQL is setting a new transactional table lock */ /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_trans |= TRX_ACTIVE_IN_MYSQL; } if (THDVAR(thd, table_locks) && thd_in_lock_tables(thd)) { @@ -10272,7 +10300,8 @@ innobase_xa_prepare( innobase_release_stat_resources(trx); - if (trx->active_trans == 0 && trx->conc_state != TRX_NOT_STARTED) { + if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 && + trx->conc_state != TRX_NOT_STARTED) { sql_print_error("trx->active_trans == 0, but trx->conc_state != " "TRX_NOT_STARTED"); @@ -10284,7 +10313,7 @@ innobase_xa_prepare( /* We were instructed to prepare the whole transaction, or this is an SQL statement end and autocommit is on */ - ut_ad(trx->active_trans); + ut_ad(trx->active_trans & TRX_ACTIVE_IN_MYSQL); error = (int) trx_prepare_for_mysql(trx); } else { diff --git a/storage/xtradb/include/trx0trx.h b/storage/xtradb/include/trx0trx.h index 4c0ce392bcd..36f9c464c2b 100644 --- a/storage/xtradb/include/trx0trx.h +++ b/storage/xtradb/include/trx0trx.h @@ -511,9 +511,10 @@ struct trx_struct{ in that case we must flush the log in trx_commit_complete_for_mysql() */ ulint duplicates; /*!< TRX_DUP_IGNORE | TRX_DUP_REPLACE */ - ulint active_trans; /*!< 1 - if a transaction in MySQL - is active. 2 - if prepare_commit_mutex - was taken */ + ulint active_trans; /*!< TRX_ACTIVE_IN_MYSQL - set if a + transaction in MySQL is active. + TRX_ACTIVE_COMMIT_ORDERED - set if + innobase_commit_ordered has run */ ulint has_search_latch; /* TRUE if this trx has latched the search system latch in S-mode */ @@ -824,6 +825,10 @@ Multiple flags can be combined with bitwise OR. */ #define TRX_SIG_OTHER_SESS 1 /* sent by another session (which must hold rights to this) */ +/* Flag bits for trx_struct.active_trans */ +#define TRX_ACTIVE_IN_MYSQL (1<<0) +#define TRX_ACTIVE_COMMIT_ORDERED (1<<1) + /** Commit node states */ enum commit_node_state { COMMIT_NODE_SEND = 1, /*!< about to send a commit signal to From fdb2ad5fb8e7d4a743bba23967a4a464f9c29ead Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Oct 2010 15:42:06 +0200 Subject: [PATCH 03/35] MWL#116: Efficient group commit: PBXT part Implement the commit_ordered() API in PBXT, getting consistent commit ordering with other engines and binlog. Make pbxt_support_xa default in MariaDB debug build (as the bug that causes assert in MySQL is fixed in MariaDB). --- storage/pbxt/src/ha_pbxt.cc | 39 ++++++++- storage/pbxt/src/thread_xt.h | 3 + storage/pbxt/src/xaction_xt.cc | 149 +++++++++++++++++++++++---------- storage/pbxt/src/xaction_xt.h | 2 + tests/consistent_snapshot.pl | 107 +++++++++++++++++++++++ 5 files changed, 253 insertions(+), 47 deletions(-) create mode 100755 tests/consistent_snapshot.pl diff --git a/storage/pbxt/src/ha_pbxt.cc b/storage/pbxt/src/ha_pbxt.cc index ef0ae582c07..85a41ce0178 100644 --- a/storage/pbxt/src/ha_pbxt.cc +++ b/storage/pbxt/src/ha_pbxt.cc @@ -108,6 +108,9 @@ static int pbxt_end(void *p); static int pbxt_panic(handlerton *hton, enum ha_panic_function flag); static void pbxt_drop_database(handlerton *hton, char *path); static int pbxt_close_connection(handlerton *hton, THD* thd); +#ifdef MARIADB_BASE_VERSION +static void pbxt_commit_ordered(handlerton *hton, THD *thd, bool all); +#endif static int pbxt_commit(handlerton *hton, THD *thd, bool all); static int pbxt_rollback(handlerton *hton, THD *thd, bool all); static int pbxt_prepare(handlerton *hton, THD *thd, bool all); @@ -1147,6 +1150,9 @@ static int pbxt_init(void *p) pbxt_hton->state = SHOW_OPTION_YES; pbxt_hton->db_type = DB_TYPE_PBXT; // Wow! I have my own! pbxt_hton->close_connection = pbxt_close_connection; /* close_connection, cleanup thread related data. */ +#ifdef MARIADB_BASE_VERSION + pbxt_hton->commit_ordered = pbxt_commit_ordered; +#endif pbxt_hton->commit = pbxt_commit; /* commit */ pbxt_hton->rollback = pbxt_rollback; /* rollback */ if (pbxt_support_xa) { @@ -1484,6 +1490,29 @@ static int pbxt_start_consistent_snapshot(handlerton *hton, THD *thd) return err; } +#ifdef MARIADB_BASE_VERSION +/* + * Quickly commit the transaction to memory and make it visible to others. + * The remaining part of commit will happen later, in pbxt_commit(). + */ +static void pbxt_commit_ordered(handlerton *hton, THD *thd, bool all) +{ + XTThreadPtr self; + + if ((self = (XTThreadPtr) *thd_ha_data(thd, hton))) { + XT_PRINT2(self, "%s pbxt_commit_ordered all=%d\n", all ? "END CONN XACT" : "END STAT", all); + + if (self->st_xact_data) { + if (all || self->st_auto_commit) { + self->st_commit_ordered = TRUE; + self->st_writer = self->st_xact_writer; + self->st_delayed_error= !xt_xn_commit_fast(self, self->st_writer); + } + } + } +} +#endif + /* * Commit the PBXT transaction of the given thread. * thd is the MySQL thread structure. @@ -1512,7 +1541,13 @@ static int pbxt_commit(handlerton *hton, THD *thd, bool all) if (all || self->st_auto_commit) { XT_PRINT0(self, "xt_xn_commit in pbxt_commit\n"); - if (!xt_xn_commit(self)) + if (self->st_commit_ordered) { + self->st_commit_ordered = FALSE; + err = !xt_xn_commit_slow(self, self->st_writer) || self->st_delayed_error; + } else { + err = !xt_xn_commit(self); + } + if (err) err = xt_ha_pbxt_thread_error_for_mysql(thd, self, FALSE); } } @@ -6064,7 +6099,7 @@ static MYSQL_SYSVAR_INT(max_threads, pbxt_max_threads, NULL, NULL, 0, 0, 20000, 1); #endif -#ifndef DEBUG +#if !defined(DEBUG) || defined(MARIADB_BASE_VERSION) static MYSQL_SYSVAR_BOOL(support_xa, pbxt_support_xa, PLUGIN_VAR_OPCMDARG, "Enable PBXT support for the XA two-phase commit, default is enabled", diff --git a/storage/pbxt/src/thread_xt.h b/storage/pbxt/src/thread_xt.h index a07f7b7ae01..282df46a5d5 100644 --- a/storage/pbxt/src/thread_xt.h +++ b/storage/pbxt/src/thread_xt.h @@ -299,6 +299,9 @@ typedef struct XTThread { xtBool st_stat_ended; /* TRUE if the statement was ended. */ xtBool st_stat_trans; /* TRUE if a statement transaction is running (started on UPDATE). */ xtBool st_stat_modify; /* TRUE if the statement is an INSERT/UPDATE/DELETE */ + xtBool st_commit_ordered; /* TRUE if we have run commit_ordered() */ + xtBool st_delayed_error; /* TRUE if we got an error in commit_ordered() */ + xtBool st_writer; /* Copy of thread->st_xact_writer (which is clobbered by xlog_append()) */ #ifdef XT_IMPLEMENT_NO_ACTION XTBasicListRec st_restrict_list; /* These records have been deleted and should have no reference. */ #endif diff --git a/storage/pbxt/src/xaction_xt.cc b/storage/pbxt/src/xaction_xt.cc index 48abc5d2b66..c5f07eedabf 100644 --- a/storage/pbxt/src/xaction_xt.cc +++ b/storage/pbxt/src/xaction_xt.cc @@ -1287,27 +1287,61 @@ xtPublic xtBool xt_xn_begin(XTThreadPtr self) return OK; } -static xtBool xn_end_xact(XTThreadPtr thread, u_int status) +static void xn_end_release_locks(XTThreadPtr thread) +{ + XTXactDataPtr xact = thread->st_xact_data; + XTDatabaseHPtr db = thread->st_database; + ASSERT_NS(xact); + + /* {REMOVE-LOCKS} Drop locks if you have any: */ + thread->st_lock_list.xt_remove_all_locks(db, thread); + + /* Do this afterwards to make sure the sweeper + * does not cleanup transactions start cleaning up + * before any transactions that were waiting for + * this transaction have completed! + */ + xact->xd_end_xn_id = db->db_xn_curr_id; + + /* Now you can sweep! */ + xact->xd_flags |= XT_XN_XAC_SWEEP; +} + +/* The commit is split into two phases: one "fast" for MariaDB commit_ordered(), + * and one "slow" for commit(). When not using internal 2pc, there is only one + * call combining both phases. + */ + +enum { + XN_END_PHASE_FAST = 1, + XN_END_PHASE_SLOW = 2, + XN_END_PHASE_BOTH = 3 +}; + +static xtBool xn_end_xact(XTThreadPtr thread, u_int status, xtBool writer, int phase) { XTXactDataPtr xact; xtBool ok = TRUE; + xtBool err; ASSERT_NS(thread->st_xact_data); if ((xact = thread->st_xact_data)) { XTDatabaseHPtr db = thread->st_database; xtXactID xn_id = xact->xd_start_xn_id; - xtBool writer; - if ((writer = thread->st_xact_writer)) { + if (writer) { /* The transaction wrote something: */ XTXactEndEntryDRec entry; xtWord4 sum; - sum = XT_CHECKSUM4_XACT(xn_id) ^ XT_CHECKSUM4_XACT(0); - entry.xe_status_1 = status; - entry.xe_checksum_1 = XT_CHECKSUM_1(sum); - XT_SET_DISK_4(entry.xe_xact_id_4, xn_id); - XT_SET_DISK_4(entry.xe_not_used_4, 0); + if (phase & XN_END_PHASE_FAST) + { + sum = XT_CHECKSUM4_XACT(xn_id) ^ XT_CHECKSUM4_XACT(0); + entry.xe_status_1 = status; + entry.xe_checksum_1 = XT_CHECKSUM_1(sum); + XT_SET_DISK_4(entry.xe_xact_id_4, xn_id); + XT_SET_DISK_4(entry.xe_not_used_4, 0); + } #ifdef XT_IMPLEMENT_NO_ACTION /* This will check any resticts that have been delayed to the end of the statement. */ @@ -1319,20 +1353,35 @@ static xtBool xn_end_xact(XTThreadPtr thread, u_int status) } #endif - /* Flush the data log: */ - if (!thread->st_dlog_buf.dlb_flush_log(TRUE, thread)) { + /* Flush the data log (in the "fast" case we already did it in prepare: */ + if ((phase & XN_END_PHASE_SLOW) && !thread->st_dlog_buf.dlb_flush_log(TRUE, thread)) { ok = FALSE; status = XT_LOG_ENT_ABORT; } /* Write and flush the transaction log: */ - if (!xt_xlog_log_data(thread, sizeof(XTXactEndEntryDRec), (XTXactLogBufferDPtr) &entry, xt_db_flush_log_at_trx_commit)) { + if (phase == XN_END_PHASE_FAST) { + /* Fast phase, delay any write or flush to later. */ + err = !xt_xlog_log_data(thread, sizeof(XTXactEndEntryDRec), (XTXactLogBufferDPtr) &entry, XT_XLOG_NO_WRITE_NO_FLUSH); + } else if (phase == XN_END_PHASE_SLOW) { + /* We already appended the commit record in the fast phase. + * Now just call with empty record to ensure we write/flush + * the log as needed for this commit. + */ + err = !xt_xlog_log_data(thread, 0, NULL, xt_db_flush_log_at_trx_commit); + } else /* phase == XN_END_PHASE_BOTH */ { + /* Both phases at once, append commit record and write/flush normally. */ + ASSERT_NS(phase == XN_END_PHASE_BOTH); + err = !xt_xlog_log_data(thread, sizeof(XTXactEndEntryDRec), (XTXactLogBufferDPtr) &entry, xt_db_flush_log_at_trx_commit); + } + + if (err) { ok = FALSE; status = XT_LOG_ENT_ABORT; /* Make sure this is done, if we failed to log * the transction end! */ - if (thread->st_xact_writer) { + if (writer) { /* Adjust this in case of error, but don't forget * to lock! */ @@ -1347,46 +1396,46 @@ static xtBool xn_end_xact(XTThreadPtr thread, u_int status) } } - /* Setting this flag completes the transaction, - * Do this before we release the locks, because - * the unlocked transactions expect the - * transaction they are waiting for to be - * gone! - */ - xact->xd_end_time = ++db->db_xn_end_time; - if (status == XT_LOG_ENT_COMMIT) { - thread->st_statistics.st_commits++; - xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); - } - else { - thread->st_statistics.st_rollbacks++; - xact->xd_flags |= XT_XN_XAC_ENDED; + if (phase & XN_END_PHASE_FAST) { + /* Setting this flag completes the transaction, + * Do this before we release the locks, because + * the unlocked transactions expect the + * transaction they are waiting for to be + * gone! + */ + xact->xd_end_time = ++db->db_xn_end_time; + if (status == XT_LOG_ENT_COMMIT) { + thread->st_statistics.st_commits++; + xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); + } + else { + thread->st_statistics.st_rollbacks++; + xact->xd_flags |= XT_XN_XAC_ENDED; + } } - /* {REMOVE-LOCKS} Drop locks is you have any: */ - thread->st_lock_list.xt_remove_all_locks(db, thread); - - /* Do this afterwards to make sure the sweeper - * does not cleanup transactions start cleaning up - * before any transactions that were waiting for - * this transaction have completed! + /* Be as fast as possible in the "fast" path, as we want to be as + * fast as possible here (we will release slow locks immediately + * after in the "slow" part). + * ToDo: If we ran the fast part, the slow part could release locks + * _before_ fsync(), rather than after. */ - xact->xd_end_xn_id = db->db_xn_curr_id; + if (!(phase & XN_END_PHASE_SLOW)) + return ok; - /* Now you can sweep! */ - xact->xd_flags |= XT_XN_XAC_SWEEP; + xn_end_release_locks(thread); } else { /* Read-only transaction can be removed, immediately */ - xact->xd_end_time = ++db->db_xn_end_time; - xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); + if (phase & XN_END_PHASE_FAST) { + xact->xd_end_time = ++db->db_xn_end_time; + xact->xd_flags |= (XT_XN_XAC_COMMITTED | XT_XN_XAC_ENDED); - /* Drop locks is you have any: */ - thread->st_lock_list.xt_remove_all_locks(db, thread); + if (!(phase & XN_END_PHASE_SLOW)) + return ok; + } - xact->xd_end_xn_id = db->db_xn_curr_id; - - xact->xd_flags |= XT_XN_XAC_SWEEP; + xn_end_release_locks(thread); if (xt_xn_delete_xact(db, xn_id, thread)) { if (db->db_xn_min_ram_id == xn_id) @@ -1478,12 +1527,22 @@ static xtBool xn_end_xact(XTThreadPtr thread, u_int status) xtPublic xtBool xt_xn_commit(XTThreadPtr thread) { - return xn_end_xact(thread, XT_LOG_ENT_COMMIT); + return xn_end_xact(thread, XT_LOG_ENT_COMMIT, thread->st_xact_writer, XN_END_PHASE_BOTH); +} + +xtPublic xtBool xt_xn_commit_fast(XTThreadPtr thread, xtBool writer) +{ + return xn_end_xact(thread, XT_LOG_ENT_COMMIT, writer, XN_END_PHASE_FAST); +} + +xtPublic xtBool xt_xn_commit_slow(XTThreadPtr thread, xtBool writer) +{ + return xn_end_xact(thread, XT_LOG_ENT_COMMIT, writer, XN_END_PHASE_SLOW); } xtPublic xtBool xt_xn_rollback(XTThreadPtr thread) { - return xn_end_xact(thread, XT_LOG_ENT_ABORT); + return xn_end_xact(thread, XT_LOG_ENT_ABORT, thread->st_xact_writer, XN_END_PHASE_BOTH); } xtPublic xtBool xt_xn_log_tab_id(XTThreadPtr self, xtTableID tab_id) diff --git a/storage/pbxt/src/xaction_xt.h b/storage/pbxt/src/xaction_xt.h index e679a0f38f0..cd350200506 100644 --- a/storage/pbxt/src/xaction_xt.h +++ b/storage/pbxt/src/xaction_xt.h @@ -193,6 +193,8 @@ void xt_wakeup_sweeper(struct XTDatabase *db); xtBool xt_xn_begin(struct XTThread *self); xtBool xt_xn_commit(struct XTThread *self); +xtBool xt_xn_commit_fast(struct XTThread *self, xtBool writer); +xtBool xt_xn_commit_slow(struct XTThread *self, xtBool writer); xtBool xt_xn_rollback(struct XTThread *self); xtBool xt_xn_log_tab_id(struct XTThread *self, xtTableID tab_id); int xt_xn_status(struct XTOpenTable *ot, xtXactID xn_id, xtRecordID rec_id); diff --git a/tests/consistent_snapshot.pl b/tests/consistent_snapshot.pl new file mode 100755 index 00000000000..9e53eaea6a1 --- /dev/null +++ b/tests/consistent_snapshot.pl @@ -0,0 +1,107 @@ +#! /usr/bin/perl + +# Test START TRANSACTION WITH CONSISTENT SNAPSHOT. +# With MWL#116, this is implemented so it is actually consistent. + +use strict; +use warnings; + +use DBI; + +my $UPDATERS= 10; +my $READERS= 5; + +my $ROWS= 50; +my $DURATION= 20; + +my $stop_time= time() + $DURATION; + +sub my_connect { + my $dbh= DBI->connect("dbi:mysql:mysql_socket=/tmp/mysql.sock;database=test", + "root", undef, { RaiseError=>1, PrintError=>0, AutoCommit=>0}); + $dbh->do("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"); + $dbh->do("SET SESSION autocommit = 0"); + return $dbh; +} + +sub my_setup { + my $dbh= my_connect(); + + $dbh->do("DROP TABLE IF EXISTS test_consistent_snapshot1, test_consistent_snapshot2"); + $dbh->do(<do(<do("INSERT INTO test_consistent_snapshot1 VALUES (?, ?)", undef, + $i, $value); + $dbh->do("INSERT INTO test_consistent_snapshot2 VALUES (?, ?)", undef, + $i, -$value); + } + $dbh->commit(); + $dbh->disconnect(); +} + +sub my_updater { + my $dbh= my_connect(); + + while (time() < $stop_time) { + my $i1= int(rand()*$ROWS); + my $i2= int(rand()*$ROWS); + my $v= int(rand()*99)-49; + $dbh->do("UPDATE test_consistent_snapshot1 SET b = b + ? WHERE a = ?", + undef, $v, $i1); + $dbh->do("UPDATE test_consistent_snapshot2 SET b = b - ? WHERE a = ?", + undef, $v, $i2); + $dbh->commit(); + } + + $dbh->disconnect(); + exit(0); +} + +sub my_reader { + my $dbh= my_connect(); + + my $iteration= 0; + while (time() < $stop_time) { + $dbh->do("START TRANSACTION WITH CONSISTENT SNAPSHOT"); + my $s1= $dbh->selectrow_arrayref("SELECT SUM(b) FROM test_consistent_snapshot1"); + $s1= $s1->[0]; + my $s2= $dbh->selectrow_arrayref("SELECT SUM(b) FROM test_consistent_snapshot2"); + $s2= $s2->[0]; + $dbh->commit(); + if ($s1 + $s2 != 0) { + print STDERR "Found inconsistency, s1=$s1 s2=$s2 iteration=$iteration\n"; + last; + } + ++$iteration; + } + + $dbh->disconnect(); + exit(0); +} + +my_setup(); + +for (1 .. $UPDATERS) { + fork() || my_updater(); +} + +for (1 .. $READERS) { + fork() || my_reader(); +} + +waitpid(-1, 0) for (1 .. ($UPDATERS + $READERS)); + +print "All checks done\n"; From ffa7e0fabe50ceb00d2d64f4806fb87f9f607b3a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Oct 2010 11:25:06 +0200 Subject: [PATCH 04/35] MWL#116: Efficient group commit: Fix bug that binlog pos stored by XtraDB during commit was wrong when more than one commit in group. Now the actual binlog position for each commit is stored in THD, and XtraDB can fetch the correct value from within commit_ordered() or commit(). mysql-test/r/group_commit_binlog_pos.result: Test case for XtraDB binlog position. mysql-test/t/group_commit_binlog_pos-master.opt: Test case for XtraDB binlog position. mysql-test/t/group_commit_binlog_pos.test: Test case for XtraDB binlog position. sql/log.cc: Save binlog position corresponding to commit in THD, and make accessible to storage engine. sql/sql_parse.cc: Add generic crash point for use in test cases. storage/xtradb/handler/ha_innodb.cc: Update to use new method of getting current binlog position that works with group commit. storage/xtradb/handler/ha_innodb.h: Update to use new method of getting current binlog position that works with group commit. --- mysql-test/r/group_commit_binlog_pos.result | 35 ++++++++ .../t/group_commit_binlog_pos-master.opt | 1 + mysql-test/t/group_commit_binlog_pos.test | 83 +++++++++++++++++++ sql/log.cc | 41 ++++++++- sql/sql_parse.cc | 4 + storage/xtradb/handler/ha_innodb.cc | 21 ++--- storage/xtradb/handler/ha_innodb.h | 15 ++-- 7 files changed, 175 insertions(+), 25 deletions(-) create mode 100644 mysql-test/r/group_commit_binlog_pos.result create mode 100644 mysql-test/t/group_commit_binlog_pos-master.opt create mode 100644 mysql-test/t/group_commit_binlog_pos.test diff --git a/mysql-test/r/group_commit_binlog_pos.result b/mysql-test/r/group_commit_binlog_pos.result new file mode 100644 index 00000000000..0d7c23cbbbd --- /dev/null +++ b/mysql-test/r/group_commit_binlog_pos.result @@ -0,0 +1,35 @@ +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES (0); +SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont EXECUTE 3"; +INSERT INTO t1 VALUES (1); +SET DEBUG_SYNC= "now WAIT_FOR con1_waiting"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con2_queued"; +INSERT INTO t1 VALUES (2); +SET DEBUG_SYNC= "now WAIT_FOR con2_queued"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con3_queued"; +INSERT INTO t1 VALUES (3); +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SELECT * FROM t1 ORDER BY a; +a +0 +1 +2 +SET SESSION debug="+d,crash_dispatch_command_before"; +SELECT 1; +ERROR HY000: Lost connection to MySQL server during query +ERROR HY000: Lost connection to MySQL server during query +ERROR HY000: Lost connection to MySQL server during query +SELECT * FROM t1 ORDER BY a; +a +0 +1 +2 +3 +InnoDB: Last MySQL binlog file position 0 767, file name ./master-bin.000001 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/t/group_commit_binlog_pos-master.opt b/mysql-test/t/group_commit_binlog_pos-master.opt new file mode 100644 index 00000000000..425fda95086 --- /dev/null +++ b/mysql-test/t/group_commit_binlog_pos-master.opt @@ -0,0 +1 @@ +--skip-stack-trace --skip-core-file diff --git a/mysql-test/t/group_commit_binlog_pos.test b/mysql-test/t/group_commit_binlog_pos.test new file mode 100644 index 00000000000..ad1a0e4b508 --- /dev/null +++ b/mysql-test/t/group_commit_binlog_pos.test @@ -0,0 +1,83 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc +# Need DBUG to crash the server intentionally +--source include/have_debug.inc +# Don't test this under valgrind, memory leaks will occur as we crash +--source include/not_valgrind.inc + +# XtraDB stores the binlog position corresponding to the last commit, and +# prints it during crash recovery. +# Test that we get the correct position when we group commit several +# transactions together. + +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES (0); + +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); +connect(con3,localhost,root,,); + +# Queue up three commits for group commit. + +connection con1; +SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont EXECUTE 3"; +send INSERT INTO t1 VALUES (1); + +connection con2; +SET DEBUG_SYNC= "now WAIT_FOR con1_waiting"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con2_queued"; +send INSERT INTO t1 VALUES (2); + +connection con3; +SET DEBUG_SYNC= "now WAIT_FOR con2_queued"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con3_queued"; +send INSERT INTO t1 VALUES (3); + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +# At this point, no transactions are committed. +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +# At this point, 1 transaction is committed. +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; + +# At this point, 2 transactions are committed. +SELECT * FROM t1 ORDER BY a; + +connection con2; +reap; + +# Now crash the server with 1+2 in-memory committed, 3 only prepared. +connection default; +system echo wait-group_commit_binlog_pos.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; +SET SESSION debug="+d,crash_dispatch_command_before"; +--error 2013 +SELECT 1; + +connection con1; +--error 2013 +reap; +connection con3; +--error 2013 +reap; + +system echo restart-group_commit_binlog_pos.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; + +connection default; +--enable_reconnect +--source include/wait_until_connected_again.inc + +# Crash recovery should recover all three transactions. +SELECT * FROM t1 ORDER BY a; + +# Check that the binlog position reported by InnoDB is the correct one +# for the end of the second transaction (as can be checked with +# mysqlbinlog). +let $MYSQLD_DATADIR= `SELECT @@datadir`; +--exec grep 'InnoDB: Last MySQL binlog file position' $MYSQLD_DATADIR/../../log/mysqld.1.err | tail -1 + +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/sql/log.cc b/sql/log.cc index 4e489662079..d99f04d4425 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -155,7 +155,7 @@ class binlog_trx_data { public: binlog_trx_data() : at_least_one_stmt_committed(0), incident(FALSE), m_pending(0), - before_stmt_pos(MY_OFF_T_UNDEF), using_xa(0) + before_stmt_pos(MY_OFF_T_UNDEF), using_xa(0), commit_bin_log_file_pos(0) { trans_log.end_of_file= max_binlog_cache_size; (void) my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, @@ -218,6 +218,7 @@ public: incident= FALSE; trans_log.end_of_file= max_binlog_cache_size; using_xa= FALSE; + commit_bin_log_file_pos= 0; DBUG_ASSERT(empty()); } @@ -297,6 +298,11 @@ public: /* Mutex and condition for wakeup after group commit. */ pthread_mutex_t LOCK_group_commit; pthread_cond_t COND_group_commit; + /* + Binlog position after current commit, available to storage engines during + commit() and commit_ordered(). + */ + ulonglong commit_bin_log_file_pos; }; handlerton *binlog_hton; @@ -5170,6 +5176,8 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) write_count++; } + current->commit_bin_log_file_pos= + log_file.pos_in_file + (log_file.write_pos - log_file.write_buffer); if (current->end_event->get_type_code() == XID_EVENT) xid_count++; } @@ -6005,6 +6013,7 @@ int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, ++num_group_commits; do { + DEBUG_SYNC(thd, "commit_loop_entry_commit_ordered"); ++num_commits; if (!current->xid_error) run_commit_ordered(current->thd, current->all); @@ -6813,6 +6822,36 @@ ulonglong mysql_bin_log_file_pos(void) { return (ulonglong) mysql_bin_log.get_log_file()->pos_in_file; } +/* + Get the current position of the MySQL binlog for transaction currently being + committed. + + This is valid to call from within storage engine commit_ordered() and + commit() methods only. + + Since it stores the position inside THD, it is safe to call without any + locking. + + Note that currently the binlog file name is not stored inside THD, but this + is still safe as it can only change when the log is rotated, and we never + rotate the binlog while commits are pending inside storage engines. +*/ +void +mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) +{ + binlog_trx_data *const trx_data= + (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + if (trx_data) + { + *out_pos= trx_data->commit_bin_log_file_pos; + *out_file= mysql_bin_log.get_log_fname(); + } + else + { + *out_pos= NULL; + *out_file= NULL; + } +} #endif /* INNODB_COMPATIBILITY_HOOKS */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index caec049c244..8dee0155f2b 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -999,6 +999,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, DBUG_ENTER("dispatch_command"); DBUG_PRINT("info", ("command: %d", command)); + DBUG_EXECUTE_IF("crash_dispatch_command_before", + { DBUG_PRINT("crash_dispatch_command_before", ("now")); + DBUG_ABORT(); }); + thd->command=command; /* Commands which always take a long time are logged into diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index d9abb5ae032..b05670e6c6e 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -2716,8 +2716,10 @@ static void innobase_commit_ordered_2( /*============*/ - trx_t* trx) /*!< in: Innodb transaction */ + trx_t* trx, /*!< in: Innodb transaction */ + THD* thd) /*!< in: MySQL thread handle */ { + ulonglong tmp_pos; DBUG_ENTER("innobase_commit_ordered"); /* We need current binlog position for ibbackup to work. @@ -2741,17 +2743,8 @@ retry: } } - /* The following calls to read the MySQL binary log - file name and the position return consistent results: - 1) We use commit_ordered() to get same commit order - in InnoDB as in binary log. - 2) A MySQL log file rotation cannot happen because - MySQL protects against this by having a counter of - transactions in prepared state and it only allows - a rotation when the counter drops to zero. See - LOCK_prep_xids and COND_prep_xids in log.cc. */ - trx->mysql_log_file_name = mysql_bin_log_file_name(); - trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos(); + mysql_bin_log_commit_pos(thd, &tmp_pos, &(trx->mysql_log_file_name)); + trx->mysql_log_offset = (ib_int64_t) tmp_pos; /* Don't do write + flush right now. For group commit to work we want to do the flush in the innobase_commit() @@ -2817,7 +2810,7 @@ innobase_commit_ordered( DBUG_ASSERT(all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); - innobase_commit_ordered_2(trx); + innobase_commit_ordered_2(trx, thd); trx->active_trans |= TRX_ACTIVE_COMMIT_ORDERED; @@ -2881,7 +2874,7 @@ innobase_commit( /* Run the fast part of commit if we did not already. */ if ((trx->active_trans & TRX_ACTIVE_COMMIT_ORDERED) == 0) { - innobase_commit_ordered_2(trx); + innobase_commit_ordered_2(trx, thd); } /* We were instructed to commit the whole transaction, or diff --git a/storage/xtradb/handler/ha_innodb.h b/storage/xtradb/handler/ha_innodb.h index 04224277deb..20363b19abd 100644 --- a/storage/xtradb/handler/ha_innodb.h +++ b/storage/xtradb/handler/ha_innodb.h @@ -239,16 +239,6 @@ LEX_STRING *thd_query_string(MYSQL_THD thd); char **thd_query(MYSQL_THD thd); #endif -/** Get the file name of the MySQL binlog. - * @return the name of the binlog file - */ -const char* mysql_bin_log_file_name(void); - -/** Get the current position of the MySQL binlog. - * @return byte offset from the beginning of the binlog - */ -ulonglong mysql_bin_log_file_pos(void); - /** Check if a user thread is a replication slave thread @param thd user thread @@ -289,6 +279,11 @@ bool thd_binlog_filter_ok(const MYSQL_THD thd); #endif /* MYSQL_VERSION_ID > 50140 */ } +/** Get the file name and position of the MySQL binlog corresponding to the + * current commit. + */ +extern void mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file); + typedef struct trx_struct trx_t; /********************************************************************//** @file handler/ha_innodb.h From dcb111ff6e8deb077530a6e0779277afc3eab7ff Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Oct 2010 11:43:05 +0200 Subject: [PATCH 05/35] MWL#116: Efficient group commit: Fix memory leak. --- sql/log.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/log.cc b/sql/log.cc index d99f04d4425..6ec186b3786 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -166,6 +166,7 @@ public: ~binlog_trx_data() { DBUG_ASSERT(pending() == NULL); + (void) pthread_mutex_destroy(&LOCK_group_commit); close_cached_file(&trans_log); } From 45131a5b103d5a8330c50c8b9c0a7922fe22298e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Oct 2010 10:51:02 +0200 Subject: [PATCH 06/35] MWL#116: Efficient group commit: Fix assertion by moving init/deinit out of constructor in global static object. --- sql/log.cc | 14 +++++++++++++- sql/log.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sql/log.cc b/sql/log.cc index 6ec186b3786..85f47d3edf9 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -5927,12 +5927,22 @@ int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, TC_LOG_group_commit::TC_LOG_group_commit() : num_commits(0), num_group_commits(0) +{ +} + +TC_LOG_group_commit::~TC_LOG_group_commit() +{ +} + +void +TC_LOG_group_commit::init() { my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, "LOCK_group_commit", MYF(0)); } -TC_LOG_group_commit::~TC_LOG_group_commit() +void +TC_LOG_group_commit::deinit() { pthread_mutex_destroy(&LOCK_group_commit); } @@ -6591,6 +6601,7 @@ int TC_LOG_BINLOG::open(const char *opt_name) DBUG_ASSERT(total_ha_2pc > 1); DBUG_ASSERT(opt_name && opt_name[0]); + TC_LOG_group_commit::init(); pthread_mutex_init(&LOCK_prep_xids, MY_MUTEX_INIT_FAST); pthread_cond_init (&COND_prep_xids, 0); @@ -6674,6 +6685,7 @@ void TC_LOG_BINLOG::close() DBUG_ASSERT(prepared_xids==0); pthread_mutex_destroy(&LOCK_prep_xids); pthread_cond_destroy (&COND_prep_xids); + TC_LOG_group_commit::deinit(); } /* diff --git a/sql/log.h b/sql/log.h index 7a6b7f070b5..f43ae433c8a 100644 --- a/sql/log.h +++ b/sql/log.h @@ -150,6 +150,8 @@ class TC_LOG_group_commit: public TC_LOG_queued public: TC_LOG_group_commit(); ~TC_LOG_group_commit(); + void init(); + void deinit(); int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered); From f0707b38ea64688735183f2e43927857236ca2ed Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 23 Oct 2010 11:50:02 +0200 Subject: [PATCH 07/35] MWL#116: Efficient group commit: Some fixes for test failures. client/mysqltest.cc: Apply Serg's work-around for MySQL BUG#57491. mysql-test/r/group_commit_binlog_pos.result: Accept different ways the server crash is detected. mysql-test/t/group_commit_binlog_pos.test: Accept different ways the server crash is detected. Skip in row-based binlogging, as otherwise the binlog offsets will be different. --- client/mysqltest.cc | 4 ++++ mysql-test/r/group_commit_binlog_pos.result | 6 +++--- mysql-test/t/group_commit_binlog_pos.test | 8 +++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index e4981bec997..78ae9a63f87 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -717,6 +717,10 @@ void handle_no_error(struct st_command*); #ifdef EMBEDDED_LIBRARY +/* workaround for MySQL BUG#57491 */ +#undef MY_WME +#define MY_WME 0 + /* attributes of the query thread */ pthread_attr_t cn_thd_attrib; diff --git a/mysql-test/r/group_commit_binlog_pos.result b/mysql-test/r/group_commit_binlog_pos.result index 0d7c23cbbbd..a0bb5ee2d8e 100644 --- a/mysql-test/r/group_commit_binlog_pos.result +++ b/mysql-test/r/group_commit_binlog_pos.result @@ -21,9 +21,9 @@ a 2 SET SESSION debug="+d,crash_dispatch_command_before"; SELECT 1; -ERROR HY000: Lost connection to MySQL server during query -ERROR HY000: Lost connection to MySQL server during query -ERROR HY000: Lost connection to MySQL server during query +Got one of the listed errors +Got one of the listed errors +Got one of the listed errors SELECT * FROM t1 ORDER BY a; a 0 diff --git a/mysql-test/t/group_commit_binlog_pos.test b/mysql-test/t/group_commit_binlog_pos.test index ad1a0e4b508..f8c5e719f11 100644 --- a/mysql-test/t/group_commit_binlog_pos.test +++ b/mysql-test/t/group_commit_binlog_pos.test @@ -1,6 +1,8 @@ --source include/have_debug_sync.inc --source include/have_innodb.inc --source include/have_log_bin.inc +--source include/have_binlog_format_mixed_or_statement.inc + # Need DBUG to crash the server intentionally --source include/have_debug.inc # Don't test this under valgrind, memory leaks will occur as we crash @@ -54,14 +56,14 @@ reap; connection default; system echo wait-group_commit_binlog_pos.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; SET SESSION debug="+d,crash_dispatch_command_before"; ---error 2013 +--error 2006,2013 SELECT 1; connection con1; ---error 2013 +--error 2006,2013 reap; connection con3; ---error 2013 +--error 2006,2013 reap; system echo restart-group_commit_binlog_pos.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; From aa23fe7dc1f8c215fb7cbff7be9e26255ad0ef93 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 Oct 2010 12:45:34 +0200 Subject: [PATCH 08/35] MWL#116: Replace atomic queue with simple mutex locking for non-transactional binlog writes. Also add missing destroy of pthread_cond. --- sql/log.cc | 69 ++++++++++++++++++++---------------------------------- sql/log.h | 6 ++--- 2 files changed, 28 insertions(+), 47 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 85f47d3edf9..3ee848cde19 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -158,15 +158,15 @@ public: before_stmt_pos(MY_OFF_T_UNDEF), using_xa(0), commit_bin_log_file_pos(0) { trans_log.end_of_file= max_binlog_cache_size; - (void) my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, - "LOCK_group_commit", MYF(0)); + (void) my_pthread_mutex_init(&LOCK_binlog_participant, MY_MUTEX_INIT_SLOW, + "LOCK_binlog_participant", MYF(0)); (void) pthread_cond_init(&COND_group_commit, 0); } ~binlog_trx_data() { DBUG_ASSERT(pending() == NULL); - (void) pthread_mutex_destroy(&LOCK_group_commit); + (void) pthread_mutex_destroy(&LOCK_binlog_participant); close_cached_file(&trans_log); } @@ -274,13 +274,13 @@ public: /* Flag set true when group commit for this transaction is finished; used with pthread_cond_wait() to wait until commit is done. - This flag is protected by LOCK_group_commit. + This flag is protected by LOCK_binlog_participant. */ bool done; /* Flag set if this transaction is the group commit leader that will handle the actual writing to the binlog. - This flag is protected by LOCK_group_commit. + This flag is protected by LOCK_binlog_participant. */ bool group_commit_leader; /* @@ -297,7 +297,7 @@ public: Log_event *end_event; Log_event *incident_event; /* Mutex and condition for wakeup after group commit. */ - pthread_mutex_t LOCK_group_commit; + pthread_mutex_t LOCK_binlog_participant; pthread_cond_t COND_group_commit; /* Binlog position after current commit, available to storage engines during @@ -4994,8 +4994,13 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) the commit and wake them up. */ - pthread_mutex_lock(&trx_data->LOCK_group_commit); - const binlog_trx_data *orig_queue= atomic_enqueue_trx(trx_data); + pthread_mutex_lock(&trx_data->LOCK_binlog_participant); + + pthread_mutex_lock(&LOCK_queue); + binlog_trx_data *orig_queue= group_commit_queue; + trx_data->next= orig_queue; + group_commit_queue= trx_data; + pthread_mutex_unlock(&LOCK_queue); if (orig_queue != NULL) { @@ -5006,7 +5011,7 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) else { trx_data->group_commit_leader= TRUE; - pthread_mutex_unlock(&trx_data->LOCK_group_commit); + pthread_mutex_unlock(&trx_data->LOCK_binlog_participant); trx_group_commit_leader(NULL); } @@ -5020,19 +5025,19 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) this thread in the group commit once the log is obtained. So here we put ourself in the queue and wait to be signalled that the group commit is done. - Note that this function must be called with the trs_data->LOCK_group_commit + Note that this function must be called with trx_data->LOCK_binlog_participant locked; the mutex will be released before return. */ void MYSQL_BIN_LOG::trx_group_commit_participant(binlog_trx_data *trx_data) { - safe_mutex_assert_owner(&trx_data->LOCK_group_commit); + safe_mutex_assert_owner(&trx_data->LOCK_binlog_participant); /* Wait until trx_data.done == true and woken up by the leader. */ while (!trx_data->done) pthread_cond_wait(&trx_data->COND_group_commit, - &trx_data->LOCK_group_commit); - pthread_mutex_unlock(&trx_data->LOCK_group_commit); + &trx_data->LOCK_binlog_participant); + pthread_mutex_unlock(&trx_data->LOCK_binlog_participant); } bool @@ -5131,7 +5136,10 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) it to the existing one. Note that there is no ordering defined between transactional and non-transactional commits. */ - binlog_trx_data *current= atomic_grab_trx_queue(); + pthread_mutex_lock(&LOCK_queue); + binlog_trx_data *current= group_commit_queue; + group_commit_queue= NULL; + pthread_mutex_unlock(&LOCK_queue); binlog_trx_data *xtra_queue= NULL; while (current) { @@ -5230,18 +5238,19 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) for (current= xtra_queue; current != NULL; current= current->next) { /* - Note that we need to take LOCK_group_commit even in the case of a leader! + Note that we need to take LOCK_binlog_participant even in the case of a + leader! Otherwise there is a race between setting and testing the group_commit_leader flag. */ - pthread_mutex_lock(¤t->LOCK_group_commit); + pthread_mutex_lock(¤t->LOCK_binlog_participant); if (!current->group_commit_leader) { current->done= true; pthread_cond_signal(¤t->COND_group_commit); } - pthread_mutex_unlock(¤t->LOCK_group_commit); + pthread_mutex_unlock(¤t->LOCK_binlog_participant); } } @@ -5291,32 +5300,6 @@ MYSQL_BIN_LOG::write_transaction(binlog_trx_data *trx_data) return 0; } -binlog_trx_data * -MYSQL_BIN_LOG::atomic_enqueue_trx(binlog_trx_data *trx_data) -{ - my_atomic_rwlock_wrlock(&LOCK_queue); - trx_data->next= group_commit_queue; - while (!my_atomic_casptr((void **)(&group_commit_queue), - (void **)(&trx_data->next), - trx_data)) - ; - my_atomic_rwlock_wrunlock(&LOCK_queue); - return trx_data->next; -} - -binlog_trx_data * -MYSQL_BIN_LOG::atomic_grab_trx_queue() -{ - my_atomic_rwlock_wrlock(&LOCK_queue); - binlog_trx_data *queue= group_commit_queue; - while (!my_atomic_casptr((void **)(&group_commit_queue), - (void **)(&queue), - NULL)) - ; - my_atomic_rwlock_wrunlock(&LOCK_queue); - return queue; -} - /** Wait until we get a signal that the binary log has been updated. diff --git a/sql/log.h b/sql/log.h index f43ae433c8a..863cb188866 100644 --- a/sql/log.h +++ b/sql/log.h @@ -404,8 +404,8 @@ class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG pthread_mutex_t LOCK_index; pthread_mutex_t LOCK_prep_xids; /* - Mutex to protect the queue of transactions waiting to participate in group - commit. (Only used on platforms without native atomic operations). + Mutex to protect the queue of non-transactional binlog writes waiting to + participate in group commit. */ pthread_mutex_t LOCK_queue; @@ -462,8 +462,6 @@ class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG bool write_transaction_to_binlog_events(binlog_trx_data *trx_data); void trx_group_commit_participant(binlog_trx_data *trx_data); void trx_group_commit_leader(TC_group_commit_entry *first); - binlog_trx_data *atomic_enqueue_trx(binlog_trx_data *trx_data); - binlog_trx_data *atomic_grab_trx_queue(); void mark_xid_done(); void mark_xids_active(uint xid_count); From b357fca4de07c932a32a00eb42073fdd589250fc Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 Oct 2010 12:58:17 +0200 Subject: [PATCH 09/35] MWL#116: better name for pthread_cond + add missing pthread_cond_destroy(). --- sql/log.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 3ee848cde19..49688a8b3c6 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -160,12 +160,13 @@ public: trans_log.end_of_file= max_binlog_cache_size; (void) my_pthread_mutex_init(&LOCK_binlog_participant, MY_MUTEX_INIT_SLOW, "LOCK_binlog_participant", MYF(0)); - (void) pthread_cond_init(&COND_group_commit, 0); + (void) pthread_cond_init(&COND_binlog_participant, 0); } ~binlog_trx_data() { DBUG_ASSERT(pending() == NULL); + (void) pthread_cond_destroy(&COND_binlog_participant); (void) pthread_mutex_destroy(&LOCK_binlog_participant); close_cached_file(&trans_log); } @@ -298,7 +299,7 @@ public: Log_event *incident_event; /* Mutex and condition for wakeup after group commit. */ pthread_mutex_t LOCK_binlog_participant; - pthread_cond_t COND_group_commit; + pthread_cond_t COND_binlog_participant; /* Binlog position after current commit, available to storage engines during commit() and commit_ordered(). @@ -5035,7 +5036,7 @@ MYSQL_BIN_LOG::trx_group_commit_participant(binlog_trx_data *trx_data) /* Wait until trx_data.done == true and woken up by the leader. */ while (!trx_data->done) - pthread_cond_wait(&trx_data->COND_group_commit, + pthread_cond_wait(&trx_data->COND_binlog_participant, &trx_data->LOCK_binlog_participant); pthread_mutex_unlock(&trx_data->LOCK_binlog_participant); } @@ -5248,7 +5249,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) if (!current->group_commit_leader) { current->done= true; - pthread_cond_signal(¤t->COND_group_commit); + pthread_cond_signal(¤t->COND_binlog_participant); } pthread_mutex_unlock(¤t->LOCK_binlog_participant); } From b91ad17cea8572f7cd21fb97d0b0ddce9f8b3c46 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 28 Oct 2010 12:40:42 +0200 Subject: [PATCH 10/35] MWL#116: Code simplifications for TC_LOG_MMAP. Make TC_LOG_MMAP (and TC_LOG_DUMMY) derive directly from TC_LOG, avoiding the inheritance hierarchy TC_LOG_queued->TC_LOG_unordered. Put the wakeup facility for commit_ordered() calls into the THD class. Some renaming to get better names. --- sql/log.cc | 88 +++++++++++++++++++----------------------------- sql/log.h | 85 +++++++++++++++++++++++----------------------- sql/sql_class.cc | 27 ++++++++++++--- sql/sql_class.h | 22 +++++++++--- 4 files changed, 119 insertions(+), 103 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 49688a8b3c6..e29758b7f0b 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -5778,43 +5778,12 @@ TC_LOG_queued::reverse_queue(TC_LOG_queued::TC_group_commit_entry *queue) return prev; } -void -TC_LOG_queued::group_commit_wait_for_wakeup(TC_group_commit_entry *entry) -{ - THD *thd= entry->thd; - pthread_mutex_lock(&thd->LOCK_commit_ordered); - while (!entry->group_commit_ready) - pthread_cond_wait(&thd->COND_commit_ordered, - &thd->LOCK_commit_ordered); - pthread_mutex_unlock(&thd->LOCK_commit_ordered); -} - -void -TC_LOG_queued::group_commit_wakeup_other(TC_group_commit_entry *other) -{ - THD *thd= other->thd; - pthread_mutex_lock(&thd->LOCK_commit_ordered); - other->group_commit_ready= TRUE; - pthread_cond_signal(&thd->COND_commit_ordered); - pthread_mutex_unlock(&thd->LOCK_commit_ordered); -} - -TC_LOG_unordered::TC_LOG_unordered() : group_commit_queue_busy(0) -{ - pthread_cond_init(&COND_queue_busy, 0); -} - -TC_LOG_unordered::~TC_LOG_unordered() -{ - pthread_cond_destroy(&COND_queue_busy); -} - -int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, - bool need_prepare_ordered, - bool need_commit_ordered) +int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, + bool need_commit_ordered) { int cookie; - struct TC_group_commit_entry entry; + struct commit_entry entry; bool is_group_commit_leader; LINT_INIT(is_group_commit_leader); @@ -5828,18 +5797,18 @@ int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, Must put us in queue so we can run_commit_ordered() in same sequence as we did run_prepare_ordered(). */ + thd->clear_wakeup_ready(); entry.thd= thd; - entry.group_commit_ready= false; - TC_group_commit_entry *previous_queue= group_commit_queue; + commit_entry *previous_queue= commit_ordered_queue; entry.next= previous_queue; - group_commit_queue= &entry; + commit_ordered_queue= &entry; is_group_commit_leader= (previous_queue == NULL); } pthread_mutex_unlock(&LOCK_prepare_ordered); } if (xid) - cookie= log_xid(thd, xid); + cookie= log_one_transaction(xid); else cookie= 0; @@ -5859,24 +5828,32 @@ int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, { /* The first in queue starts the ball rolling. */ pthread_mutex_lock(&LOCK_prepare_ordered); - while (group_commit_queue_busy) + while (commit_ordered_queue_busy) pthread_cond_wait(&COND_queue_busy, &LOCK_prepare_ordered); - TC_group_commit_entry *queue= group_commit_queue; - group_commit_queue= NULL; + commit_entry *queue= commit_ordered_queue; + commit_ordered_queue= NULL; /* Mark the queue busy while we bounce it from one thread to the next. */ - group_commit_queue_busy= TRUE; + commit_ordered_queue_busy= true; pthread_mutex_unlock(&LOCK_prepare_ordered); - queue= reverse_queue(queue); - DBUG_ASSERT(queue == &entry && queue->thd == thd); + /* Reverse the queue list so we get correct order. */ + commit_entry *prev= NULL; + while (queue) + { + commit_entry *next= queue->next; + queue->next= prev; + prev= queue; + queue= next; + } + DBUG_ASSERT(prev == &entry && prev->thd == thd); } else { /* Not first in queue; just wait until previous thread wakes us up. */ - group_commit_wait_for_wakeup(&entry); + thd->wait_for_wakeup_ready(); } } @@ -5890,15 +5867,15 @@ int TC_LOG_unordered::log_and_order(THD *thd, my_xid xid, bool all, if (need_prepare_ordered) { - TC_group_commit_entry *next= entry.next; + commit_entry *next= entry.next; if (next) { - group_commit_wakeup_other(next); + next->thd->signal_wakeup_ready(); } else { pthread_mutex_lock(&LOCK_prepare_ordered); - group_commit_queue_busy= FALSE; + commit_ordered_queue_busy= false; pthread_cond_signal(&COND_queue_busy); pthread_mutex_unlock(&LOCK_prepare_ordered); } @@ -5940,9 +5917,9 @@ int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, struct TC_group_commit_entry entry; bool is_group_commit_leader; + thd->clear_wakeup_ready(); entry.thd= thd; entry.all= all; - entry.group_commit_ready= false; entry.xid_error= 0; pthread_mutex_lock(&LOCK_prepare_ordered); @@ -6019,7 +5996,7 @@ int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, */ TC_group_commit_entry *next= current->next; if (current != &entry) // Don't wake up ourself - group_commit_wakeup_other(current); + current->thd->signal_wakeup_ready(); current= next; } while (current != NULL); DEBUG_SYNC(thd, "commit_after_group_run_commit_ordered"); @@ -6029,7 +6006,7 @@ int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, else { /* If not leader, just wait until leader wakes us up. */ - group_commit_wait_for_wakeup(&entry); + thd->wait_for_wakeup_ready(); } /* @@ -6181,6 +6158,7 @@ int TC_LOG_MMAP::open(const char *opt_name) pthread_mutex_init(&LOCK_pool, MY_MUTEX_INIT_FAST); pthread_cond_init(&COND_active, 0); pthread_cond_init(&COND_pool, 0); + pthread_cond_init(&COND_queue_busy, 0); inited=6; @@ -6188,6 +6166,8 @@ int TC_LOG_MMAP::open(const char *opt_name) active=pages; pool=pages+1; pool_last=pages+npages-1; + commit_ordered_queue= NULL; + commit_ordered_queue_busy= false; return 0; @@ -6293,7 +6273,7 @@ int TC_LOG_MMAP::overflow() to the position in memory where xid was logged to. */ -int TC_LOG_MMAP::log_xid(THD *thd, my_xid xid) +int TC_LOG_MMAP::log_one_transaction(my_xid xid) { int err; PAGE *p; @@ -6462,6 +6442,8 @@ void TC_LOG_MMAP::close() pthread_mutex_destroy(&LOCK_active); pthread_mutex_destroy(&LOCK_pool); pthread_cond_destroy(&COND_pool); + pthread_cond_destroy(&COND_active); + pthread_cond_destroy(&COND_queue_busy); case 5: data[0]='A'; // garble the first (signature) byte, in case my_delete fails case 4: diff --git a/sql/log.h b/sql/log.h index 863cb188866..37f5462f198 100644 --- a/sql/log.h +++ b/sql/log.h @@ -91,11 +91,6 @@ protected: THD *thd; /* This is the `all' parameter for ha_commit_trans() etc. */ bool all; - /* - Flag set true when it is time for this thread to wake up after group - commit. Used with THD::LOCK_commit_ordered and THD::COND_commit_ordered. - */ - bool group_commit_ready; /* Set by TC_LOG_group_commit::group_log_xid(), to return per-thd error and cookie. @@ -105,9 +100,6 @@ protected: TC_group_commit_entry * reverse_queue(TC_group_commit_entry *queue); - void group_commit_wait_for_wakeup(TC_group_commit_entry *entry); - void group_commit_wakeup_other(TC_group_commit_entry *other); - /* This is a queue of threads waiting for being allowed to commit. Access to the queue must be protected by LOCK_prepare_ordered. @@ -115,36 +107,6 @@ protected: TC_group_commit_entry *group_commit_queue; }; -class TC_LOG_unordered: public TC_LOG_queued -{ -public: - TC_LOG_unordered(); - ~TC_LOG_unordered(); - - int log_and_order(THD *thd, my_xid xid, bool all, - bool need_prepare_ordered, bool need_commit_ordered); - -protected: - virtual int log_xid(THD *thd, my_xid xid)=0; - -private: - /* - This flag and condition is used to reserve the queue while threads in it - each run the commit_ordered() methods one after the other. Only once the - last commit_ordered() in the queue is done can we start on a new queue - run. - - Since we start this process in the first thread in the queue and finish in - the last (and possibly different) thread, we need a condition variable for - this (we cannot unlock a mutex in a different thread than the one who - locked it). - - The condition is used together with the LOCK_prepare_ordered mutex. - */ - my_bool group_commit_queue_busy; - pthread_cond_t COND_queue_busy; -}; - class TC_LOG_group_commit: public TC_LOG_queued { public: @@ -206,18 +168,28 @@ private: pthread_mutex_t LOCK_group_commit; }; -class TC_LOG_DUMMY: public TC_LOG_unordered // use it to disable the logging +class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging { public: TC_LOG_DUMMY() {} int open(const char *opt_name) { return 0; } void close() { } - int log_xid(THD *thd, my_xid xid) { return 1; } + /* + TC_LOG_DUMMY is only used when there are <= 1 XA-capable engines, and we + only use internal XA during commit when >= 2 XA-capable engines + participate. + */ + int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, bool need_commit_ordered) + { + DBUG_ASSERT(0 /* Internal error - TC_LOG_DUMMY::log_and_order() called */); + return 1; + } void unlog(ulong cookie, my_xid xid) { } }; #ifdef HAVE_MMAP -class TC_LOG_MMAP: public TC_LOG_unordered +class TC_LOG_MMAP: public TC_LOG { public: // only to keep Sun Forte on sol9x86 happy typedef enum { @@ -238,6 +210,13 @@ class TC_LOG_MMAP: public TC_LOG_unordered pthread_cond_t cond; // to wait for a sync } PAGE; + /* List of THDs for which to invoke commit_ordered(), in order. */ + struct commit_entry + { + struct commit_entry *next; + THD *thd; + }; + char logname[FN_REFLEN]; File fd; my_off_t file_length; @@ -252,16 +231,38 @@ class TC_LOG_MMAP: public TC_LOG_unordered */ pthread_mutex_t LOCK_active, LOCK_pool, LOCK_sync; pthread_cond_t COND_pool, COND_active; + /* + Queue of threads that need to call commit_ordered(). + Access to this queue must be protected by LOCK_prepare_ordered. + */ + commit_entry *commit_ordered_queue; + /* + This flag and condition is used to reserve the queue while threads in it + each run the commit_ordered() methods one after the other. Only once the + last commit_ordered() in the queue is done can we start on a new queue + run. + + Since we start this process in the first thread in the queue and finish in + the last (and possibly different) thread, we need a condition variable for + this (we cannot unlock a mutex in a different thread than the one who + locked it). + + The condition is used together with the LOCK_prepare_ordered mutex. + */ + my_bool commit_ordered_queue_busy; + pthread_cond_t COND_queue_busy; public: TC_LOG_MMAP(): inited(0) {} int open(const char *opt_name); void close(); - int log_xid(THD *thd, my_xid xid); + int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, bool need_commit_ordered); void unlog(ulong cookie, my_xid xid); int recover(); private: + int log_one_transaction(my_xid xid); void get_active_from_pool(); int sync(); int overflow(); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7c8ed46347f..563617f0a5b 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -704,8 +704,8 @@ THD::THD() active_vio = 0; #endif pthread_mutex_init(&LOCK_thd_data, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&LOCK_commit_ordered, MY_MUTEX_INIT_FAST); - pthread_cond_init(&COND_commit_ordered, 0); + pthread_mutex_init(&LOCK_wakeup_ready, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_wakeup_ready, 0); /* Variables with default values */ proc_info="login"; @@ -1037,8 +1037,8 @@ THD::~THD() free_root(&transaction.mem_root,MYF(0)); #endif mysys_var=0; // Safety (shouldn't be needed) - pthread_cond_destroy(&COND_commit_ordered); - pthread_mutex_destroy(&LOCK_commit_ordered); + pthread_cond_destroy(&COND_wakeup_ready); + pthread_mutex_destroy(&LOCK_wakeup_ready); pthread_mutex_destroy(&LOCK_thd_data); #ifndef DBUG_OFF dbug_sentry= THD_SENTRY_GONE; @@ -4009,6 +4009,25 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, DBUG_RETURN(0); } +void +THD::wait_for_wakeup_ready() +{ + pthread_mutex_lock(&LOCK_wakeup_ready); + while (!wakeup_ready) + pthread_cond_wait(&COND_wakeup_ready, &LOCK_wakeup_ready); + pthread_mutex_unlock(&LOCK_wakeup_ready); +} + +void +THD::signal_wakeup_ready() +{ + pthread_mutex_lock(&LOCK_wakeup_ready); + wakeup_ready= true; + pthread_cond_signal(&COND_wakeup_ready); + pthread_mutex_unlock(&LOCK_wakeup_ready); +} + + bool Discrete_intervals_list::append(ulonglong start, ulonglong val, ulonglong incr) { diff --git a/sql/sql_class.h b/sql/sql_class.h index ed02504e3ab..618d6a6e089 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1447,10 +1447,6 @@ public: /* container for handler's private per-connection data */ Ha_data ha_data[MAX_HA]; - /* Mutex and condition for waking up threads after group commit. */ - pthread_mutex_t LOCK_commit_ordered; - pthread_cond_t COND_commit_ordered; - #ifndef MYSQL_CLIENT int binlog_setup_trx_data(); @@ -2380,6 +2376,14 @@ public: LEX_STRING get_invoker_user() { return invoker_user; } LEX_STRING get_invoker_host() { return invoker_host; } bool has_invoker() { return invoker_user.length > 0; } + void clear_wakeup_ready() { wakeup_ready= false; } + /* + Sleep waiting for others to wake us up with signal_wakeup_ready(). + Must call clear_wakeup_ready() before waiting. + */ + void wait_for_wakeup_ready(); + /* Wake this thread up from wait_for_wakeup_ready(). */ + void signal_wakeup_ready(); private: /** The current internal error handler for this thread, or NULL. */ Internal_error_handler *m_internal_handler; @@ -2418,6 +2422,16 @@ private: */ LEX_STRING invoker_user; LEX_STRING invoker_host; + /* + Flag, mutex and condition for a thread to wait for a signal from another + thread. + + Currently used to wait for group commit to complete, can also be used for + other purposes. + */ + bool wakeup_ready; + pthread_mutex_t LOCK_wakeup_ready; + pthread_cond_t COND_wakeup_ready; }; From 5614ebe7ed8e56cbd345158395c1c1930b0752d1 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 29 Oct 2010 13:58:47 +0200 Subject: [PATCH 11/35] MWL#116: after-architecture-review code refactoring and cleanup. Remove the extra class hierarchy with classes TC_LOG_queued, TC_LOG_unordered, and TC_LOG_group_commit, folding the code into the TC_LOG_MMAP and TC_LOG_BINLOG classes. In particular TC_LOG_BINLOG is greatly simplified by this, unifying the code path for transactional and non-transactional commit. Remove unnecessary locking of LOCK_log in MYSQL_BIN_LOG::write() (backport of same fix from mysql-5.5). --- mysql-test/r/group_commit.result | 6 +- mysql-test/r/group_commit_binlog_pos.result | 2 +- mysql-test/t/group_commit.test | 6 +- mysql-test/t/group_commit_binlog_pos.test | 2 +- sql/log.cc | 664 ++++++-------------- sql/log.h | 143 +---- 6 files changed, 221 insertions(+), 602 deletions(-) diff --git a/mysql-test/r/group_commit.result b/mysql-test/r/group_commit.result index c7993227f8f..9e80dc6da6e 100644 --- a/mysql-test/r/group_commit.result +++ b/mysql-test/r/group_commit.result @@ -3,11 +3,11 @@ SELECT variable_value INTO @commits FROM information_schema.global_status WHERE variable_name = 'binlog_commits'; SELECT variable_value INTO @group_commits FROM information_schema.global_status WHERE variable_name = 'binlog_group_commits'; -SET DEBUG_SYNC= "commit_after_group_log_xid SIGNAL group1_running WAIT_FOR group2_queued"; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group1_running WAIT_FOR group2_queued"; INSERT INTO t1 VALUES ("con1"); set DEBUG_SYNC= "now WAIT_FOR group1_running"; SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; -SET DEBUG_SYNC= "commit_after_release_LOCK_group_commit WAIT_FOR group3_committed"; +SET DEBUG_SYNC= "commit_after_release_LOCK_log WAIT_FOR group3_committed"; SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; INSERT INTO t1 VALUES ("con2"); SET DEBUG_SYNC= "now WAIT_FOR group2_con2"; @@ -25,7 +25,7 @@ SELECT * FROM t1 ORDER BY a; a con1 SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; -SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con5_leader WAIT_FOR con6_queued"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con5_leader WAIT_FOR con6_queued"; INSERT INTO t1 VALUES ("con5"); SET DEBUG_SYNC= "now WAIT_FOR con5_leader"; SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con6_queued"; diff --git a/mysql-test/r/group_commit_binlog_pos.result b/mysql-test/r/group_commit_binlog_pos.result index a0bb5ee2d8e..67ae30bbb79 100644 --- a/mysql-test/r/group_commit_binlog_pos.result +++ b/mysql-test/r/group_commit_binlog_pos.result @@ -1,6 +1,6 @@ CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; INSERT INTO t1 VALUES (0); -SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con1_waiting WAIT_FOR con3_queued"; SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont EXECUTE 3"; INSERT INTO t1 VALUES (1); SET DEBUG_SYNC= "now WAIT_FOR con1_waiting"; diff --git a/mysql-test/t/group_commit.test b/mysql-test/t/group_commit.test index df4ea6654d4..7c87c166844 100644 --- a/mysql-test/t/group_commit.test +++ b/mysql-test/t/group_commit.test @@ -27,7 +27,7 @@ connect(con6,localhost,root,,); # group2 to queue up before finishing. connection con1; -SET DEBUG_SYNC= "commit_after_group_log_xid SIGNAL group1_running WAIT_FOR group2_queued"; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group1_running WAIT_FOR group2_queued"; send INSERT INTO t1 VALUES ("con1"); # Make group2 (with three threads) queue up. @@ -37,7 +37,7 @@ send INSERT INTO t1 VALUES ("con1"); connection con2; set DEBUG_SYNC= "now WAIT_FOR group1_running"; SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; -SET DEBUG_SYNC= "commit_after_release_LOCK_group_commit WAIT_FOR group3_committed"; +SET DEBUG_SYNC= "commit_after_release_LOCK_log WAIT_FOR group3_committed"; SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; send INSERT INTO t1 VALUES ("con2"); connection con3; @@ -69,7 +69,7 @@ SELECT * FROM t1 ORDER BY a; connection con5; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; -SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con5_leader WAIT_FOR con6_queued"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con5_leader WAIT_FOR con6_queued"; send INSERT INTO t1 VALUES ("con5"); connection con6; diff --git a/mysql-test/t/group_commit_binlog_pos.test b/mysql-test/t/group_commit_binlog_pos.test index f8c5e719f11..00cf6ab685f 100644 --- a/mysql-test/t/group_commit_binlog_pos.test +++ b/mysql-test/t/group_commit_binlog_pos.test @@ -23,7 +23,7 @@ connect(con3,localhost,root,,); # Queue up three commits for group commit. connection con1; -SET DEBUG_SYNC= "commit_after_get_LOCK_group_commit SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con1_waiting WAIT_FOR con3_queued"; SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont EXECUTE 3"; send INSERT INTO t1 VALUES (1); diff --git a/sql/log.cc b/sql/log.cc index e29758b7f0b..f2884c1ad38 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -155,19 +155,14 @@ class binlog_trx_data { public: binlog_trx_data() : at_least_one_stmt_committed(0), incident(FALSE), m_pending(0), - before_stmt_pos(MY_OFF_T_UNDEF), using_xa(0), commit_bin_log_file_pos(0) + before_stmt_pos(MY_OFF_T_UNDEF), commit_bin_log_file_pos(0), using_xa(0) { trans_log.end_of_file= max_binlog_cache_size; - (void) my_pthread_mutex_init(&LOCK_binlog_participant, MY_MUTEX_INIT_SLOW, - "LOCK_binlog_participant", MYF(0)); - (void) pthread_cond_init(&COND_binlog_participant, 0); } ~binlog_trx_data() { DBUG_ASSERT(pending() == NULL); - (void) pthread_cond_destroy(&COND_binlog_participant); - (void) pthread_mutex_destroy(&LOCK_binlog_participant); close_cached_file(&trans_log); } @@ -265,46 +260,17 @@ public: Binlog position before the start of the current statement. */ my_off_t before_stmt_pos; + /* + Binlog position after current commit, available to storage engines during + commit_ordered() and commit(). + */ + ulonglong commit_bin_log_file_pos; - /* 0 or error when writing to binlog; set during group commit. */ - int error; - /* If error != 0, value of errno (for my_error() reporting). */ - int commit_errno; - /* Link for queueing transactions up for group commit to binlog. */ - binlog_trx_data *next; - /* - Flag set true when group commit for this transaction is finished; used - with pthread_cond_wait() to wait until commit is done. - This flag is protected by LOCK_binlog_participant. - */ - bool done; - /* - Flag set if this transaction is the group commit leader that will handle - the actual writing to the binlog. - This flag is protected by LOCK_binlog_participant. - */ - bool group_commit_leader; /* Flag set true if this transaction is committed with log_xid() as part of XA, false if not. */ bool using_xa; - /* - Extra events (BEGIN, COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be - written during group commit. The incident_event is only valid if - has_incident() is true. - */ - Log_event *begin_event; - Log_event *end_event; - Log_event *incident_event; - /* Mutex and condition for wakeup after group commit. */ - pthread_mutex_t LOCK_binlog_participant; - pthread_cond_t COND_binlog_participant; - /* - Binlog position after current commit, available to storage engines during - commit() and commit_ordered(). - */ - ulonglong commit_bin_log_file_pos; }; handlerton *binlog_hton; @@ -1441,30 +1407,6 @@ static int binlog_close_connection(handlerton *hton, THD *thd) return 0; } -/* Helper functions for binlog_flush_trx_cache(). */ -static int -binlog_flush_trx_cache_prepare(THD *thd) -{ - if (thd->binlog_flush_pending_rows_event(TRUE)) - return 1; - return 0; -} - -static void -binlog_flush_trx_cache_finish(THD *thd, binlog_trx_data *trx_data) -{ - IO_CACHE *trans_log= &trx_data->trans_log; - - trx_data->reset(); - - statistic_increment(binlog_cache_use, &LOCK_status); - if (trans_log->disk_writes != 0) - { - statistic_increment(binlog_cache_disk_use, &LOCK_status); - trans_log->disk_writes= 0; - } -} - /* End a transaction, writing events to the binary log. @@ -1487,14 +1429,15 @@ binlog_flush_trx_cache_finish(THD *thd, binlog_trx_data *trx_data) */ static int binlog_flush_trx_cache(THD *thd, binlog_trx_data *trx_data, - Log_event *end_ev) + Log_event *end_ev, bool all) { DBUG_ENTER("binlog_flush_trx_cache"); + IO_CACHE *trans_log= &trx_data->trans_log; DBUG_PRINT("info", ("thd->options={ %s%s}", FLAGSTR(thd->options, OPTION_NOT_AUTOCOMMIT), FLAGSTR(thd->options, OPTION_BEGIN))); - if (binlog_flush_trx_cache_prepare(thd)) + if (thd->binlog_flush_pending_rows_event(TRUE)) DBUG_RETURN(1); /* @@ -1507,9 +1450,17 @@ binlog_flush_trx_cache(THD *thd, binlog_trx_data *trx_data, were, we would have to ensure that we're not ending a statement inside a stored function. */ - int error= mysql_bin_log.write_transaction_to_binlog(thd, trx_data, end_ev); + int error= mysql_bin_log.write_transaction_to_binlog(thd, trx_data, + end_ev, all); - binlog_flush_trx_cache_finish(thd, trx_data); + trx_data->reset(); + + statistic_increment(binlog_cache_use, &LOCK_status); + if (trans_log->disk_writes != 0) + { + statistic_increment(binlog_cache_disk_use, &LOCK_status); + trans_log->disk_writes= 0; + } DBUG_ASSERT(thd->binlog_get_pending_rows_event() == NULL); DBUG_RETURN(error); @@ -1578,51 +1529,11 @@ static LEX_STRING const write_error_msg= static int binlog_prepare(handlerton *hton, THD *thd, bool all) { /* - If this prepare is for a single statement in the middle of a transactions, - not the actual transaction commit, then we do nothing. The real work is - only done later, in the prepare for making persistent changes. + do nothing. + just pretend we can do 2pc, so that MySQL won't + switch to 1pc. + real work will be done in MYSQL_BIN_LOG::log_and_order() */ - if (!all && (thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) - return 0; - - binlog_trx_data *trx_data= - (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); - - trx_data->using_xa= TRUE; - - if (binlog_flush_trx_cache_prepare(thd)) - return 1; - - my_xid xid= thd->transaction.xid_state.xid.get_my_xid(); - if (!xid) - { - /* Skip logging this transaction, marked by setting end_event to NULL. */ - trx_data->end_event= NULL; - return 0; - } - - /* - Allocate the extra events that will be logged to the binlog in binlog group - commit. Use placement new to allocate them on the THD memroot, as they need - to remain live until log_xid() returns. - */ - size_t needed_size= sizeof(Query_log_event) + sizeof(Xid_log_event); - if (trx_data->has_incident()) - needed_size+= sizeof(Incident_log_event); - uchar *mem= (uchar *)thd->alloc(needed_size); - if (!mem) - return 1; - - trx_data->begin_event= new ((void *)mem) - Query_log_event(thd, STRING_WITH_LEN("BEGIN"), TRUE, TRUE, 0); - mem+= sizeof(Query_log_event); - - trx_data->end_event= new ((void *)mem) Xid_log_event(thd, xid); - - if (trx_data->has_incident()) - trx_data->incident_event= new ((void *)(mem + sizeof(Xid_log_event))) - Incident_log_event(thd, INCIDENT_LOST_EVENTS, write_error_msg); - return 0; } @@ -1646,11 +1557,11 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) binlog_trx_data *const trx_data= (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); - if (trx_data->using_xa) + if (trx_data->empty()) { // we're here because trans_log was flushed in MYSQL_BIN_LOG::log_xid() - binlog_flush_trx_cache_finish(thd, trx_data); - DBUG_RETURN(error); + trx_data->reset(); + DBUG_RETURN(0); } /* @@ -1673,7 +1584,7 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) !stmt_has_updated_trans_table(thd) && stmt_has_updated_non_trans_table(thd))) { Query_log_event end_ev(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, 0); - error= binlog_flush_trx_cache(thd, trx_data, &end_ev); + error= binlog_flush_trx_cache(thd, trx_data, &end_ev, all); } trx_data->at_least_one_stmt_committed = my_b_tell(&trx_data->trans_log) > 0; @@ -1757,7 +1668,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) thd->current_stmt_binlog_row_based)) { Query_log_event end_ev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, TRUE, 0); - error= binlog_flush_trx_cache(thd, trx_data, &end_ev); + error= binlog_flush_trx_cache(thd, trx_data, &end_ev, all); } /* Otherwise, we simply truncate the cache as there is no change on @@ -2599,6 +2510,7 @@ const char *MYSQL_LOG::generate_name(const char *log_name, MYSQL_BIN_LOG::MYSQL_BIN_LOG() :bytes_written(0), prepared_xids(0), file_id(1), open_count(1), need_start_event(TRUE), + group_commit_queue(0), num_commits(0), num_group_commits(0), is_relay_log(0), description_event_for_exec(0), description_event_for_queue(0) { @@ -2626,7 +2538,6 @@ void MYSQL_BIN_LOG::cleanup() delete description_event_for_exec; (void) pthread_mutex_destroy(&LOCK_log); (void) pthread_mutex_destroy(&LOCK_index); - (void) pthread_mutex_destroy(&LOCK_queue); (void) pthread_cond_destroy(&update_cond); } DBUG_VOID_RETURN; @@ -2655,8 +2566,6 @@ void MYSQL_BIN_LOG::init_pthread_objects() */ (void) my_pthread_mutex_init(&LOCK_index, MY_MUTEX_INIT_SLOW, "LOCK_index", MYF_NO_DEADLOCK_DETECTION); - (void) my_pthread_mutex_init(&LOCK_queue, MY_MUTEX_INIT_FAST, "LOCK_queue", - MYF(0)); (void) pthread_cond_init(&update_cond, 0); } @@ -4461,11 +4370,6 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) } /* - Flush the pending rows event to the transaction cache or to the - log file. Since this function potentially aquire the LOCK_log - mutex, we do this before aquiring the LOCK_log mutex in this - function. - We only end the statement if we are in a top-level statement. If we are inside a stored function, we do not end the statement since this will close all tables on the slave. @@ -4475,8 +4379,6 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) if (thd->binlog_flush_pending_rows_event(end_stmt)) DBUG_RETURN(error); - pthread_mutex_lock(&LOCK_log); - /* In most cases this is only called if 'is_open()' is true; in fact this is mostly called if is_open() *was* true a few instructions before, but it @@ -4497,7 +4399,6 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) thd->lex->sql_command != SQLCOM_SAVEPOINT && !binlog_filter->db_ok(local_db))) { - VOID(pthread_mutex_unlock(&LOCK_log)); DBUG_RETURN(0); } #endif /* HAVE_REPLICATION */ @@ -4539,15 +4440,11 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) thd->binlog_start_trans_and_stmt(); file= trans_log; } - /* - TODO as Mats suggested, for all the cases above where we write to - trans_log, it sounds unnecessary to lock LOCK_log. We should rather - test first if we want to write to trans_log, and if not, lock - LOCK_log. - */ } #endif /* USING_TRANSACTIONS */ DBUG_PRINT("info",("event type: %d",event_info->get_type_code())); + if (file == &log_file) + pthread_mutex_lock(&LOCK_log); /* No check for auto events flag here - this write method should @@ -4572,7 +4469,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT, thd->first_successful_insert_id_in_prev_stmt_for_binlog); if (e.write(file)) - goto err; + goto err_unlock; } if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0) { @@ -4583,13 +4480,13 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) thd->auto_inc_intervals_in_cur_stmt_for_binlog. minimum()); if (e.write(file)) - goto err; + goto err_unlock; } if (thd->rand_used) { Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2); if (e.write(file)) - goto err; + goto err_unlock; } if (thd->user_var_events.elements) { @@ -4604,7 +4501,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) user_var_event->type, user_var_event->charset_number); if (e.write(file)) - goto err; + goto err_unlock; } } } @@ -4616,23 +4513,26 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) if (event_info->write(file) || DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0)) - goto err; + goto err_unlock; if (file == &log_file) // we are writing to the real log (disk) { if (flush_and_sync()) - goto err; + goto err_unlock; signal_update(); rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } error=0; +err_unlock: + if (file == &log_file) + pthread_mutex_unlock(&LOCK_log); + err: if (error) set_write_error(thd); } - pthread_mutex_unlock(&LOCK_log); DBUG_RETURN(error); } @@ -4957,10 +4857,16 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) bool MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, - Log_event *end_ev) + Log_event *end_ev, bool all) { + group_commit_entry entry; DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_to_binlog"); + entry.thd= thd; + entry.trx_data= trx_data; + entry.error= 0; + entry.all= all; + /* Create the necessary events here, where we have the correct THD (and thread context). @@ -4969,23 +4875,23 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, thread. */ Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), TRUE, TRUE, 0); - trx_data->begin_event= &qinfo; - trx_data->end_event= end_ev; + entry.begin_event= &qinfo; + entry.end_event= end_ev; if (trx_data->has_incident()) { Incident_log_event inc_ev(thd, INCIDENT_LOST_EVENTS, write_error_msg); - trx_data->incident_event= &inc_ev; - DBUG_RETURN(write_transaction_to_binlog_events(trx_data)); + entry.incident_event= &inc_ev; + DBUG_RETURN(write_transaction_to_binlog_events(&entry)); } else { - trx_data->incident_event= NULL; - DBUG_RETURN(write_transaction_to_binlog_events(trx_data)); + entry.incident_event= NULL; + DBUG_RETURN(write_transaction_to_binlog_events(&entry)); } } bool -MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) +MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) { /* To facilitate group commit for the binlog, we first queue up ourselves in @@ -4995,91 +4901,61 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(binlog_trx_data *trx_data) the commit and wake them up. */ - pthread_mutex_lock(&trx_data->LOCK_binlog_participant); + entry->thd->clear_wakeup_ready(); + pthread_mutex_lock(&LOCK_prepare_ordered); + group_commit_entry *orig_queue= group_commit_queue; + entry->next= orig_queue; + group_commit_queue= entry; - pthread_mutex_lock(&LOCK_queue); - binlog_trx_data *orig_queue= group_commit_queue; - trx_data->next= orig_queue; - group_commit_queue= trx_data; - pthread_mutex_unlock(&LOCK_queue); + if (entry->trx_data->using_xa) + { + DEBUG_SYNC(entry->thd, "commit_before_prepare_ordered"); + run_prepare_ordered(entry->thd, entry->all); + DEBUG_SYNC(entry->thd, "commit_after_prepare_ordered"); + } + pthread_mutex_unlock(&LOCK_prepare_ordered); + /* + The first in the queue handle group commit for all; the others just wait + to be signalled when group commit is done. + */ if (orig_queue != NULL) - { - trx_data->group_commit_leader= FALSE; - trx_data->done= FALSE; - trx_group_commit_participant(trx_data); - } + entry->thd->wait_for_wakeup_ready(); else + trx_group_commit_leader(entry); + + if (!entry->error) + return 0; + + switch (entry->error) { - trx_data->group_commit_leader= TRUE; - pthread_mutex_unlock(&trx_data->LOCK_binlog_participant); - trx_group_commit_leader(NULL); - } - - return trx_group_commit_finish(trx_data); -} - -/* - Participate as secondary transaction in group commit. - - Another thread is already waiting to obtain the LOCK_log, and should include - this thread in the group commit once the log is obtained. So here we put - ourself in the queue and wait to be signalled that the group commit is done. - - Note that this function must be called with trx_data->LOCK_binlog_participant - locked; the mutex will be released before return. -*/ -void -MYSQL_BIN_LOG::trx_group_commit_participant(binlog_trx_data *trx_data) -{ - safe_mutex_assert_owner(&trx_data->LOCK_binlog_participant); - - /* Wait until trx_data.done == true and woken up by the leader. */ - while (!trx_data->done) - pthread_cond_wait(&trx_data->COND_binlog_participant, - &trx_data->LOCK_binlog_participant); - pthread_mutex_unlock(&trx_data->LOCK_binlog_participant); -} - -bool -MYSQL_BIN_LOG::trx_group_commit_finish(binlog_trx_data *trx_data) -{ - DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_finish"); - DBUG_PRINT("info", ("trx_data->error=%d\n", trx_data->error)); - if (trx_data->error) - { - switch (trx_data->error) - { - case ER_ERROR_ON_WRITE: - my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, trx_data->commit_errno); - break; - case ER_ERROR_ON_READ: - my_error(ER_ERROR_ON_READ, MYF(ME_NOREFRESH), - trx_data->trans_log.file_name, trx_data->commit_errno); - break; - default: - /* - There are not (and should not be) any errors thrown not covered above. - But just in case one is added later without updating the above switch - statement, include a catch-all. - */ - my_printf_error(trx_data->error, - "Error writing transaction to binary log: %d", - MYF(ME_NOREFRESH), trx_data->error); - } - + case ER_ERROR_ON_WRITE: + my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, entry->commit_errno); + break; + case ER_ERROR_ON_READ: + my_error(ER_ERROR_ON_READ, MYF(ME_NOREFRESH), + entry->trx_data->trans_log.file_name, entry->commit_errno); + break; + default: /* - Since we return error, this transaction XID will not be committed, so - we need to mark it as not needed for recovery (unlog() is not called - for a transaction if log_xid() fails). - */ - if (trx_data->end_event->get_type_code() == XID_EVENT) - mark_xid_done(); - - DBUG_RETURN(1); + There are not (and should not be) any errors thrown not covered above. + But just in case one is added later without updating the above switch + statement, include a catch-all. + */ + my_printf_error(entry->error, + "Error writing transaction to binary log: %d", + MYF(ME_NOREFRESH), entry->error); } - DBUG_RETURN(0); + /* + Since we return error, this transaction XID will not be committed, so + we need to mark it as not needed for recovery (unlog() is not called + for a transaction if log_xid() fails). + */ + if (entry->trx_data->using_xa) + mark_xid_done(); + + return 1; } /* @@ -5093,69 +4969,36 @@ MYSQL_BIN_LOG::trx_group_commit_finish(binlog_trx_data *trx_data) */ void -MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) +MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) { + DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); uint xid_count= 0; uint write_count= 0; - /* First, put anything from group_log_xid into the queue. */ - binlog_trx_data *full_queue= NULL; - binlog_trx_data **next_ptr= &full_queue; - for (TC_group_commit_entry *entry= first; entry; entry= entry->next) - { - binlog_trx_data *const trx_data= - (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); - - /* Skip log_xid for transactions without xid, marked by NULL end_event. */ - if (!trx_data->end_event) - continue; - - trx_data->error= 0; - *next_ptr= trx_data; - next_ptr= &(trx_data->next); - } - /* - Next, lock the LOCK_log(), and once we get it, add any additional writes + Lock the LOCK_log(), and once we get it, collect any additional writes that queued up while we were waiting. - - Note that if some writer not going through log_xid() comes in and gets the - LOCK_log before us, they will not be able to include us in their group - commit (and they are not able to handle ensuring same commit order between - us and participating transactional storage engines anyway). - - On the other hand, when we get the LOCK_log, we will be able to include - any non-trasactional writes that queued up in our group commit. This - should hopefully not be too big of a problem, as group commit is most - important for the transactional case anyway when durability (fsync) is - enabled. */ VOID(pthread_mutex_lock(&LOCK_log)); + DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log"); - /* - As the queue is in reverse order of entering, reverse the queue as we add - it to the existing one. Note that there is no ordering defined between - transactional and non-transactional commits. - */ - pthread_mutex_lock(&LOCK_queue); - binlog_trx_data *current= group_commit_queue; + pthread_mutex_lock(&LOCK_prepare_ordered); + group_commit_entry *current= group_commit_queue; group_commit_queue= NULL; - pthread_mutex_unlock(&LOCK_queue); - binlog_trx_data *xtra_queue= NULL; + pthread_mutex_unlock(&LOCK_prepare_ordered); + + /* As the queue is in reverse order of entering, reverse it. */ + group_commit_entry *queue= NULL; while (current) { - current->error= 0; - binlog_trx_data *next= current->next; - current->next= xtra_queue; - xtra_queue= current; + group_commit_entry *next= current->next; + current->next= queue; + queue= current; current= next; } - *next_ptr= xtra_queue; + DBUG_ASSERT(leader == queue /* the leader should be first in queue */); - /* - Now we have in full_queue the list of transactions to be committed in - order. - */ + /* Now we have in queue the list of transactions to be committed in order. */ DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { @@ -5169,9 +5012,14 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) current->error and let the thread do the error reporting itself once we wake it up. */ - for (current= full_queue; current != NULL; current= current->next) + for (current= queue; current != NULL; current= current->next) { - IO_CACHE *cache= ¤t->trans_log; + binlog_trx_data *trx_data= current->trx_data; + IO_CACHE *cache= &trx_data->trans_log; + + /* Skip log_xid for transactions without xid, marked by NULL end_event. */ + if (!current->end_event) + continue; /* We only bother to write to the binary log if there is anything @@ -5186,9 +5034,9 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) write_count++; } - current->commit_bin_log_file_pos= + trx_data->commit_bin_log_file_pos= log_file.pos_in_file + (log_file.write_pos - log_file.write_buffer); - if (current->end_event->get_type_code() == XID_EVENT) + if (trx_data->using_xa) xid_count++; } @@ -5196,7 +5044,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) { if (flush_and_sync()) { - for (current= full_queue; current != NULL; current= current->next) + for (current= queue; current != NULL; current= current->next) { if (!current->error) { @@ -5213,7 +5061,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) /* if any commit_events are Xid_log_event, increase the number of - prepared_xids (it's decreasd in ::unlog()). Binlog cannot be rotated + prepared_xids (it's decreased in ::unlog()). Binlog cannot be rotated if there're prepared xids in it - see the comment in new_file() for an explanation. If no Xid_log_events (then it's all Query_log_event) rotate binlog, @@ -5227,37 +5075,49 @@ MYSQL_BIN_LOG::trx_group_commit_leader(TC_group_commit_entry *first) rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } - VOID(pthread_mutex_unlock(&LOCK_log)); + DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered"); + pthread_mutex_lock(&LOCK_commit_ordered); + /* + We cannot unlock LOCK_log until we have locked LOCK_commit_ordered; + otherwise scheduling could allow the next group commit to run ahead of us, + messing up the order of commit_ordered() calls. But as soon as + LOCK_commit_ordered is obtained, we can let the next group commit start. + */ + pthread_mutex_unlock(&LOCK_log); + DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log"); + ++num_group_commits; /* - Signal those that are not part of group_log_xid, and are not group leaders - running the queue. - - Since a group leader runs the queue itself if a group_log_xid does not get - to do it forst, such leader threads do not need wait or wakeup. + Wakeup each participant waiting for our group commit, first calling the + commit_ordered() methods for any transactions doing 2-phase commit. */ - for (current= xtra_queue; current != NULL; current= current->next) + current= queue; + while (current != NULL) { - /* - Note that we need to take LOCK_binlog_participant even in the case of a - leader! + DEBUG_SYNC(leader->thd, "commit_loop_entry_commit_ordered"); + ++num_commits; + if (current->trx_data->using_xa && !current->error) + run_commit_ordered(current->thd, current->all); - Otherwise there is a race between setting and testing the - group_commit_leader flag. + /* + Careful not to access current->next after waking up the other thread! As + it may change immediately after wakeup. */ - pthread_mutex_lock(¤t->LOCK_binlog_participant); - if (!current->group_commit_leader) - { - current->done= true; - pthread_cond_signal(¤t->COND_binlog_participant); - } - pthread_mutex_unlock(¤t->LOCK_binlog_participant); + group_commit_entry *next= current->next; + if (current != leader) // Don't wake up ourself + current->thd->signal_wakeup_ready(); + current= next; } + DEBUG_SYNC(leader->thd, "commit_after_group_run_commit_ordered"); + pthread_mutex_unlock(&LOCK_commit_ordered); + + DBUG_VOID_RETURN; } int -MYSQL_BIN_LOG::write_transaction(binlog_trx_data *trx_data) +MYSQL_BIN_LOG::write_transaction(group_commit_entry *entry) { + binlog_trx_data *trx_data= entry->trx_data; IO_CACHE *cache= &trx_data->trans_log; /* Log "BEGIN" at the beginning of every transaction. Here, a transaction is @@ -5272,7 +5132,7 @@ MYSQL_BIN_LOG::write_transaction(binlog_trx_data *trx_data) in wrong positions being shown to the user, MASTER_POS_WAIT undue waiting etc. */ - if (trx_data->begin_event->write(&log_file)) + if (entry->begin_event->write(&log_file)) return ER_ERROR_ON_WRITE; DBUG_EXECUTE_IF("crash_before_writing_xid", @@ -5289,10 +5149,10 @@ MYSQL_BIN_LOG::write_transaction(binlog_trx_data *trx_data) if (write_cache(cache)) return ER_ERROR_ON_WRITE; - if (trx_data->end_event->write(&log_file)) + if (entry->end_event->write(&log_file)) return ER_ERROR_ON_WRITE; - if (trx_data->has_incident() && trx_data->incident_event->write(&log_file)) + if (entry->incident_event && entry->incident_event->write(&log_file)) return ER_ERROR_ON_WRITE; if (cache->error) // Error on read @@ -5754,30 +5614,6 @@ TC_LOG::run_commit_ordered(THD *thd, bool all) } } -TC_LOG_queued::TC_LOG_queued() : group_commit_queue(NULL) -{ -} - -TC_LOG_queued::~TC_LOG_queued() -{ -} - -TC_LOG_queued::TC_group_commit_entry * -TC_LOG_queued::reverse_queue(TC_LOG_queued::TC_group_commit_entry *queue) -{ - TC_group_commit_entry *entry= queue; - TC_group_commit_entry *prev= NULL; - while (entry) - { - TC_group_commit_entry *next= entry->next; - entry->next= prev; - prev= entry; - entry= next; - } - - return prev; -} - int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) @@ -5886,142 +5722,6 @@ int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, } -TC_LOG_group_commit::TC_LOG_group_commit() - : num_commits(0), num_group_commits(0) -{ -} - -TC_LOG_group_commit::~TC_LOG_group_commit() -{ -} - -void -TC_LOG_group_commit::init() -{ - my_pthread_mutex_init(&LOCK_group_commit, MY_MUTEX_INIT_SLOW, - "LOCK_group_commit", MYF(0)); -} - -void -TC_LOG_group_commit::deinit() -{ - pthread_mutex_destroy(&LOCK_group_commit); -} - -int TC_LOG_group_commit::log_and_order(THD *thd, my_xid xid, bool all, - bool need_prepare_ordered, - bool need_commit_ordered) -{ - IF_DBUG(int err;) - int cookie; - struct TC_group_commit_entry entry; - bool is_group_commit_leader; - - thd->clear_wakeup_ready(); - entry.thd= thd; - entry.all= all; - entry.xid_error= 0; - - pthread_mutex_lock(&LOCK_prepare_ordered); - TC_group_commit_entry *previous_queue= group_commit_queue; - entry.next= previous_queue; - group_commit_queue= &entry; - - DEBUG_SYNC(thd, "commit_before_prepare_ordered"); - run_prepare_ordered(thd, all); - DEBUG_SYNC(thd, "commit_after_prepare_ordered"); - pthread_mutex_unlock(&LOCK_prepare_ordered); - - is_group_commit_leader= (previous_queue == NULL); - - if (is_group_commit_leader) - { - TC_group_commit_entry *current; - - pthread_mutex_lock(&LOCK_group_commit); - DEBUG_SYNC(thd, "commit_after_get_LOCK_group_commit"); - - pthread_mutex_lock(&LOCK_prepare_ordered); - TC_group_commit_entry *queue= group_commit_queue; - group_commit_queue= NULL; - pthread_mutex_unlock(&LOCK_prepare_ordered); - - /* - Since we enqueue at the head, the queue is actually in reverse order. - So reverse it back into correct commit order before returning. - */ - queue= reverse_queue(queue); - - /* The first in the queue is the leader. */ - DBUG_ASSERT(queue == &entry && queue->thd == thd); - - DEBUG_SYNC(thd, "commit_before_group_log_xid"); - /* This will set individual error codes in each thd->xid_error. */ - group_log_xid(queue); - DEBUG_SYNC(thd, "commit_after_group_log_xid"); - - /* - Call commit_ordered methods for all transactions in the queue - (that did not get an error in group_log_xid()). - - We do this under an additional global LOCK_commit_ordered; this is - so that transactions that do not need 2-phase commit do not have - to wait for the potentially long duration of LOCK_group_commit. - */ - current= queue; - - DEBUG_SYNC(thd, "commit_before_get_LOCK_commit_ordered"); - pthread_mutex_lock(&LOCK_commit_ordered); - /* - We cannot unlock LOCK_group_commit until we have locked - LOCK_commit_ordered; otherwise scheduling could allow the next - group commit to run ahead of us, messing up the order of - commit_ordered() calls. But as soon as LOCK_commit_ordered is - obtained, we can let the next group commit start. - */ - pthread_mutex_unlock(&LOCK_group_commit); - DEBUG_SYNC(thd, "commit_after_release_LOCK_group_commit"); - - ++num_group_commits; - do - { - DEBUG_SYNC(thd, "commit_loop_entry_commit_ordered"); - ++num_commits; - if (!current->xid_error) - run_commit_ordered(current->thd, current->all); - - /* - Careful not to access current->next_commit_ordered after waking up - the other thread! As it may change immediately after wakeup. - */ - TC_group_commit_entry *next= current->next; - if (current != &entry) // Don't wake up ourself - current->thd->signal_wakeup_ready(); - current= next; - } while (current != NULL); - DEBUG_SYNC(thd, "commit_after_group_run_commit_ordered"); - - pthread_mutex_unlock(&LOCK_commit_ordered); - } - else - { - /* If not leader, just wait until leader wakes us up. */ - thd->wait_for_wakeup_ready(); - } - - /* - Now that we're back in our own thread context, do any delayed processing - and error reporting. - */ - IF_DBUG(err= entry.xid_error;) - cookie= xid_log_after(&entry); - /* The cookie must be non-zero in the non-error case. */ - DBUG_ASSERT(err || cookie); - - return cookie; -} - - /********* transaction coordinator log for 2pc - mmap() based solution *******/ /* @@ -6567,7 +6267,6 @@ int TC_LOG_BINLOG::open(const char *opt_name) DBUG_ASSERT(total_ha_2pc > 1); DBUG_ASSERT(opt_name && opt_name[0]); - TC_LOG_group_commit::init(); pthread_mutex_init(&LOCK_prep_xids, MY_MUTEX_INIT_FAST); pthread_cond_init (&COND_prep_xids, 0); @@ -6651,36 +6350,33 @@ void TC_LOG_BINLOG::close() DBUG_ASSERT(prepared_xids==0); pthread_mutex_destroy(&LOCK_prep_xids); pthread_cond_destroy (&COND_prep_xids); - TC_LOG_group_commit::deinit(); } /* Do a binlog log_xid() for a group of transactions, linked through thd->next_commit_ordered. */ -void -TC_LOG_BINLOG::group_log_xid(TC_group_commit_entry *first) -{ - DBUG_ENTER("TC_LOG_BINLOG::group_log_xid"); - trx_group_commit_leader(first); - for (TC_group_commit_entry *entry= first; entry; entry= entry->next) - { - binlog_trx_data *const trx_data= - (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); - entry->xid_error= trx_data->error; - } - DBUG_VOID_RETURN; -} - int -TC_LOG_BINLOG::xid_log_after(TC_group_commit_entry *entry) +TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered __attribute__((unused)), + bool need_commit_ordered __attribute__((unused))) { + int err; + DBUG_ENTER("TC_LOG_BINLOG::log_and_order"); + binlog_trx_data *const trx_data= - (binlog_trx_data*) thd_get_ha_data(entry->thd, binlog_hton); - if (trx_group_commit_finish(trx_data)) - return 0; // Returning zero cookie signals error + (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + + trx_data->using_xa= TRUE; + if (xid) + { + Xid_log_event xid_event(thd, xid); + err= binlog_flush_trx_cache(thd, trx_data, &xid_event, all); + } else - return 1; + err= binlog_flush_trx_cache(thd, trx_data, NULL, all); + + DBUG_RETURN(!err); } /* diff --git a/sql/log.h b/sql/log.h index 37f5462f198..c5a2a72647b 100644 --- a/sql/log.h +++ b/sql/log.h @@ -73,101 +73,6 @@ extern pthread_mutex_t LOCK_commit_ordered; extern void TC_init(); extern void TC_destroy(); -/* - Base class for two TC implementations TC_LOG_unordered and - TC_LOG_group_commit that both use a queue of threads waiting for group - commit. -*/ -class TC_LOG_queued: public TC_LOG -{ -protected: - TC_LOG_queued(); - ~TC_LOG_queued(); - - /* Structure used to link list of THDs waiting for group commit. */ - struct TC_group_commit_entry - { - struct TC_group_commit_entry *next; - THD *thd; - /* This is the `all' parameter for ha_commit_trans() etc. */ - bool all; - /* - Set by TC_LOG_group_commit::group_log_xid(), to return per-thd error and - cookie. - */ - int xid_error; - }; - - TC_group_commit_entry * reverse_queue(TC_group_commit_entry *queue); - - /* - This is a queue of threads waiting for being allowed to commit. - Access to the queue must be protected by LOCK_prepare_ordered. - */ - TC_group_commit_entry *group_commit_queue; -}; - -class TC_LOG_group_commit: public TC_LOG_queued -{ -public: - TC_LOG_group_commit(); - ~TC_LOG_group_commit(); - void init(); - void deinit(); - - int log_and_order(THD *thd, my_xid xid, bool all, - bool need_prepare_ordered, bool need_commit_ordered); - -protected: - /* Total number of committed transactions. */ - ulonglong num_commits; - /* Number of group commits done. */ - ulonglong num_group_commits; - - /* - When using this class, this method is used instead of log_xid() to do - logging of a group of transactions all at once. - - The transactions will be linked through THD::next_commit_ordered. - - Additionally, when this method is used instead of log_xid(), the order in - which handler->prepare_ordered() and handler->commit_ordered() are called - is guaranteed to be the same as the order of calls and THD list elements - for group_log_xid(). - - This can be used to efficiently implement group commit that at the same - time preserves the order of commits among handlers and TC (eg. to get same - commit order in InnoDB and binary log). - - For TCs that do not need this, it can be preferable to use plain log_xid() - with class TC_LOG_unordered instead, as it allows threads to run log_xid() - in parallel with each other. In contrast, group_log_xid() runs under a - global mutex, so it is guaranteed that only once call into it will be - active at once. - - Since this call handles multiple threads/THDs at once, my_error() (and - other code that relies on thread local storage) cannot be used in this - method. Instead, the implementation must record any error and report it as - the return value from xid_log_after(), which will be invoked individually - for each thread. - - In the success case, this method must set thd->xid_cookie for each thread - to the cookie that is normally returned from log_xid() (which must be - non-zero in the non-error case). - */ - virtual void group_log_xid(TC_group_commit_entry *first) = 0; - /* - Called for each transaction (in corrent thread context) after - group_log_xid() has finished, but with no guarantee on ordering among - threads. - Can be used to do error reporting etc. */ - virtual int xid_log_after(TC_group_commit_entry *entry) = 0; - -private: - /* Mutex used to serialise calls to group_log_xid(). */ - pthread_mutex_t LOCK_group_commit; -}; - class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging { public: @@ -398,17 +303,33 @@ private: }; class binlog_trx_data; -class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG +class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG { private: + struct group_commit_entry + { + struct group_commit_entry *next; + THD *thd; + binlog_trx_data *trx_data; + /* + Extra events (BEGIN, COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be + written during group commit. The incident_event is only valid if + trx_data->has_incident() is true. + */ + Log_event *begin_event; + Log_event *end_event; + Log_event *incident_event; + /* Set during group commit to record any per-thread error. */ + int error; + int commit_errno; + /* This is the `all' parameter for ha_commit_ordered(). */ + bool all; + /* True if we come in through XA log_and_order(), false otherwise. */ + }; + /* LOCK_log and LOCK_index are inited by init_pthread_objects() */ pthread_mutex_t LOCK_index; pthread_mutex_t LOCK_prep_xids; - /* - Mutex to protect the queue of non-transactional binlog writes waiting to - participate in group commit. - */ - pthread_mutex_t LOCK_queue; pthread_cond_t COND_prep_xids; pthread_cond_t update_cond; @@ -449,7 +370,11 @@ class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG */ bool no_auto_events; /* Queue of transactions queued up to participate in group commit. */ - binlog_trx_data *group_commit_queue; + group_commit_entry *group_commit_queue; + /* Total number of committed transactions. */ + ulonglong num_commits; + /* Number of group commits done. */ + ulonglong num_group_commits; int write_to_file(IO_CACHE *cache); /* @@ -459,10 +384,9 @@ class MYSQL_BIN_LOG: public TC_LOG_group_commit, private MYSQL_LOG */ void new_file_without_locking(); void new_file_impl(bool need_lock); - int write_transaction(binlog_trx_data *trx_data); - bool write_transaction_to_binlog_events(binlog_trx_data *trx_data); - void trx_group_commit_participant(binlog_trx_data *trx_data); - void trx_group_commit_leader(TC_group_commit_entry *first); + int write_transaction(group_commit_entry *entry); + bool write_transaction_to_binlog_events(group_commit_entry *entry); + void trx_group_commit_leader(group_commit_entry *leader); void mark_xid_done(); void mark_xids_active(uint xid_count); @@ -493,8 +417,8 @@ public: int open(const char *opt_name); void close(); - void group_log_xid(TC_group_commit_entry *first); - int xid_log_after(TC_group_commit_entry *entry); + int log_and_order(THD *thd, my_xid xid, bool all, + bool need_prepare_ordered, bool need_commit_ordered); void unlog(ulong cookie, my_xid xid); int recover(IO_CACHE *log, Format_description_log_event *fdle); #if !defined(MYSQL_CLIENT) @@ -540,8 +464,7 @@ public: void reset_gathered_updates(THD *thd); bool write(Log_event* event_info); // binary log write bool write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, - Log_event *end_ev); - bool trx_group_commit_finish(binlog_trx_data *trx_data); + Log_event *end_ev, bool all); bool write_incident(THD *thd); int write_cache(IO_CACHE *cache); From a786357be1c09c1ee64d954dda1ddf5f8b641533 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Nov 2010 16:01:25 +0100 Subject: [PATCH 12/35] Minor whitespace/comment cleanup. --- sql/handler.h | 10 ++++------ sql/log.h | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/sql/handler.h b/sql/handler.h index 6e7174bcc60..e0a89f76271 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -737,12 +737,10 @@ struct handlerton cannot invoke code that relies on thread local storage, in particular it cannot call my_error(). - When prepare_ordered() is called, the transaction coordinator has already - decided to commit (not rollback) the transaction. So prepare_ordered() - cannot cause a rollback by returning an error, all possible errors must - be handled in prepare() (the prepare_ordered() method returns void). In - case of some fatal error, a record of the error must be made internally - by the engine and returned from commit() later. + prepare_ordered() cannot cause a rollback by returning an error, all + possible errors must be handled in prepare() (the prepare_ordered() + method returns void). In case of some fatal error, a record of the error + must be made internally by the engine and returned from commit() later. Note that for user-level XA SQL commands, no consistent ordering among prepare_ordered() and commit_ordered() is guaranteed (as that would diff --git a/sql/log.h b/sql/log.h index c5a2a72647b..fcc9d5a711b 100644 --- a/sql/log.h +++ b/sql/log.h @@ -330,7 +330,6 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG /* LOCK_log and LOCK_index are inited by init_pthread_objects() */ pthread_mutex_t LOCK_index; pthread_mutex_t LOCK_prep_xids; - pthread_cond_t COND_prep_xids; pthread_cond_t update_cond; ulonglong bytes_written; From 805f009360288ac6a98572218f2f8cb2402fcf08 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 2 Nov 2010 08:40:27 +0100 Subject: [PATCH 13/35] Fix error insert to have deterministic errno (fixes test failure on mac). --- mysql-test/suite/binlog/r/binlog_ioerr.result | 4 ++-- sql/log.cc | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mysql-test/suite/binlog/r/binlog_ioerr.result b/mysql-test/suite/binlog/r/binlog_ioerr.result index 2300f3c5f82..04ac0340746 100644 --- a/mysql-test/suite/binlog/r/binlog_ioerr.result +++ b/mysql-test/suite/binlog/r/binlog_ioerr.result @@ -4,9 +4,9 @@ CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; INSERT INTO t1 VALUES(0); SET SESSION debug='+d,fail_binlog_write_1'; INSERT INTO t1 VALUES(1); -ERROR HY000: Error writing file 'master-bin' (errno: 22) +ERROR HY000: Error writing file 'master-bin' (errno: 28) INSERT INTO t1 VALUES(2); -ERROR HY000: Error writing file 'master-bin' (errno: 22) +ERROR HY000: Error writing file 'master-bin' (errno: 28) SET SESSION debug=''; INSERT INTO t1 VALUES(3); SELECT * FROM t1; diff --git a/sql/log.cc b/sql/log.cc index 0d1d7ae7535..d18ded1f24e 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4772,7 +4772,8 @@ int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache) } /* Write data to the binary log file */ - DBUG_EXECUTE_IF("fail_binlog_write_1", return ER_ERROR_ON_WRITE;); + DBUG_EXECUTE_IF("fail_binlog_write_1", + errno= 28; return ER_ERROR_ON_WRITE;); if (my_b_write(&log_file, cache->read_pos, length)) return ER_ERROR_ON_WRITE; cache->read_pos=cache->read_end; // Mark buffer used up From a2d921be3634ceff4ab4c67f57b27a481d4a28df Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 3 Nov 2010 16:54:38 +0100 Subject: [PATCH 14/35] MWL#116: Add two more test cases: - Test internal 2-phase commit when binlog is enabled globally, but disabled in the session - Test crashing at various points during commit --- mysql-test/r/group_commit_crash.result | 120 ++++++++++++++++++ mysql-test/suite/pbxt/r/pbxt_xa_binlog.result | 31 +++++ mysql-test/suite/pbxt/t/pbxt_xa_binlog.test | 31 +++++ mysql-test/t/group_commit_crash-master.opt | 1 + mysql-test/t/group_commit_crash.test | 80 ++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 mysql-test/r/group_commit_crash.result create mode 100644 mysql-test/suite/pbxt/r/pbxt_xa_binlog.result create mode 100644 mysql-test/suite/pbxt/t/pbxt_xa_binlog.test create mode 100644 mysql-test/t/group_commit_crash-master.opt create mode 100644 mysql-test/t/group_commit_crash.test diff --git a/mysql-test/r/group_commit_crash.result b/mysql-test/r/group_commit_crash.result new file mode 100644 index 00000000000..044161695e3 --- /dev/null +++ b/mysql-test/r/group_commit_crash.result @@ -0,0 +1,120 @@ +CREATE TABLE t1(a CHAR(255), +b CHAR(255), +c CHAR(255), +d CHAR(255), +id INT AUTO_INCREMENT, +PRIMARY KEY(id)) ENGINE=InnoDB; +create table t2 like t1; +create procedure setcrash(IN i INT) +begin +CASE i +WHEN 1 THEN SET SESSION debug="d,crash_commit_after_prepare"; +WHEN 2 THEN SET SESSION debug="d,crash_commit_after_log"; +WHEN 3 THEN SET SESSION debug="d,crash_commit_before_unlog"; +WHEN 4 THEN SET SESSION debug="d,crash_commit_after"; +WHEN 5 THEN SET SESSION debug="d,crash_commit_before"; +ELSE BEGIN END; +END CASE; +end // +FLUSH TABLES; +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(5); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(4); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(3); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(2); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(1); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +delete from t1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE setcrash; diff --git a/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result b/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result new file mode 100644 index 00000000000..ec12a8a48ae --- /dev/null +++ b/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result @@ -0,0 +1,31 @@ +drop table if exists t1, t2; +SET binlog_format = 'mixed'; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +CREATE TABLE t2 (b INT PRIMARY KEY) ENGINE=pbxt; +BEGIN; +SELECT @@log_bin; +@@log_bin +1 +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +COMMIT; +select * from t1; +a +1 +select * from t2; +b +2 +SET sql_log_bin = 0; +INSERT INTO t1 VALUES (3); +INSERT INTO t2 VALUES (4); +COMMIT; +select * from t1 order by a; +a +1 +3 +select * from t2 order by b; +b +2 +4 +drop table t1, t2; +drop database pbxt; diff --git a/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test b/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test new file mode 100644 index 00000000000..e327c5a7b40 --- /dev/null +++ b/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test @@ -0,0 +1,31 @@ +--source include/have_innodb.inc +--source include/have_log_bin.inc + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings + +SET binlog_format = 'mixed'; + +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +CREATE TABLE t2 (b INT PRIMARY KEY) ENGINE=pbxt; +BEGIN; +# verify that binlog is on +SELECT @@log_bin; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +COMMIT; +select * from t1; +select * from t2; + +# Test 2-phase commit when we disable binlogging. +SET sql_log_bin = 0; +INSERT INTO t1 VALUES (3); +INSERT INTO t2 VALUES (4); +COMMIT; +select * from t1 order by a; +select * from t2 order by b; + +drop table t1, t2; +drop database pbxt; + diff --git a/mysql-test/t/group_commit_crash-master.opt b/mysql-test/t/group_commit_crash-master.opt new file mode 100644 index 00000000000..425fda95086 --- /dev/null +++ b/mysql-test/t/group_commit_crash-master.opt @@ -0,0 +1 @@ +--skip-stack-trace --skip-core-file diff --git a/mysql-test/t/group_commit_crash.test b/mysql-test/t/group_commit_crash.test new file mode 100644 index 00000000000..273cd6230eb --- /dev/null +++ b/mysql-test/t/group_commit_crash.test @@ -0,0 +1,80 @@ +# Testing group commit by crashing a few times. +# Test adapted from the Facebook patch: lp:mysqlatfacebook +--source include/not_embedded.inc +# Don't test this under valgrind, memory leaks will occur +--source include/not_valgrind.inc + +# Binary must be compiled with debug for crash to occur +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc + +let $file_format_check=`SELECT @@innodb_file_format_check`; +CREATE TABLE t1(a CHAR(255), + b CHAR(255), + c CHAR(255), + d CHAR(255), + id INT AUTO_INCREMENT, + PRIMARY KEY(id)) ENGINE=InnoDB; +create table t2 like t1; +delimiter //; +create procedure setcrash(IN i INT) +begin + CASE i + WHEN 1 THEN SET SESSION debug="d,crash_commit_after_prepare"; + WHEN 2 THEN SET SESSION debug="d,crash_commit_after_log"; + WHEN 3 THEN SET SESSION debug="d,crash_commit_before_unlog"; + WHEN 4 THEN SET SESSION debug="d,crash_commit_after"; + WHEN 5 THEN SET SESSION debug="d,crash_commit_before"; + ELSE BEGIN END; + END CASE; +end // +delimiter ;// +# Avoid getting a crashed mysql.proc table. +FLUSH TABLES; + +let $numtests = 5; + +let $numinserts = 10; +while ($numinserts) +{ + dec $numinserts; + INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +} + +--enable_reconnect + +while ($numtests) +{ + RESET MASTER; + + START TRANSACTION; + insert into t1 select * from t2; + # Write file to make mysql-test-run.pl expect crash + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + + eval call setcrash($numtests); + + # Run the crashing query + --error 2006,2013 + COMMIT; + + # Poll the server waiting for it to be back online again. + --source include/wait_until_connected_again.inc + + # table and binlog should be in sync. + SELECT * FROM t1 ORDER BY id; + SHOW BINLOG EVENTS LIMIT 2,1; + + delete from t1; + + dec $numtests; +} + +# final cleanup +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE setcrash; +--disable_query_log +eval SET GLOBAL innodb_file_format_check=$file_format_check; +--enable_query_log From d4fa9ef0c744db07665c0fa63e55aeb05dd03a4d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 3 Nov 2010 17:07:08 +0100 Subject: [PATCH 15/35] MWL#163: release of row locks in InnoDB during prepare Port the Facebook patch for releasing InnoDB row locks early to the MWL#116 framework. A new --innodb-release-locks-early option (off by default) enables a prepare_ordered() handlerton method which will release row locks and commit a transaction to memory immediately after successful prepare. If the server subsequently tries to rollback (ie. due to binlog error), crashes the server to prevent corrupting the InnoDB state. mysql-test/r/innodb_release_row_locks_early.result: Test case. mysql-test/t/innodb_release_row_locks_early-master.opt: Test case. mysql-test/t/innodb_release_row_locks_early.test: Test case. sql/log.cc: Add DEBUG_SYNC points for testing. storage/xtradb/handler/ha_innodb.cc: Release locks during prepare phase if --innodb-release-locks-early. Crash the server if we are asked to rollback after releasing locks and committing the transaction to memory. storage/xtradb/include/srv0srv.h: Add variable for --innodb-release-locks-early option. storage/xtradb/include/trx0sys.ic: If --innodb-release-locks-early, treat a transaction as committed to memory as soon as it enters the TRX_PREPARED state. storage/xtradb/srv/srv0srv.c: Add variable for --innodb-release-locks-early option. --- .../r/innodb_release_row_locks_early.result | 103 +++++++++++++ .../innodb_release_row_locks_early-master.opt | 1 + .../t/innodb_release_row_locks_early.test | 135 ++++++++++++++++++ sql/log.cc | 3 + storage/xtradb/handler/ha_innodb.cc | 68 +++++++++ storage/xtradb/include/srv0srv.h | 3 + storage/xtradb/include/trx0sys.ic | 9 +- storage/xtradb/srv/srv0srv.c | 3 + 8 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 mysql-test/r/innodb_release_row_locks_early.result create mode 100644 mysql-test/t/innodb_release_row_locks_early-master.opt create mode 100644 mysql-test/t/innodb_release_row_locks_early.test diff --git a/mysql-test/r/innodb_release_row_locks_early.result b/mysql-test/r/innodb_release_row_locks_early.result new file mode 100644 index 00000000000..c162f9d4351 --- /dev/null +++ b/mysql-test/r/innodb_release_row_locks_early.result @@ -0,0 +1,103 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (k INT NOT NULL, a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, PRIMARY KEY(k)) ENGINE=InnoDB; +INSERT INTO t1 (k, a, b, c) VALUES (1, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (2, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (3, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (4, 0, 0, 0); +RESET MASTER; +SET DEBUG_SYNC= 'RESET'; +# Connection c1 +BEGIN; +UPDATE t1 SET a=10 WHERE k=1; +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; +COMMIT; +# Connection c2 +SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; +BEGIN; +SELECT * FROM t1 WHERE k=1 FOR UPDATE; +k a b c +1 10 0 0 +UPDATE t1 SET a=20 WHERE k=1; +SET DEBUG_SYNC="now SIGNAL c2_committing"; +COMMIT; +# Connection c1 +BEGIN; +UPDATE t1 SET a=10 WHERE k=2; +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committed TIMEOUT 2"; +COMMIT; +# Connection c2 +SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; +BEGIN; +SELECT * FROM t1 WHERE k=2 FOR UPDATE; +k a b c +2 10 0 0 +UPDATE t1 SET a=20 WHERE k=2; +SET DEBUG_SYNC="binlog_after_log_and_order SIGNAL c2_committed"; +COMMIT; +# Connection c1 +# This should warn about DEBUG_SYNC timeout +Warnings: +Warning 1639 debug sync point wait timed out +# Connection c2 +SHOW BINLOG EVENTS LIMIT 2,12; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 265 use `test`; UPDATE t1 SET a=10 WHERE k=1 +master-bin.000001 265 Xid 1 292 COMMIT /* xid=XX */ +master-bin.000001 292 Query 1 360 BEGIN +master-bin.000001 360 Query 1 451 use `test`; UPDATE t1 SET a=20 WHERE k=1 +master-bin.000001 451 Xid 1 478 COMMIT /* xid=XX */ +master-bin.000001 478 Query 1 546 BEGIN +master-bin.000001 546 Query 1 637 use `test`; UPDATE t1 SET a=10 WHERE k=2 +master-bin.000001 637 Xid 1 664 COMMIT /* xid=XX */ +master-bin.000001 664 Query 1 732 BEGIN +master-bin.000001 732 Query 1 823 use `test`; UPDATE t1 SET a=20 WHERE k=2 +master-bin.000001 823 Xid 1 850 COMMIT /* xid=XX */ +# Connection c1 +RESET MASTER; +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; +UPDATE t1 SET a=10 WHERE k=3; +# Connection c2 +SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; +SELECT * FROM t1 WHERE k=3 FOR UPDATE; +k a b c +3 10 0 0 +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c2_committing"; +UPDATE t1 SET a=20 WHERE k=3; +# Connection c1 +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committed TIMEOUT 2"; +UPDATE t1 SET a=10 WHERE k=4; +# Connection c2 +SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; +SELECT * FROM t1 WHERE k=4 FOR UPDATE; +k a b c +4 10 0 0 +SET DEBUG_SYNC="binlog_after_log_and_order SIGNAL c2_committed"; +UPDATE t1 SET a=20 WHERE k=4; +# Connection c1 +# This should warn about DEBUG_SYNC timeout +SHOW WARNINGS; +Level Code Message +Warning 1639 debug sync point wait timed out +# Connection c2 +SHOW BINLOG EVENTS LIMIT 1,12; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 106 Query 1 174 BEGIN +master-bin.000001 174 Query 1 265 use `test`; UPDATE t1 SET a=10 WHERE k=3 +master-bin.000001 265 Xid 1 292 COMMIT /* xid=XX */ +master-bin.000001 292 Query 1 360 BEGIN +master-bin.000001 360 Query 1 451 use `test`; UPDATE t1 SET a=20 WHERE k=3 +master-bin.000001 451 Xid 1 478 COMMIT /* xid=XX */ +master-bin.000001 478 Query 1 546 BEGIN +master-bin.000001 546 Query 1 637 use `test`; UPDATE t1 SET a=10 WHERE k=4 +master-bin.000001 637 Xid 1 664 COMMIT /* xid=XX */ +master-bin.000001 664 Query 1 732 BEGIN +master-bin.000001 732 Query 1 823 use `test`; UPDATE t1 SET a=20 WHERE k=4 +master-bin.000001 823 Xid 1 850 COMMIT /* xid=XX */ +SELECT * FROM t1 ORDER BY k; +k a b c +1 20 0 0 +2 20 0 0 +3 20 0 0 +4 20 0 0 +DROP TABLE t1; +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/t/innodb_release_row_locks_early-master.opt b/mysql-test/t/innodb_release_row_locks_early-master.opt new file mode 100644 index 00000000000..57ce087cd25 --- /dev/null +++ b/mysql-test/t/innodb_release_row_locks_early-master.opt @@ -0,0 +1 @@ +--innodb-release-locks-early=1 diff --git a/mysql-test/t/innodb_release_row_locks_early.test b/mysql-test/t/innodb_release_row_locks_early.test new file mode 100644 index 00000000000..06b5d884f50 --- /dev/null +++ b/mysql-test/t/innodb_release_row_locks_early.test @@ -0,0 +1,135 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (k INT NOT NULL, a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, PRIMARY KEY(k)) ENGINE=InnoDB; +INSERT INTO t1 (k, a, b, c) VALUES (1, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (2, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (3, 0, 0, 0); +INSERT INTO t1 (k, a, b, c) VALUES (4, 0, 0, 0); + +RESET MASTER; +SET DEBUG_SYNC= 'RESET'; + +# Two transactions A,B that update the same row. +# A releases row locks during the prepare phase, and waits using DEBUG_SYNC. +# B then updates the same row. +# Verify that +# - B's update can proceed while A is waiting for commit, showing that +# locks are released early. +# - B cannot be binlogged before A. + +connect(c1,127.0.0.1,root,,test,$MASTER_MYPORT,); +connect(c2,127.0.0.1,root,,test,$MASTER_MYPORT,); + +connection c1; +--echo # Connection c1 + +# First verify that row locks are released early. +BEGIN; +UPDATE t1 SET a=10 WHERE k=1; +# Wait until c2 starts COMMIT, to verify that we release our locks in prepare. +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; +send COMMIT; + + connection c2; + --echo # Connection c2 + SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; + BEGIN; + SELECT * FROM t1 WHERE k=1 FOR UPDATE; + UPDATE t1 SET a=20 WHERE k=1; + SET DEBUG_SYNC="now SIGNAL c2_committing"; + COMMIT; + +connection c1; +--echo # Connection c1 +reap; + +# Now verify that binlog order is correct. +BEGIN; +UPDATE t1 SET a=10 WHERE k=2; +# This time wait until c2 is binlogged. This should time out, as we must not +# allow c2 to finish commit before c1. +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committed TIMEOUT 2"; +send COMMIT; + + connection c2; + --echo # Connection c2 + SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; + BEGIN; + SELECT * FROM t1 WHERE k=2 FOR UPDATE; + UPDATE t1 SET a=20 WHERE k=2; + SET DEBUG_SYNC="binlog_after_log_and_order SIGNAL c2_committed"; + send COMMIT; + +connection c1; +--echo # Connection c1 +--echo # This should warn about DEBUG_SYNC timeout +reap; + +connection c2; +--echo # Connection c2 +reap; + +--replace_regex /xid=[0-9]+/xid=XX/ +SHOW BINLOG EVENTS LIMIT 2,12; + + +connection c1; +--echo # Connection c1 +# Now the same thing, but using autocommit. +RESET MASTER; +# First verify that row locks are released early. +# Wait until c2 starts COMMIT, to verify that we release our locks in prepare. +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; +send UPDATE t1 SET a=10 WHERE k=3; + + connection c2; + --echo # Connection c2 + SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; + SELECT * FROM t1 WHERE k=3 FOR UPDATE; + SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c2_committing"; + UPDATE t1 SET a=20 WHERE k=3; + +connection c1; +--echo # Connection c1 +reap; + +# Now verify that binlog order is correct, this time with autocommit. +# This time wait until c2 is binlogged. This should time out, as we must not +# allow c2 to finish commit before c1. +SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committed TIMEOUT 2"; +send UPDATE t1 SET a=10 WHERE k=4; + + connection c2; + --echo # Connection c2 + SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; + SELECT * FROM t1 WHERE k=4 FOR UPDATE; + SET DEBUG_SYNC="binlog_after_log_and_order SIGNAL c2_committed"; + send UPDATE t1 SET a=20 WHERE k=4; + +connection c1; +--echo # Connection c1 +--echo # This should warn about DEBUG_SYNC timeout +reap; +# There is a bug (patch pending) that warnings are not shown automatically +# by the client; just do it manually. When bug fix is merged, remove the +# manual SHOW WARNINGS. +SHOW WARNINGS; + +connection c2; +--echo # Connection c2 +reap; + +--replace_regex /xid=[0-9]+/xid=XX/ +SHOW BINLOG EVENTS LIMIT 1,12; + + +SELECT * FROM t1 ORDER BY k; + +DROP TABLE t1; +SET DEBUG_SYNC= 'RESET'; diff --git a/sql/log.cc b/sql/log.cc index d18ded1f24e..1177bc7d44e 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4915,6 +4915,7 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) DEBUG_SYNC(entry->thd, "commit_after_prepare_ordered"); } pthread_mutex_unlock(&LOCK_prepare_ordered); + DEBUG_SYNC(entry->thd, "commit_after_release_LOCK_prepare_ordered"); /* The first in the queue handle group commit for all; the others just wait @@ -6399,6 +6400,8 @@ TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, else err= binlog_flush_trx_cache(thd, trx_data, NULL, all); + DEBUG_SYNC(thd, "binlog_after_log_and_order"); + DBUG_RETURN(!err); } diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 7b9a4ee253c..5a663c0eaba 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -221,6 +221,7 @@ static const char* innobase_change_buffering_values[IBUF_USE_COUNT] = { static INNOBASE_SHARE *get_share(const char *table_name); static void free_share(INNOBASE_SHARE *share); static int innobase_close_connection(handlerton *hton, THD* thd); +static void innobase_prepare_ordered(handlerton *hton, THD* thd, bool all); static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all); static int innobase_commit(handlerton *hton, THD* thd, bool all); static int innobase_rollback(handlerton *hton, THD* thd, bool all); @@ -2042,6 +2043,10 @@ innobase_init( innobase_hton->savepoint_set=innobase_savepoint; innobase_hton->savepoint_rollback=innobase_rollback_to_savepoint; innobase_hton->savepoint_release=innobase_release_savepoint; + if (innobase_release_locks_early) + innobase_hton->prepare_ordered=innobase_prepare_ordered; + else + innobase_hton->prepare_ordered=NULL; innobase_hton->commit_ordered=innobase_commit_ordered; innobase_hton->commit=innobase_commit; innobase_hton->rollback=innobase_rollback; @@ -2728,6 +2733,54 @@ innobase_start_trx_and_assign_read_view( DBUG_RETURN(0); } +/*****************************************************************//** +Release row locks early during prepare phase. + +Only enabled if --innodb-release-locks-early=1. In this case, a prepared +transaction is treated as committed to memory (but not to disk), and we +release row locks at the end of the prepare phase. + +The consistent commit ordering guarantees of prepare_ordered() calls means +that transactions will not be binlogged in different order than locks are +released (which would cause trouble for statement-based replication). + +This optimisation is not 100% safe, so is not enabled by default. But some +applications may decide to enable it to reduce contention on hotspot rows. + +The consequences of enabling this are: + + - It is not possible to rollback after successful prepare(). If there is + a need to rollback (ie. failure to binlog the transaction), we crash the + server (!) + + - If we crash during commit, it is possible that an application/user can have + seen another transaction committed that is not recovered by XA crash + recovery. Thus durability is partially lost. However, consistency is still + guaranteed, we never recover a transaction and not recover another + transaction that committed before. */ +static +void +innobase_prepare_ordered( +/*============*/ + handlerton *hton, /*!< in: Innodb handlerton */ + THD* thd, /*!< in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /*!< in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + DBUG_ENTER("innobase_prepare_ordered"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + mutex_enter(&kernel_mutex); + lock_release_off_kernel(trx); + mutex_exit(&kernel_mutex); + + DBUG_VOID_RETURN; +} + static void innobase_commit_ordered_2( @@ -2968,6 +3021,15 @@ innobase_rollback( row_unlock_table_autoinc_for_mysql(trx); + /* if transaction has already released locks, it is too late to + rollback */ + if (innobase_release_locks_early && trx->conc_state == TRX_PREPARED + && UT_LIST_GET_LEN(trx->trx_locks) == 0) { + sql_print_error("Rollback after releasing locks! " + "errno=%d, dberr=%d", errno, trx->error_state); + ut_error; + } + if (all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { @@ -11672,6 +11734,11 @@ static MYSQL_SYSVAR_ULINT(pass_corrupt_table, srv_pass_corrupt_table, "except for the deletion.", NULL, NULL, 0, 0, 1, 0); +static MYSQL_SYSVAR_BOOL(release_locks_early, innobase_release_locks_early, + PLUGIN_VAR_READONLY, + "Release row locks in the prepare stage instead of in the commit stage", + NULL, NULL, FALSE); + static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(page_size), MYSQL_SYSVAR(additional_mem_pool_size), @@ -11758,6 +11825,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(auto_lru_dump), MYSQL_SYSVAR(use_purge_thread), MYSQL_SYSVAR(pass_corrupt_table), + MYSQL_SYSVAR(release_locks_early), NULL }; diff --git a/storage/xtradb/include/srv0srv.h b/storage/xtradb/include/srv0srv.h index dc455581350..d0b923db619 100644 --- a/storage/xtradb/include/srv0srv.h +++ b/storage/xtradb/include/srv0srv.h @@ -345,6 +345,9 @@ extern ulint srv_buf_pool_reads; /** Time in seconds between automatic buffer pool dumps */ extern uint srv_auto_lru_dump; +/** Release row locks already in the prepare phase */ +extern my_bool innobase_release_locks_early; + /** Status variables to be passed to MySQL */ typedef struct export_var_struct export_struc; diff --git a/storage/xtradb/include/trx0sys.ic b/storage/xtradb/include/trx0sys.ic index c7b09d4aec2..8a897c913c9 100644 --- a/storage/xtradb/include/trx0sys.ic +++ b/storage/xtradb/include/trx0sys.ic @@ -338,6 +338,12 @@ trx_list_get_min_trx_id(void) /****************************************************************//** Checks if a transaction with the given id is active. +IMPORTANT ASSUMPTION: + It is assumed that this function is only used for the purpose of + determining if locks need to be created for the input transaction. + So if innobase_release_locks_early global option is set, and if + the transaction is already in prepared state, this returns FALSE, + as locks are no longer needed for the transaction. @return TRUE if active */ UNIV_INLINE ibool @@ -366,7 +372,8 @@ trx_is_active( trx = trx_get_on_id(trx_id); if (trx && (trx->conc_state == TRX_ACTIVE - || trx->conc_state == TRX_PREPARED)) { + || (trx->conc_state == TRX_PREPARED && + !innobase_release_locks_early))) { return(TRUE); } diff --git a/storage/xtradb/srv/srv0srv.c b/storage/xtradb/srv/srv0srv.c index c1d0f255c64..14c10cabeb9 100644 --- a/storage/xtradb/srv/srv0srv.c +++ b/storage/xtradb/srv/srv0srv.c @@ -487,6 +487,9 @@ UNIV_INTERN FILE* srv_misc_tmpfile; UNIV_INTERN ulint srv_main_thread_process_no = 0; UNIV_INTERN ulint srv_main_thread_id = 0; +/* Release row locks already in the prepare phase */ +UNIV_INTERN my_bool innobase_release_locks_early = FALSE; + /* The following count work done by srv_master_thread. */ /* Iterations by the 'once per second' loop. */ From 7322e38827ce50f86e3facbb63d9c77e744da6df Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 7 Nov 2010 22:37:43 +0100 Subject: [PATCH 16/35] MWL#136: Cross-engine consistency for START TRANSACTION WITH CONSISTENT SNAPSHOT Make the binlog handlerton participate in START TRANSACTION WITH CONSISTENT SNAPSHOT, recording the binlog position corresponding to the snapshot taken in other MVCC storage engines. Expose this consistent binlog position as the new status variables binlog_trx_file and binlog_trx_position. This enables to get a fully non-locking snapshot of the database (including binlog position for slave provisioning), avoiding the need for FLUSH TABLES WITH READ LOCK. Modify mysqldump to detect if the server supports this new feature, and if so, avoid FLUSH TABLES WITH READ LOCK for --single-transaction --master-data snapshot backups. --- client/mysqldump.c | 119 ++++++++++++++---- mysql-test/r/mysqldump-max.result | 56 +++++++++ .../suite/binlog/r/binlog_consistent.result | 99 +++++++++++++++ .../suite/binlog/t/binlog_consistent.test | 88 +++++++++++++ mysql-test/t/mysqldump-max.test | 81 ++++++++++++ sql/log.cc | 105 +++++++++++++--- sql/log.h | 8 +- 7 files changed, 511 insertions(+), 45 deletions(-) create mode 100644 mysql-test/suite/binlog/r/binlog_consistent.result create mode 100644 mysql-test/suite/binlog/t/binlog_consistent.test diff --git a/client/mysqldump.c b/client/mysqldump.c index bb09af4a3c2..bc7f495eb98 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -77,6 +77,9 @@ #define IGNORE_DATA 0x01 /* don't dump data for this table */ #define IGNORE_INSERT_DELAYED 0x02 /* table doesn't support INSERT DELAYED */ +/* Chars needed to store LONGLONG, excluding trailing '\0'. */ +#define LONGLONG_LEN 20 + static void add_load_option(DYNAMIC_STRING *str, const char *option, const char *option_value); static ulong find_set(TYPELIB *lib, const char *x, uint length, @@ -344,9 +347,9 @@ static struct my_option my_long_options[] = "This causes the binary log position and filename to be appended to the " "output. If equal to 1, will print it as a CHANGE MASTER command; if equal" " to 2, that command will be prefixed with a comment symbol. " - "This option will turn --lock-all-tables on, unless " - "--single-transaction is specified too (in which case a " - "global read lock is only taken a short time at the beginning of the dump; " + "This option will turn --lock-all-tables on, unless --single-transaction " + "is specified too (on servers before MariaDB 5.3 this will still take a " + "global read lock for a short time at the beginning of the dump; " "don't forget to read about --single-transaction below). In all cases, " "any action on logs will happen at the exact moment of the dump. " "Option automatically turns --lock-tables off.", @@ -1109,6 +1112,44 @@ static int fetch_db_collation(const char *db_name, } +/* + Check if server supports non-blocking binlog position using the + binlog_trx_file and binlog_trx_position status variables. If it does, + also return the position obtained if output pointers are non-NULL. + Returns 1 if position available, 0 if not. +*/ +static int +check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int found; + + if (mysql_query_with_error_report(mysql, &res, + "SHOW STATUS LIKE 'binlog_trx_%'")) + return 1; + + found= 0; + while ((row= mysql_fetch_row(res))) + { + if (0 == strcmp(row[0], "binlog_trx_file")) + { + if (binlog_pos_file) + strmake(binlog_pos_file, row[1], FN_REFLEN-1); + found++; + } + else if (0 == strcmp(row[0], "binlog_trx_position")) + { + if (binlog_pos_offset) + strmake(binlog_pos_offset, row[1], LONGLONG_LEN); + found++; + } + } + mysql_free_result(res); + + return (found == 2); +} + static char *my_case_str(const char *str, uint str_len, const char *token, @@ -4351,42 +4392,65 @@ static int dump_selected_tables(char *db, char **table_names, int tables) } /* dump_selected_tables */ -static int do_show_master_status(MYSQL *mysql_con) +static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos) { MYSQL_ROW row; MYSQL_RES *master; + char binlog_pos_file[FN_REFLEN]; + char binlog_pos_offset[LONGLONG_LEN+1]; + char *file, *offset; const char *comment_prefix= (opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : ""; - if (mysql_query_with_error_report(mysql_con, &master, "SHOW MASTER STATUS")) + + if (consistent_binlog_pos) { - return 1; + if(!check_consistent_binlog_pos(binlog_pos_file, binlog_pos_offset)) + return 1; + file= binlog_pos_file; + offset= binlog_pos_offset; } else { + if (mysql_query_with_error_report(mysql_con, &master, "SHOW MASTER STATUS")) + return 1; + row= mysql_fetch_row(master); if (row && row[0] && row[1]) { - /* SHOW MASTER STATUS reports file and position */ - if (opt_comments) - fprintf(md_result_file, - "\n--\n-- Position to start replication or point-in-time " - "recovery from\n--\n\n"); - fprintf(md_result_file, - "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", - comment_prefix, row[0], row[1]); - check_io(md_result_file); + file= row[0]; + offset= row[1]; } - else if (!ignore_errors) + else { - /* SHOW MASTER STATUS reports nothing and --force is not enabled */ - my_printf_error(0, "Error: Binlogging on server not active", - MYF(0)); mysql_free_result(master); - maybe_exit(EX_MYSQLERR); - return 1; + if (!ignore_errors) + { + /* SHOW MASTER STATUS reports nothing and --force is not enabled */ + my_printf_error(0, "Error: Binlogging on server not active", + MYF(0)); + maybe_exit(EX_MYSQLERR); + return 1; + } + else + { + return 0; + } } - mysql_free_result(master); } + + /* SHOW MASTER STATUS reports file and position */ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- Position to start replication or point-in-time " + "recovery from\n--\n\n"); + fprintf(md_result_file, + "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", + comment_prefix, file, offset); + check_io(md_result_file); + + if (!consistent_binlog_pos) + mysql_free_result(master); + return 0; } @@ -5025,6 +5089,7 @@ int main(int argc, char **argv) { char bin_log_name[FN_REFLEN]; int exit_code; + int consistent_binlog_pos= 0; MY_INIT("mysqldump"); compatible_mode_normal_str[0]= 0; @@ -5055,7 +5120,13 @@ int main(int argc, char **argv) if (!path) write_header(md_result_file, *argv); - if ((opt_lock_all_tables || opt_master_data) && + if (opt_single_transaction && opt_master_data) + { + /* See if we can avoid FLUSH TABLES WITH READ LOCK (MariaDB 5.3+). */ + consistent_binlog_pos= check_consistent_binlog_pos(NULL, NULL); + } + + if ((opt_lock_all_tables || (opt_master_data && !consistent_binlog_pos)) && do_flush_tables_read_lock(mysql)) goto err; if (opt_single_transaction && start_transaction(mysql)) @@ -5073,7 +5144,7 @@ int main(int argc, char **argv) goto err; flush_logs= 0; /* not anymore; that would not be sensible */ } - if (opt_master_data && do_show_master_status(mysql)) + if (opt_master_data && do_show_master_status(mysql, consistent_binlog_pos)) goto err; if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */ goto err; diff --git a/mysql-test/r/mysqldump-max.result b/mysql-test/r/mysqldump-max.result index c300f3d7996..57c9180691a 100644 --- a/mysql-test/r/mysqldump-max.result +++ b/mysql-test/r/mysqldump-max.result @@ -290,3 +290,59 @@ COUNT(*) DROP VIEW v1; DROP TABLE t1; SET GLOBAL storage_engine=@old_engine; +# Connection default +RESET MASTER; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1,0), (2,0); +SELECT GET_LOCK("block_queries_1", 120); +GET_LOCK("block_queries_1", 120) +1 +# Connection c3 +SELECT GET_LOCK("block_queries_2", 120); +GET_LOCK("block_queries_2", 120) +1 +# Connection c1 +SET @c= 0; +SELECT IF(@c<1, @c:=@c+1, GET_LOCK("block_queries_1", 120)) FROM t1 ORDER BY a; +# Connection c2 +SET binlog_format="row"; +SET @d= 10; +UPDATE t2 SET b=IF(@d<=10, @d:=@d+1, GET_LOCK("block_queries_2", 120)) ORDER BY a; +# Connection default +# Make sure other queries are running (and waiting). +SELECT RELEASE_LOCK("block_queries_1"); +RELEASE_LOCK("block_queries_1") +1 +# Connection c3 +SELECT RELEASE_LOCK("block_queries_2"); +RELEASE_LOCK("block_queries_2") +1 +# Connection c1 +IF(@c<1, @c:=@c+1, GET_LOCK("block_queries_1", 120)) +1 +1 +# Connection c2 +# Connection default +SELECT * FROM t2 ORDER BY a; +a b +1 11 +2 1 +DROP TABLE t1; +DROP TABLE t2; +SHOW BINLOG EVENTS LIMIT 6,3; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 524 Query 1 592 BEGIN +master-bin.000001 592 Query 1 689 use `test`; INSERT INTO t2 VALUES (1,0), (2,0) +master-bin.000001 689 Xid 1 716 COMMIT /* XID */ +-- CHANGE MASTER TO MASTER_LOG_FILE='./master-bin.000001', MASTER_LOG_POS=716; +SELECT * FROM t1 ORDER BY a; +a +1 +2 +SELECT * FROM t2 ORDER BY a; +a b +1 0 +2 0 +DROP TABLE t1,t2; diff --git a/mysql-test/suite/binlog/r/binlog_consistent.result b/mysql-test/suite/binlog/r/binlog_consistent.result new file mode 100644 index 00000000000..dc479ebe29c --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_consistent.result @@ -0,0 +1,99 @@ +RESET MASTER; +# Connection default +CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb; +SHOW MASTER STATUS; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 241 +SHOW STATUS LIKE 'binlog_trx_%'; +Variable_name Value +binlog_trx_file ./master-bin.000001 +binlog_trx_position 241 +BEGIN; +INSERT INTO t1 VALUES (0, ""); +# Connection con1 +BEGIN; +INSERT INTO t1 VALUES (1, ""); +# Connection con2 +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=myisam; +BEGIN; +INSERT INTO t1 VALUES (2, "first"); +INSERT INTO t2 VALUES (2); +INSERT INTO t1 VALUES (2, "second"); +# Connection default +COMMIT; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +# Connection con3 +BEGIN; +INSERT INTO t1 VALUES (3, ""); +INSERT INTO t2 VALUES (3); +# Connection con4 +BEGIN; +INSERT INTO t1 VALUES (4, ""); +COMMIT; +# Connection default +SELECT * FROM t1 ORDER BY a,b; +a b +0 +SHOW STATUS LIKE 'binlog_trx_%'; +Variable_name Value +binlog_trx_file ./master-bin.000001 +binlog_trx_position 540 +SHOW MASTER STATUS; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 727 +SELECT * FROM t2 ORDER BY a; +a +2 +3 +# Connection con1 +COMMIT; +# Connection con2 +COMMIT; +# Connection con3 +COMMIT; +FLUSH LOGS; +# Connection default +SELECT * FROM t1 ORDER BY a,b; +a b +0 +SHOW STATUS LIKE 'binlog_trx_%'; +Variable_name Value +binlog_trx_file ./master-bin.000001 +binlog_trx_position 540 +SHOW MASTER STATUS; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000002 106 +COMMIT; +SHOW STATUS LIKE 'binlog_trx_%'; +Variable_name Value +binlog_trx_file ./master-bin.000002 +binlog_trx_position 106 +SHOW MASTER STATUS; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000002 106 +SHOW BINLOG EVENTS; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 4 Format_desc 1 106 Server ver: #, Binlog ver: # +master-bin.000001 106 Query 1 241 use `test`; CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb +master-bin.000001 241 Query 1 353 use `test`; CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=myisam +master-bin.000001 353 Query 1 421 BEGIN +master-bin.000001 421 Query 1 513 use `test`; INSERT INTO t1 VALUES (0, "") +master-bin.000001 513 Xid 1 540 COMMIT /* XID */ +master-bin.000001 540 Query 1 608 BEGIN +master-bin.000001 608 Query 1 700 use `test`; INSERT INTO t1 VALUES (4, "") +master-bin.000001 700 Xid 1 727 COMMIT /* XID */ +master-bin.000001 727 Query 1 795 BEGIN +master-bin.000001 795 Query 1 887 use `test`; INSERT INTO t1 VALUES (1, "") +master-bin.000001 887 Xid 1 914 COMMIT /* XID */ +master-bin.000001 914 Query 1 982 BEGIN +master-bin.000001 982 Query 1 1079 use `test`; INSERT INTO t1 VALUES (2, "first") +master-bin.000001 1079 Query 1 1167 use `test`; INSERT INTO t2 VALUES (2) +master-bin.000001 1167 Query 1 1265 use `test`; INSERT INTO t1 VALUES (2, "second") +master-bin.000001 1265 Xid 1 1292 COMMIT /* XID */ +master-bin.000001 1292 Query 1 1360 BEGIN +master-bin.000001 1360 Query 1 1452 use `test`; INSERT INTO t1 VALUES (3, "") +master-bin.000001 1452 Query 1 1540 use `test`; INSERT INTO t2 VALUES (3) +master-bin.000001 1540 Xid 1 1567 COMMIT /* XID */ +master-bin.000001 1567 Rotate 1 1611 master-bin.000002;pos=4 +DROP TABLE t1,t2; diff --git a/mysql-test/suite/binlog/t/binlog_consistent.test b/mysql-test/suite/binlog/t/binlog_consistent.test new file mode 100644 index 00000000000..b1df7e45678 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_consistent.test @@ -0,0 +1,88 @@ +--source include/have_log_bin.inc +--source include/have_binlog_format_mixed_or_statement.inc +--source include/have_innodb.inc + +RESET MASTER; + +# Test that we get the correct binlog position from START TRANSACTION WITH +# CONSISTENT SNAPSHOT even when other transactions are active. + +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); +connect(con3,localhost,root,,); +connect(con4,localhost,root,,); + +connection default; +--echo # Connection default + +CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb; +SHOW MASTER STATUS; +SHOW STATUS LIKE 'binlog_trx_%'; +BEGIN; +INSERT INTO t1 VALUES (0, ""); + +connection con1; +--echo # Connection con1 +BEGIN; +INSERT INTO t1 VALUES (1, ""); + +connection con2; +--echo # Connection con2 +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=myisam; +BEGIN; +INSERT INTO t1 VALUES (2, "first"); +INSERT INTO t2 VALUES (2); +INSERT INTO t1 VALUES (2, "second"); + +connection default; +--echo # Connection default +COMMIT; + +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION WITH CONSISTENT SNAPSHOT; + +connection con3; +--echo # Connection con3 +BEGIN; +INSERT INTO t1 VALUES (3, ""); +INSERT INTO t2 VALUES (3); + +connection con4; +--echo # Connection con4 +BEGIN; +INSERT INTO t1 VALUES (4, ""); +COMMIT; + +connection default; +--echo # Connection default +SELECT * FROM t1 ORDER BY a,b; +SHOW STATUS LIKE 'binlog_trx_%'; +SHOW MASTER STATUS; +SELECT * FROM t2 ORDER BY a; + +connection con1; +--echo # Connection con1 +COMMIT; + +connection con2; +--echo # Connection con2 +COMMIT; + +connection con3; +--echo # Connection con3 +COMMIT; +FLUSH LOGS; + +connection default; +--echo # Connection default +SELECT * FROM t1 ORDER BY a,b; +SHOW STATUS LIKE 'binlog_trx_%'; +SHOW MASTER STATUS; +COMMIT; +SHOW STATUS LIKE 'binlog_trx_%'; +SHOW MASTER STATUS; + +--replace_regex /\/\* xid=.* \*\//\/* XID *\// /Server ver: .*, Binlog ver: .*/Server ver: #, Binlog ver: #/ /table_id: [0-9]+/table_id: #/ +SHOW BINLOG EVENTS; + +DROP TABLE t1,t2; diff --git a/mysql-test/t/mysqldump-max.test b/mysql-test/t/mysqldump-max.test index 1e8b9647503..0a489b7eac7 100644 --- a/mysql-test/t/mysqldump-max.test +++ b/mysql-test/t/mysqldump-max.test @@ -2,6 +2,7 @@ --source include/not_embedded.inc --source include/have_innodb.inc --source include/have_archive.inc +--source include/have_log_bin.inc --disable_warnings drop table if exists t1, t2, t3, t4, t5, t6; @@ -1124,3 +1125,83 @@ DROP VIEW v1; DROP TABLE t1; SET GLOBAL storage_engine=@old_engine; + +# Test fully non-locking mysqldump with consistent binlog position (MWL#136). + +connect(c1,127.0.0.1,root,,test,$MASTER_MYPORT,); +connect(c2,127.0.0.1,root,,test,$MASTER_MYPORT,); +connect(c3,127.0.0.1,root,,test,$MASTER_MYPORT,); + +connection default; +--echo # Connection default +RESET MASTER; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1,0), (2,0); +SELECT GET_LOCK("block_queries_1", 120); + +connection c3; +--echo # Connection c3 +SELECT GET_LOCK("block_queries_2", 120); + +# Start two queries that will be running on the tables during mysqldump +connection c1; +--echo # Connection c1 +SET @c= 0; +send SELECT IF(@c<1, @c:=@c+1, GET_LOCK("block_queries_1", 120)) FROM t1 ORDER BY a; + +connection c2; +--echo # Connection c2 +SET binlog_format="row"; +SET @d= 10; +send UPDATE t2 SET b=IF(@d<=10, @d:=@d+1, GET_LOCK("block_queries_2", 120)) ORDER BY a; + +connection default; +--echo # Connection default +--echo # Make sure other queries are running (and waiting). +let $wait_condition= + SELECT COUNT(*) FROM information_schema.processlist + WHERE state = "User lock" AND info LIKE 'SELECT%block_queries_1%'; +--source include/wait_condition.inc +let $wait_condition= + SELECT COUNT(*) FROM information_schema.processlist + WHERE state = "User lock" AND info LIKE 'UPDATE%block_queries_2%'; +--source include/wait_condition.inc + +--exec $MYSQL_DUMP --master-data=2 --single-transaction test t1 t2 > $MYSQLTEST_VARDIR/tmp/mwl136.sql + +SELECT RELEASE_LOCK("block_queries_1"); + +connection c3; +--echo # Connection c3 +SELECT RELEASE_LOCK("block_queries_2"); + +connection c1; +--echo # Connection c1 +reap; + +connection c2; +--echo # Connection c2 +reap; + +connection default; +--echo # Connection default +SELECT * FROM t2 ORDER BY a; +DROP TABLE t1; +DROP TABLE t2; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/mwl136.sql + +--replace_regex /\/\* xid=.* \*\//\/* XID *\// /Server ver: .*, Binlog ver: .*/Server ver: #, Binlog ver: #/ /table_id: [0-9]+/table_id: #/ +SHOW BINLOG EVENTS LIMIT 6,3; +--perl +my $f= "$ENV{MYSQLTEST_VARDIR}/tmp/mwl136.sql"; +open F, '<', $f or die "Failed to open $f: $!\n"; +while () { + print if /CHANGE MASTER TO/; +} +EOF +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +DROP TABLE t1,t2; diff --git a/sql/log.cc b/sql/log.cc index d18ded1f24e..5b3264986c1 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -62,6 +62,7 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv); static int binlog_commit(handlerton *hton, THD *thd, bool all); static int binlog_rollback(handlerton *hton, THD *thd, bool all); static int binlog_prepare(handlerton *hton, THD *thd, bool all); +static int binlog_start_consistent_snapshot(handlerton *hton, THD *thd); /** Silence all errors and warnings reported when performing a write @@ -155,9 +156,10 @@ class binlog_trx_data { public: binlog_trx_data() : at_least_one_stmt_committed(0), incident(FALSE), m_pending(0), - before_stmt_pos(MY_OFF_T_UNDEF), commit_bin_log_file_pos(0), using_xa(0) + before_stmt_pos(MY_OFF_T_UNDEF), last_commit_pos_offset(0), using_xa(0) { trans_log.end_of_file= max_binlog_cache_size; + strcpy(last_commit_pos_file, ""); } ~binlog_trx_data() @@ -215,7 +217,8 @@ public: incident= FALSE; trans_log.end_of_file= max_binlog_cache_size; using_xa= FALSE; - commit_bin_log_file_pos= 0; + strcpy(last_commit_pos_file, ""); + last_commit_pos_offset= 0; DBUG_ASSERT(empty()); } @@ -261,10 +264,14 @@ public: */ my_off_t before_stmt_pos; /* - Binlog position after current commit, available to storage engines during - commit_ordered() and commit(). + Binlog position for current transaction. + For START TRANSACTION WITH CONSISTENT SNAPSHOT, this is the binlog + position corresponding to the snapshot taken. During (and after) commit, + this is set to the binlog position corresponding to just after the + commit (so storage engines can store it in their transaction log). */ - ulonglong commit_bin_log_file_pos; + char last_commit_pos_file[FN_REFLEN]; + my_off_t last_commit_pos_offset; /* Flag set true if this transaction is committed with log_xid() as part of @@ -1392,6 +1399,7 @@ int binlog_init(void *p) binlog_hton->commit= binlog_commit; binlog_hton->rollback= binlog_rollback; binlog_hton->prepare= binlog_prepare; + binlog_hton->start_consistent_snapshot= binlog_start_consistent_snapshot; binlog_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN; return 0; } @@ -2771,6 +2779,11 @@ bool MYSQL_BIN_LOG::open(const char *log_name, if (flush_io_cache(&log_file) || my_sync(log_file.file, MYF(MY_WME))) goto err; + pthread_mutex_lock(&LOCK_commit_ordered); + strmake(last_commit_pos_file, log_file_name, + sizeof(last_commit_pos_file)-1); + last_commit_pos_offset= my_b_tell(&log_file); + pthread_mutex_unlock(&LOCK_commit_ordered); if (write_file_name_to_index_file) { @@ -4199,6 +4212,25 @@ void THD::binlog_set_stmt_begin() { trx_data->before_stmt_pos= pos; } +static int +binlog_start_consistent_snapshot(handlerton *hton, THD *thd) +{ + int err= 0; + binlog_trx_data *trx_data; + DBUG_ENTER("binlog_start_consistent_snapshot"); + + thd->binlog_setup_trx_data(); + trx_data= (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + + /* Server layer calls us with LOCK_commit_ordered locked, so this is safe. */ + strmake(trx_data->last_commit_pos_file, mysql_bin_log.last_commit_pos_file, + sizeof(trx_data->last_commit_pos_file)-1); + trx_data->last_commit_pos_offset= mysql_bin_log.last_commit_pos_offset; + + trans_register_ha(thd, TRUE, hton); + + DBUG_RETURN(err); +} /* Write a table map to the binary log. @@ -4337,6 +4369,9 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + pthread_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= my_b_tell(&log_file); + pthread_mutex_unlock(&LOCK_commit_ordered); pthread_mutex_unlock(&LOCK_log); } @@ -4526,7 +4561,12 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) err_unlock: if (file == &log_file) + { + pthread_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= my_b_tell(&log_file); + pthread_mutex_unlock(&LOCK_commit_ordered); pthread_mutex_unlock(&LOCK_log); + } err: if (error) @@ -4827,6 +4867,9 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) signal_update(); rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + pthread_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= my_b_tell(&log_file); + pthread_mutex_unlock(&LOCK_commit_ordered); pthread_mutex_unlock(&LOCK_log); DBUG_RETURN(error); @@ -4972,9 +5015,11 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) void MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) { - DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); uint xid_count= 0; uint write_count= 0; + my_off_t commit_offset; + DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); + LINT_INIT(commit_offset); /* Lock the LOCK_log(), and once we get it, collect any additional writes @@ -5035,8 +5080,11 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) write_count++; } - trx_data->commit_bin_log_file_pos= + strmake(trx_data->last_commit_pos_file, log_file_name, + sizeof(trx_data->last_commit_pos_file)-1); + commit_offset= log_file.pos_in_file + (log_file.write_pos - log_file.write_buffer); + trx_data->last_commit_pos_offset= commit_offset; if (trx_data->using_xa) xid_count++; } @@ -5078,6 +5126,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered"); pthread_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= commit_offset; /* We cannot unlock LOCK_log until we have locked LOCK_commit_ordered; otherwise scheduling could allow the next group commit to run ahead of us, @@ -6529,10 +6578,6 @@ ulonglong mysql_bin_log_file_pos(void) Since it stores the position inside THD, it is safe to call without any locking. - - Note that currently the binlog file name is not stored inside THD, but this - is still safe as it can only change when the log is rotated, and we never - rotate the binlog while commits are pending inside storage engines. */ void mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) @@ -6541,8 +6586,8 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); if (trx_data) { - *out_pos= trx_data->commit_bin_log_file_pos; - *out_file= mysql_bin_log.get_log_fname(); + *out_file= trx_data->last_commit_pos_file; + *out_pos= (ulonglong)(trx_data->last_commit_pos_offset); } else { @@ -6555,6 +6600,8 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) static ulonglong binlog_status_var_num_commits; static ulonglong binlog_status_var_num_group_commits; +static char binlog_trx_file[FN_REFLEN]; +static ulonglong binlog_trx_position; static SHOW_VAR binlog_status_vars_detail[]= { @@ -6562,12 +6609,16 @@ static SHOW_VAR binlog_status_vars_detail[]= (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, {"group_commits", (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, + {"trx_file", + (char *)&binlog_trx_file, SHOW_CHAR}, + {"trx_position", + (char *)&binlog_trx_position, SHOW_LONGLONG}, {NullS, NullS, SHOW_LONG} }; static int show_binlog_vars(THD *thd, SHOW_VAR *var, char *buff) { - mysql_bin_log.set_status_variables(); + mysql_bin_log.set_status_variables(thd); var->type= SHOW_ARRAY; var->value= (char *)&binlog_status_vars_detail; return 0; @@ -6606,17 +6657,31 @@ static struct st_mysql_sys_var *binlog_sys_vars[]= This is called only under LOCK_status, so we can fill in a static array. */ void -TC_LOG_BINLOG::set_status_variables() +TC_LOG_BINLOG::set_status_variables(THD *thd) { - ulonglong num_commits, num_group_commits; + binlog_trx_data *trx_data; + + if (thd) + trx_data= (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + else + trx_data= NULL; pthread_mutex_lock(&LOCK_commit_ordered); - num_commits= this->num_commits; - num_group_commits= this->num_group_commits; + binlog_status_var_num_commits= this->num_commits; + binlog_status_var_num_group_commits= this->num_group_commits; + if (!trx_data || 0 == strcmp(trx_data->last_commit_pos_file, "")) + { + strmake(binlog_trx_file, last_commit_pos_file, sizeof(binlog_trx_file)-1); + binlog_trx_position= last_commit_pos_offset; + } pthread_mutex_unlock(&LOCK_commit_ordered); - binlog_status_var_num_commits= num_commits; - binlog_status_var_num_group_commits= num_group_commits; + if (trx_data && 0 != strcmp(trx_data->last_commit_pos_file, "")) + { + strmake(binlog_trx_file, trx_data->last_commit_pos_file, + sizeof(binlog_trx_file)-1); + binlog_trx_position= trx_data->last_commit_pos_offset; + } } struct st_mysql_storage_engine binlog_storage_engine= diff --git a/sql/log.h b/sql/log.h index fcc9d5a711b..e2266114e81 100644 --- a/sql/log.h +++ b/sql/log.h @@ -406,6 +406,12 @@ public: */ Format_description_log_event *description_event_for_exec, *description_event_for_queue; + /* + Binlog position of last commit (or non-transactional write) to the binlog. + Access to this is protected by LOCK_commit_ordered. + */ + char last_commit_pos_file[FN_REFLEN]; + my_off_t last_commit_pos_offset; MYSQL_BIN_LOG(); /* @@ -521,7 +527,7 @@ public: inline void unlock_index() { pthread_mutex_unlock(&LOCK_index);} inline IO_CACHE *get_index_file() { return &index_file;} inline uint32 get_open_count() { return open_count; } - void set_status_variables(); + void set_status_variables(THD *thd); }; class Log_event_handler From 0bc31c3ef8f2116960d442b03c11544e0c65b468 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 8 Nov 2010 09:11:44 +0100 Subject: [PATCH 17/35] MWL#136: Fix test failures for row-based binlogging by fixing binlog format. --- mysql-test/r/group_commit_crash.result | 5 +++++ mysql-test/r/mysqldump-max.result | 1 + mysql-test/t/group_commit_crash.test | 1 + mysql-test/t/mysqldump-max.test | 1 + 4 files changed, 8 insertions(+) diff --git a/mysql-test/r/group_commit_crash.result b/mysql-test/r/group_commit_crash.result index 044161695e3..46b6a05a2da 100644 --- a/mysql-test/r/group_commit_crash.result +++ b/mysql-test/r/group_commit_crash.result @@ -27,6 +27,7 @@ INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +SET binlog_format= mixed; RESET MASTER; START TRANSACTION; insert into t1 select * from t2; @@ -38,6 +39,7 @@ a b c d id SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info delete from t1; +SET binlog_format= mixed; RESET MASTER; START TRANSACTION; insert into t1 select * from t2; @@ -60,6 +62,7 @@ SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 delete from t1; +SET binlog_format= mixed; RESET MASTER; START TRANSACTION; insert into t1 select * from t2; @@ -82,6 +85,7 @@ SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 delete from t1; +SET binlog_format= mixed; RESET MASTER; START TRANSACTION; insert into t1 select * from t2; @@ -104,6 +108,7 @@ SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 delete from t1; +SET binlog_format= mixed; RESET MASTER; START TRANSACTION; insert into t1 select * from t2; diff --git a/mysql-test/r/mysqldump-max.result b/mysql-test/r/mysqldump-max.result index 57c9180691a..d4509b51923 100644 --- a/mysql-test/r/mysqldump-max.result +++ b/mysql-test/r/mysqldump-max.result @@ -291,6 +291,7 @@ DROP VIEW v1; DROP TABLE t1; SET GLOBAL storage_engine=@old_engine; # Connection default +SET binlog_format= mixed; RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t1 VALUES (1),(2); diff --git a/mysql-test/t/group_commit_crash.test b/mysql-test/t/group_commit_crash.test index 273cd6230eb..b21032d7993 100644 --- a/mysql-test/t/group_commit_crash.test +++ b/mysql-test/t/group_commit_crash.test @@ -46,6 +46,7 @@ while ($numinserts) while ($numtests) { + SET binlog_format= mixed; RESET MASTER; START TRANSACTION; diff --git a/mysql-test/t/mysqldump-max.test b/mysql-test/t/mysqldump-max.test index 0a489b7eac7..27c1a3ce20c 100644 --- a/mysql-test/t/mysqldump-max.test +++ b/mysql-test/t/mysqldump-max.test @@ -1134,6 +1134,7 @@ connect(c3,127.0.0.1,root,,test,$MASTER_MYPORT,); connection default; --echo # Connection default +SET binlog_format= mixed; RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t1 VALUES (1),(2); From 2a97416e9b0ad90f99499717331e1acc03d8e097 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 8 Nov 2010 11:59:39 +0100 Subject: [PATCH 18/35] MWL#136: Rename binlog_trx_{file,position} to the much better binlog_snapshot_{file,position} --- client/mysqldump.c | 10 ++++---- .../suite/binlog/r/binlog_consistent.result | 24 +++++++++---------- .../suite/binlog/t/binlog_consistent.test | 8 +++---- sql/log.cc | 23 +++++++++--------- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/client/mysqldump.c b/client/mysqldump.c index bc7f495eb98..1c968553329 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -1114,8 +1114,8 @@ static int fetch_db_collation(const char *db_name, /* Check if server supports non-blocking binlog position using the - binlog_trx_file and binlog_trx_position status variables. If it does, - also return the position obtained if output pointers are non-NULL. + binlog_snapshot_file and binlog_snapshot_position status variables. If it + does, also return the position obtained if output pointers are non-NULL. Returns 1 if position available, 0 if not. */ static int @@ -1126,19 +1126,19 @@ check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset) int found; if (mysql_query_with_error_report(mysql, &res, - "SHOW STATUS LIKE 'binlog_trx_%'")) + "SHOW STATUS LIKE 'binlog_snapshot_%'")) return 1; found= 0; while ((row= mysql_fetch_row(res))) { - if (0 == strcmp(row[0], "binlog_trx_file")) + if (0 == strcmp(row[0], "binlog_snapshot_file")) { if (binlog_pos_file) strmake(binlog_pos_file, row[1], FN_REFLEN-1); found++; } - else if (0 == strcmp(row[0], "binlog_trx_position")) + else if (0 == strcmp(row[0], "binlog_snapshot_position")) { if (binlog_pos_offset) strmake(binlog_pos_offset, row[1], LONGLONG_LEN); diff --git a/mysql-test/suite/binlog/r/binlog_consistent.result b/mysql-test/suite/binlog/r/binlog_consistent.result index dc479ebe29c..8784d251c5f 100644 --- a/mysql-test/suite/binlog/r/binlog_consistent.result +++ b/mysql-test/suite/binlog/r/binlog_consistent.result @@ -4,10 +4,10 @@ CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb; SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000001 241 -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_trx_file ./master-bin.000001 -binlog_trx_position 241 +binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_position 241 BEGIN; INSERT INTO t1 VALUES (0, ""); # Connection con1 @@ -35,10 +35,10 @@ COMMIT; SELECT * FROM t1 ORDER BY a,b; a b 0 -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_trx_file ./master-bin.000001 -binlog_trx_position 540 +binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_position 540 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000001 727 @@ -57,18 +57,18 @@ FLUSH LOGS; SELECT * FROM t1 ORDER BY a,b; a b 0 -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_trx_file ./master-bin.000001 -binlog_trx_position 540 +binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_position 540 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000002 106 COMMIT; -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_trx_file ./master-bin.000002 -binlog_trx_position 106 +binlog_snapshot_file ./master-bin.000002 +binlog_snapshot_position 106 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000002 106 diff --git a/mysql-test/suite/binlog/t/binlog_consistent.test b/mysql-test/suite/binlog/t/binlog_consistent.test index b1df7e45678..21e8ac111d4 100644 --- a/mysql-test/suite/binlog/t/binlog_consistent.test +++ b/mysql-test/suite/binlog/t/binlog_consistent.test @@ -17,7 +17,7 @@ connection default; CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb; SHOW MASTER STATUS; -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; BEGIN; INSERT INTO t1 VALUES (0, ""); @@ -56,7 +56,7 @@ COMMIT; connection default; --echo # Connection default SELECT * FROM t1 ORDER BY a,b; -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; SHOW MASTER STATUS; SELECT * FROM t2 ORDER BY a; @@ -76,10 +76,10 @@ FLUSH LOGS; connection default; --echo # Connection default SELECT * FROM t1 ORDER BY a,b; -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; SHOW MASTER STATUS; COMMIT; -SHOW STATUS LIKE 'binlog_trx_%'; +SHOW STATUS LIKE 'binlog_snapshot_%'; SHOW MASTER STATUS; --replace_regex /\/\* xid=.* \*\//\/* XID *\// /Server ver: .*, Binlog ver: .*/Server ver: #, Binlog ver: #/ /table_id: [0-9]+/table_id: #/ diff --git a/sql/log.cc b/sql/log.cc index 5b3264986c1..fb42b42e0cb 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6600,8 +6600,8 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) static ulonglong binlog_status_var_num_commits; static ulonglong binlog_status_var_num_group_commits; -static char binlog_trx_file[FN_REFLEN]; -static ulonglong binlog_trx_position; +static char binlog_snapshot_file[FN_REFLEN]; +static ulonglong binlog_snapshot_position; static SHOW_VAR binlog_status_vars_detail[]= { @@ -6609,10 +6609,10 @@ static SHOW_VAR binlog_status_vars_detail[]= (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, {"group_commits", (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, - {"trx_file", - (char *)&binlog_trx_file, SHOW_CHAR}, - {"trx_position", - (char *)&binlog_trx_position, SHOW_LONGLONG}, + {"snapshot_file", + (char *)&binlog_snapshot_file, SHOW_CHAR}, + {"snapshot_position", + (char *)&binlog_snapshot_position, SHOW_LONGLONG}, {NullS, NullS, SHOW_LONG} }; @@ -6671,16 +6671,17 @@ TC_LOG_BINLOG::set_status_variables(THD *thd) binlog_status_var_num_group_commits= this->num_group_commits; if (!trx_data || 0 == strcmp(trx_data->last_commit_pos_file, "")) { - strmake(binlog_trx_file, last_commit_pos_file, sizeof(binlog_trx_file)-1); - binlog_trx_position= last_commit_pos_offset; + strmake(binlog_snapshot_file, last_commit_pos_file, + sizeof(binlog_snapshot_file)-1); + binlog_snapshot_position= last_commit_pos_offset; } pthread_mutex_unlock(&LOCK_commit_ordered); if (trx_data && 0 != strcmp(trx_data->last_commit_pos_file, "")) { - strmake(binlog_trx_file, trx_data->last_commit_pos_file, - sizeof(binlog_trx_file)-1); - binlog_trx_position= trx_data->last_commit_pos_offset; + strmake(binlog_snapshot_file, trx_data->last_commit_pos_file, + sizeof(binlog_snapshot_file)-1); + binlog_snapshot_position= trx_data->last_commit_pos_offset; } } From 3abce27e9d45282084aa8d0972ffb6721bac16f5 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 27 Dec 2010 22:37:37 +0100 Subject: [PATCH 19/35] Merge Percona patch row_based_replication_without_primary_key.patch into MariaDB. This patch improves the selection of index to use to apply row-based DELETE and UPDATE events on tables with no primary key (original code picks the first index unconditionally). If ANALYZE TABLE is done, the index cardinalities will be compared and the best index will be used. Fixes some problems in the original patch: - Without ANALYZE TABLE, rec_per_key statistics is not available; in this case the original patch could choose a really bad index, even ignoring a primary key. - The original patch did not consider multi-column keys correctly, and could thus pick a less desirable single-column key over a good multi-column index. Also fixes Bug#58997, and adds test cases. --- .../suite/rpl/r/rpl_row_index_choice.result | 143 +++++++++++ .../suite/rpl/t/rpl_row_index_choice.test | 241 ++++++++++++++++++ sql/log_event.cc | 128 ++++++++-- sql/log_event.h | 3 + 4 files changed, 492 insertions(+), 23 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_row_index_choice.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_index_choice.test diff --git a/mysql-test/suite/rpl/r/rpl_row_index_choice.result b/mysql-test/suite/rpl/r/rpl_row_index_choice.result new file mode 100644 index 00000000000..d04576489af --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_index_choice.result @@ -0,0 +1,143 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (a int, b varchar(100), fulltext(b)) engine=MyISAM; +INSERT INTO t1 VALUES (1,"a"), (2,"b"); +UPDATE t1 SET b='A' WHERE a=1; +DELETE FROM t1 WHERE a=2; +SELECT * FROM t1 ORDER BY a; +a b +1 A +DROP TABLE t1; +CREATE TABLE t1 (d INT PRIMARY KEY) ENGINE=myisam; +INSERT INTO t1 VALUES (0); +INSERT INTO t1 SELECT d+1 FROM t1; +INSERT INTO t1 SELECT d+2 FROM t1; +INSERT INTO t1 SELECT d+4 FROM t1; +INSERT INTO t1 SELECT d+8 FROM t1; +INSERT INTO t1 SELECT d+16 FROM t1; +INSERT INTO t1 SELECT d+32 FROM t1; +INSERT INTO t1 SELECT d+64 FROM t1; +INSERT INTO t1 SELECT d+128 FROM t1; +INSERT INTO t1 SELECT d+256 FROM t1; +INSERT INTO t1 SELECT d+512 FROM t1; +CREATE TABLE t2 (a INT, b INT, c INT, d INT, +KEY wrong_key(a), +KEY expected_key(b,c), +KEY wrong_key2(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; +ANALYZE TABLE t2; +Table Op Msg_type Msg_text +test.t2 analyze status OK +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d +4 1 5 10042 +DROP TABLE t2; +CREATE TABLE t2 (a INT, b INT, c INT, d INT NOT NULL, e INT, +UNIQUE wrong_key3(a,e), +KEY wrong_key4(b,c), +UNIQUE expected_key(d)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, NULL FROM t1; +ANALYZE TABLE t2; +Table Op Msg_type Msg_text +test.t2 analyze status OK +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d e +4 1 5 10042 NULL +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT NOT NULL, +KEY wrong_key5(b), +UNIQUE expected_key(d), +KEY wrong_key6(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d +4 1 5 10042 +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT NOT NULL, +KEY expected_key(b), +KEY wrong_key7(d), +KEY wrong_key8(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d +4 1 5 10042 +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, +UNIQUE wrong_key9(d), +KEY wrong_key10(a), +PRIMARY KEY expected_key(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan,slave_crash_if_index_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d +4 1 5 10042 +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, e INT, +UNIQUE wrong_key11(e), +KEY wrong_key12(a), +KEY expected_key(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, IF(d<10, d, NULL) FROM t1; +ANALYZE TABLE t2; +Table Op Msg_type Msg_text +test.t2 analyze status OK +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d e +4 1 5 10042 NULL +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, e INT, +KEY wrong_key13(a), +UNIQUE expected_key(e), +KEY wrong_key14(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, IF(d<10, d, NULL) FROM t1; +# Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); +a b c d e +4 1 5 10042 NULL +DROP TABLE t2; +CREATE TABLE t2 (a INT NOT NULL, d INT) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d FROM t1; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; +SELECT * FROM t2 WHERE d IN (10042,53); +a d +4 10042 +DROP TABLE t2; +DROP TABLE t1; +SET GLOBAL debug=""; diff --git a/mysql-test/suite/rpl/t/rpl_row_index_choice.test b/mysql-test/suite/rpl/t/rpl_row_index_choice.test new file mode 100644 index 00000000000..a4ff999ec69 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_index_choice.test @@ -0,0 +1,241 @@ +--source include/have_binlog_format_row.inc +--source include/master-slave.inc +--source include/have_debug.inc +--source include/have_innodb.inc + +# Bug#58997: Row-based replication breaks on table with only fulltext index: +connection master; +CREATE TABLE t1 (a int, b varchar(100), fulltext(b)) engine=MyISAM; +INSERT INTO t1 VALUES (1,"a"), (2,"b"); +UPDATE t1 SET b='A' WHERE a=1; +DELETE FROM t1 WHERE a=2; + +sync_slave_with_master; + +connection slave; + +SELECT * FROM t1 ORDER BY a; + +connection master; +DROP TABLE t1; + + +# A utility table used to populate subsequent tables in various ways. +connection master; +CREATE TABLE t1 (d INT PRIMARY KEY) ENGINE=myisam; +INSERT INTO t1 VALUES (0); +INSERT INTO t1 SELECT d+1 FROM t1; +INSERT INTO t1 SELECT d+2 FROM t1; +INSERT INTO t1 SELECT d+4 FROM t1; +INSERT INTO t1 SELECT d+8 FROM t1; +INSERT INTO t1 SELECT d+16 FROM t1; +INSERT INTO t1 SELECT d+32 FROM t1; +INSERT INTO t1 SELECT d+64 FROM t1; +INSERT INTO t1 SELECT d+128 FROM t1; +INSERT INTO t1 SELECT d+256 FROM t1; +INSERT INTO t1 SELECT d+512 FROM t1; + +# Test that we pick the better multi-column index, even if the +# single-column index is more selective in the first column. +CREATE TABLE t2 (a INT, b INT, c INT, d INT, + KEY wrong_key(a), + KEY expected_key(b,c), + KEY wrong_key2(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; + +sync_slave_with_master; +connection slave; +ANALYZE TABLE t2; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +# Test that we don't pick a unique index with NULLS over a more selective +# non-unique index. +connection master; +DROP TABLE t2; +CREATE TABLE t2 (a INT, b INT, c INT, d INT NOT NULL, e INT, + UNIQUE wrong_key3(a,e), + KEY wrong_key4(b,c), + UNIQUE expected_key(d)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, NULL FROM t1; + +sync_slave_with_master; +connection slave; +ANALYZE TABLE t2; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + +# Test that we pick a reasonable index when there is no rec_per_key[] +# information (no ANALYZE TABLE). +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT NOT NULL, + KEY wrong_key5(b), + UNIQUE expected_key(d), + KEY wrong_key6(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; + +sync_slave_with_master; +connection slave; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +# Also test without ANALYZE when we pick the sub-optimal index. +# In this case the key on (d) is the best one, but without ANALYZE TABLE we +# have no information and will pick the first one on (b). +# (This test should be updated if we improve the index selection, of course). +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT NOT NULL, + KEY expected_key(b), + KEY wrong_key7(d), + KEY wrong_key8(c)) ENGINE=myisam; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; + +sync_slave_with_master; +connection slave; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +# Test that we pick the primary key for InnoDB, if available. +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, + UNIQUE wrong_key9(d), + KEY wrong_key10(a), + PRIMARY KEY expected_key(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d FROM t1; + +sync_slave_with_master; +connection slave; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan,slave_crash_if_index_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +# Test that we pick a good index for InnoDB when primary key is not available. +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, e INT, + UNIQUE wrong_key11(e), + KEY wrong_key12(a), + KEY expected_key(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, IF(d<10, d, NULL) FROM t1; + +sync_slave_with_master; +connection slave; +ANALYZE TABLE t2; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +# When there is no ANALYZE TABLE, InnoDB will just report "1" for index +# cardinality for all indexes in rec_per_key. So currently we cannot choose +# index to use intelligently. Just test that we work as expected (select +# first index, remember that unique keys are sorted first by server). +CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, e INT, + KEY wrong_key13(a), + UNIQUE expected_key(e), + KEY wrong_key14(c,b)) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d MOD 41, d MOD 37, d, IF(d<10, d, NULL) FROM t1; + +sync_slave_with_master; +connection slave; +--echo # Slave will crash if using the wrong or no index +SET GLOBAL debug="+d,slave_crash_if_wrong_index,slave_crash_if_table_scan"; + +connection master; +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +# Finally, test behaviour when no indexes are available at all. +CREATE TABLE t2 (a INT NOT NULL, d INT) ENGINE=innodb; +INSERT INTO t2 SELECT d DIV 10, d FROM t1; + +UPDATE t2 SET d=10042 WHERE d=42; +DELETE FROM t2 WHERE d=53; + +sync_slave_with_master; +connection slave; +SELECT * FROM t2 WHERE d IN (10042,53); + +connection master; +DROP TABLE t2; + + +connection master; +DROP TABLE t1; +sync_slave_with_master; +connection slave; +SET GLOBAL debug=""; diff --git a/sql/log_event.cc b/sql/log_event.cc index fb4fc5b79b3..09fa2fcf578 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -7128,7 +7128,8 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, m_width(tbl_arg ? tbl_arg->s->fields : 1), m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0) #ifdef HAVE_REPLICATION - , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL) + , m_curr_row(NULL), m_curr_row_end(NULL), + m_key(NULL), m_key_info(NULL), m_key_nr(0) #endif { /* @@ -7176,7 +7177,8 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, #endif m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL) + , m_curr_row(NULL), m_curr_row_end(NULL), + m_key(NULL), m_key_info(NULL), m_key_nr(0) #endif { DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)"); @@ -9043,6 +9045,86 @@ record_compare_exit: return result; } + +/** + Find the best key to use when locating the row in @c find_row(). + + A primary key is preferred if it exists; otherwise a unique index is + preferred. Else we pick the index with the smalles rec_per_key value. + + If a suitable key is found, set @c m_key, @c m_key_nr and @c m_key_info + member fields appropriately. + + @returns Error code on failure, 0 on success. +*/ +int Rows_log_event::find_key() +{ + uint i, best_key_nr, last_part; + KEY *key, *best_key; + ulong best_rec_per_key, tmp; + DBUG_ENTER("Rows_log_event::find_key"); + DBUG_ASSERT(m_table && m_table->in_use != NULL); + + best_key_nr= MAX_KEY; + LINT_INIT(best_key); + LINT_INIT(best_rec_per_key); + + /* + Keys are sorted so that any primary key is first, followed by unique keys, + followed by any other. So we will automatically pick the primary key if + it exists. + */ + for (i= 0, key= m_table->key_info; i < m_table->s->keys; i++, key++) + { + if (!m_table->s->keys_in_use.is_set(i)) + continue; + /* + We cannot use a unique key with NULL-able columns to uniquely identify + a row (but we can still select it for range scan below if nothing better + is available). + */ + if ((key->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME) + { + best_key_nr= i; + best_key= key; + break; + } + /* + We can only use a non-unique key if it allows range scans (ie. skip + FULLTEXT indexes and such). + */ + last_part= key->key_parts - 1; + DBUG_PRINT("info", ("Index %s rec_per_key[%u]= %lu", + key->name, last_part, key->rec_per_key[last_part])); + if (!(m_table->file->index_flags(i, last_part, 1) & HA_READ_NEXT)) + continue; + + tmp= key->rec_per_key[last_part]; + if (best_key_nr == MAX_KEY || (tmp > 0 && tmp < best_rec_per_key)) + { + best_key_nr= i; + best_key= key; + best_rec_per_key= tmp; + } + } + + if (best_key_nr == MAX_KEY) + { + m_key_info= NULL; + DBUG_RETURN(0); + } + + // Allocate buffer for key searches + m_key= (uchar *) my_malloc(best_key->key_length, MYF(MY_WME)); + if (m_key == NULL) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + m_key_info= best_key; + m_key_nr= best_key_nr; + + DBUG_RETURN(0);; +} + + /** Locate the current row in event's table. @@ -9142,12 +9224,17 @@ int Rows_log_event::find_row(const Relay_log_info *rli) */ store_record(table,record[1]); - if (table->s->keys > 0 && table->s->keys_in_use.is_set(0)) + if (m_key_info) { - DBUG_PRINT("info",("locating record using primary key (index_read)")); + DBUG_PRINT("info",("locating record using key #%u [%s] (index_read)", + m_key_nr, m_key_info->name)); + /* We use this to test that the correct key is used in test cases. */ + DBUG_EXECUTE_IF("slave_crash_if_wrong_index", + if(0 != strcmp(m_key_info->name,"expected_key")) abort();); - /* The 0th key is active: search the table using the index */ - if (!table->file->inited && (error= table->file->ha_index_init(0, FALSE))) + /* The key is active: search the table using the index */ + if (!table->file->inited && + (error= table->file->ha_index_init(m_key_nr, FALSE))) { DBUG_PRINT("info",("ha_index_init returns error %d",error)); table->file->print_error(error, MYF(0)); @@ -9157,14 +9244,14 @@ int Rows_log_event::find_row(const Relay_log_info *rli) /* Fill key data for the row */ DBUG_ASSERT(m_key); - key_copy(m_key, table->record[0], table->key_info, 0); + key_copy(m_key, table->record[0], m_key_info, 0); /* Don't print debug messages when running valgrind since they can trigger false warnings. */ #ifndef HAVE_valgrind - DBUG_DUMP("key data", m_key, table->key_info->key_length); + DBUG_DUMP("key data", m_key, m_key_info->key_length); #endif /* @@ -9250,6 +9337,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) record we are looking for is stored in record[1]. */ DBUG_PRINT("info",("non-unique index, scanning it to find matching record")); + /* We use this to test that the correct key is used in test cases. */ + DBUG_EXECUTE_IF("slave_crash_if_index_scan", abort();); while (record_compare(table)) { @@ -9288,6 +9377,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) else { DBUG_PRINT("info",("locating record using table scan (rnd_next)")); + /* We use this to test that the correct key is used in test cases. */ + DBUG_EXECUTE_IF("slave_crash_if_table_scan", abort();); int restart_count= 0; // Number of times scanning has restarted from top @@ -9407,14 +9498,7 @@ Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability return 0; } - if (m_table->s->keys > 0) - { - // Allocate buffer for key searches - m_key= (uchar*)my_malloc(m_table->key_info->key_length, MYF(MY_WME)); - if (!m_key) - return HA_ERR_OUT_OF_MEM; - } - return 0; + return find_key(); } int @@ -9425,6 +9509,7 @@ Delete_rows_log_event::do_after_row_operations(const Slave_reporting_capability m_table->file->ha_index_or_rnd_end(); my_free(m_key, MYF(MY_ALLOW_ZERO_PTR)); m_key= NULL; + m_key_info= NULL; return error; } @@ -9527,13 +9612,9 @@ Update_rows_log_event::Update_rows_log_event(const char *buf, uint event_len, int Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const) { - if (m_table->s->keys > 0) - { - // Allocate buffer for key searches - m_key= (uchar*)my_malloc(m_table->key_info->key_length, MYF(MY_WME)); - if (!m_key) - return HA_ERR_OUT_OF_MEM; - } + int err; + if ((err= find_key())) + return err; m_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; @@ -9548,6 +9629,7 @@ Update_rows_log_event::do_after_row_operations(const Slave_reporting_capability m_table->file->ha_index_or_rnd_end(); my_free(m_key, MYF(MY_ALLOW_ZERO_PTR)); // Free for multi_malloc m_key= NULL; + m_key_info= NULL; return error; } diff --git a/sql/log_event.h b/sql/log_event.h index 770fb29301b..a7529d07206 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -3592,7 +3592,10 @@ protected: const uchar *m_curr_row; /* Start of the row being processed */ const uchar *m_curr_row_end; /* One-after the end of the current row */ uchar *m_key; /* Buffer to keep key value during searches */ + KEY *m_key_info; /* Pointer to KEY info for m_key_nr */ + uint m_key_nr; /* Key number */ + int find_key(); // Find a best key to use in find_row() int find_row(const Relay_log_info *const); int write_row(const Relay_log_info *const, const bool); From 32379aeba9222d255f151bdea4954932fe784021 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 10 Jan 2011 14:53:09 +0100 Subject: [PATCH 20/35] Merge Percona patch MWL#47 into mariadb 5.2-percona. This patch adds options to annotate the binlog (and the mysqlbinlog output) with the original SQL query for queries that are logged using row-based replication. --- client/client_priv.h | 1 + client/mysqlbinlog.cc | 134 +- .../mix_innodb_myisam_binlog.test | 9 +- mysql-test/extra/rpl_tests/rpl_deadlock.test | 4 +- mysql-test/extra/rpl_tests/rpl_log.test | 1 + .../extra/rpl_tests/rpl_row_annotate.test | 155 +++ mysql-test/include/binlog_start_pos.inc | 26 + mysql-test/include/show_binlog_events.inc | 2 +- mysql-test/include/show_binlog_events2.inc | 2 +- mysql-test/r/ctype_cp932_binlog_stm.result | 2 - mysql-test/r/mysqlbinlog.result | 19 +- mysql-test/std_data/words3.dat | 66 + .../suite/binlog/r/binlog_row_annotate.result | 1219 +++++++++++++++++ .../suite/binlog/r/binlog_stm_binlog.result | 4 +- .../suite/binlog/t/binlog_incident.test | 3 +- mysql-test/suite/binlog/t/binlog_killed.test | 4 +- .../binlog/t/binlog_killed_simulate.test | 3 +- .../binlog/t/binlog_row_annotate-master.opt | 1 + .../suite/binlog/t/binlog_row_annotate.test | 189 +++ mysql-test/suite/maria/r/maria-connect.result | 8 +- mysql-test/suite/maria/t/maria-connect.test | 7 +- .../suite/rpl/r/rpl_deadlock_innodb.result | 4 +- .../suite/rpl/r/rpl_row_annotate_do.result | 144 ++ .../suite/rpl/r/rpl_row_annotate_dont.result | 126 ++ .../suite/rpl/r/rpl_row_conflicts.result | 4 +- .../suite/rpl/t/rpl_row_annotate_do-slave.opt | 1 + .../suite/rpl/t/rpl_row_annotate_do.test | 16 + .../rpl/t/rpl_row_annotate_dont-slave.opt | 1 + .../suite/rpl/t/rpl_row_annotate_dont.test | 9 + mysql-test/suite/rpl/t/rpl_row_flsh_tbls.test | 3 +- .../suite/rpl/t/rpl_row_mysqlbinlog.test | 18 +- mysql-test/suite/rpl/t/rpl_stm_flsh_tbls.test | 3 +- mysql-test/t/ctype_cp932_binlog_stm.test | 8 - mysql-test/t/mysqlbinlog-master.opt | 1 - mysql-test/t/mysqlbinlog.test | 99 +- mysql-test/t/mysqlbinlog2.test | 34 +- sql/handler.cc | 9 +- sql/log.cc | 24 +- sql/log.h | 3 +- sql/log_event.cc | 164 ++- sql/log_event.h | 62 +- sql/mysql_priv.h | 4 + sql/mysqld.cc | 15 + sql/rpl_rli.cc | 4 +- sql/rpl_rli.h | 38 + sql/set_var.cc | 3 + sql/slave.cc | 63 +- sql/slave.h | 1 + sql/sql_binlog.cc | 2 +- sql/sql_class.h | 4 +- sql/sql_insert.cc | 5 + sql/sql_repl.cc | 74 +- 52 files changed, 2649 insertions(+), 156 deletions(-) create mode 100644 mysql-test/extra/rpl_tests/rpl_row_annotate.test create mode 100644 mysql-test/include/binlog_start_pos.inc create mode 100644 mysql-test/std_data/words3.dat create mode 100644 mysql-test/suite/binlog/r/binlog_row_annotate.result create mode 100644 mysql-test/suite/binlog/t/binlog_row_annotate-master.opt create mode 100644 mysql-test/suite/binlog/t/binlog_row_annotate.test create mode 100644 mysql-test/suite/rpl/r/rpl_row_annotate_do.result create mode 100644 mysql-test/suite/rpl/r/rpl_row_annotate_dont.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_annotate_do-slave.opt create mode 100644 mysql-test/suite/rpl/t/rpl_row_annotate_do.test create mode 100644 mysql-test/suite/rpl/t/rpl_row_annotate_dont-slave.opt create mode 100644 mysql-test/suite/rpl/t/rpl_row_annotate_dont.test delete mode 100644 mysql-test/t/mysqlbinlog-master.opt diff --git a/client/client_priv.h b/client/client_priv.h index f6bde7a594c..0b617c564df 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -95,5 +95,6 @@ enum options_client OPT_REWRITE_DB, OPT_PLUGIN_DIR, OPT_DEFAULT_PLUGIN, + OPT_SKIP_ANNOTATE_ROWS_EVENTS, OPT_MAX_CLIENT_OPTION /* should be always the last */ }; diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 420042e6055..4018cd3908f 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -107,6 +107,7 @@ static ulonglong rec_count= 0; static short binlog_flags = 0; static MYSQL* mysql = NULL; static const char* dirname_for_local_load= 0; +static bool opt_skip_annotate_rows_events= 0; /** Pointer to the Format_description_log_event of the currently active binlog. @@ -128,6 +129,70 @@ enum Exit_status { OK_STOP }; +/** + Pointer to the last read Annotate_rows_log_event. Having read an + Annotate_rows event, we should not print it immediatedly because all + subsequent rbr events can be filtered away, and have to keep it for a while. + Also because of that when reading a remote Annotate event we have to keep + its binary log representation in a separately allocated buffer. +*/ +static Annotate_rows_log_event *annotate_event= NULL; + +void free_annotate_event() +{ + if (annotate_event) + { + delete annotate_event; + annotate_event= 0; + } +} + +Log_event* read_remote_annotate_event(uchar* net_buf, ulong event_len, + const char **error_msg) +{ + uchar *event_buf; + Log_event* event; + + if (!(event_buf= (uchar*) my_malloc(event_len + 1, MYF(MY_WME)))) + { + error("Out of memory"); + return 0; + } + + memcpy(event_buf, net_buf, event_len); + event_buf[event_len]= 0; + + if (!(event= Log_event::read_log_event((const char*) event_buf, event_len, + error_msg, glob_description_event))) + { + my_free(event_buf, MYF(0)); + return 0; + } + /* + Ensure the event->temp_buf is pointing to the allocated buffer. + (TRUE = free temp_buf on the event deletion) + */ + event->register_temp_buf((char*)event_buf, TRUE); + + return event; +} + +void keep_annotate_event(Annotate_rows_log_event* event) +{ + free_annotate_event(); + annotate_event= event; +} + +void print_annotate_event(PRINT_EVENT_INFO *print_event_info) +{ + if (annotate_event) + { + annotate_event->print(result_file, print_event_info); + delete annotate_event; // the event should not be printed more than once + annotate_event= 0; + } +} + static Exit_status dump_local_log_entries(PRINT_EVENT_INFO *print_event_info, const char* logname); static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info, @@ -927,6 +992,19 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, my_free(fname, MYF(MY_WME)); break; } + case ANNOTATE_ROWS_EVENT: + if (!opt_skip_annotate_rows_events) + { + /* + We don't print Annotate event just now because all subsequent + rbr-events can be filtered away. Instead we'll keep the event + till it will be printed together with the first not filtered + away Table map or the last rbr will be processed. + */ + keep_annotate_event((Annotate_rows_log_event*) ev); + destroy_evt= FALSE; + } + break; case TABLE_MAP_EVENT: { Table_map_log_event *map= ((Table_map_log_event *)ev); @@ -936,6 +1014,13 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, destroy_evt= FALSE; goto end; } + /* + The Table map is to be printed, so it's just the time when we may + print the kept Annotate event (if there is any). + print_annotate_event() also deletes the kept Annotate event. + */ + print_annotate_event(print_event_info); + size_t len_to= 0; const char* db_to= binlog_filter->get_rewrite_db(map->get_db_name(), &len_to); if (len_to && map->rewrite_db(db_to, len_to, glob_description_event)) @@ -972,6 +1057,13 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, if (print_event_info->m_table_map_ignored.count() > 0) print_event_info->m_table_map_ignored.clear_tables(); + /* + If there is a kept Annotate event and all corresponding + rbr-events were filtered away, the Annotate event was not + freed and it is just the time to do it. + */ + free_annotate_event(); + /* One needs to take into account an event that gets filtered but was last event in the statement. If this is @@ -1206,6 +1298,11 @@ that may lead to an endless loop.", "Updates to a database with a different name than the original. \ Example: rewrite-db='from->to'.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"skip-annotate-rows-events", OPT_SKIP_ANNOTATE_ROWS_EVENTS, + "Don't print Annotate_rows events stored in the binary log.", + (uchar**) &opt_skip_annotate_rows_events, + (uchar**) &opt_skip_annotate_rows_events, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; @@ -1677,6 +1774,8 @@ static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info, cast to uint32. */ int4store(buf, (uint32)start_position); + if (!opt_skip_annotate_rows_events) + binlog_flags|= BINLOG_SEND_ANNOTATE_ROWS_EVENT; int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags); size_t tlen = strlen(logname); @@ -1709,18 +1808,30 @@ static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info, break; // end of data DBUG_PRINT("info",( "len: %lu net->read_pos[5]: %d\n", len, net->read_pos[5])); - if (!(ev= Log_event::read_log_event((const char*) net->read_pos + 1 , - len - 1, &error_msg, - glob_description_event))) + if (net->read_pos[5] == ANNOTATE_ROWS_EVENT) { - error("Could not construct log event object: %s", error_msg); - DBUG_RETURN(ERROR_STOP); - } - /* - If reading from a remote host, ensure the temp_buf for the - Log_event class is pointing to the incoming stream. - */ - ev->register_temp_buf((char *) net->read_pos + 1, FALSE); + if (!(ev= read_remote_annotate_event(net->read_pos + 1, len - 1, + &error_msg))) + { + error("Could not construct annotate event object: %s", error_msg); + DBUG_RETURN(ERROR_STOP); + } + } + else + { + if (!(ev= Log_event::read_log_event((const char*) net->read_pos + 1 , + len - 1, &error_msg, + glob_description_event))) + { + error("Could not construct log event object: %s", error_msg); + DBUG_RETURN(ERROR_STOP); + } + /* + If reading from a remote host, ensure the temp_buf for the + Log_event class is pointing to the incoming stream. + */ + ev->register_temp_buf((char *) net->read_pos + 1, FALSE); + } Log_event_type type= ev->get_type_code(); if (glob_description_event->binlog_version >= 3 || @@ -2230,6 +2341,7 @@ int main(int argc, char** argv) if (result_file != stdout) my_fclose(result_file, MYF(0)); cleanup(); + free_annotate_event(); delete binlog_filter; free_root(&s_mem_root, MYF(0)); free_defaults(defaults_argv); diff --git a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test index 5db79e4f848..96c85e024e9 100644 --- a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test +++ b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test @@ -321,14 +321,19 @@ let $MYSQLD_DATADIR= `select @@datadir`; # we check that the error code of the "ROLLBACK" event is 0 and not # ER_SERVER_SHUTDOWN (i.e. disconnection just rolls back transaction # and does not make slave to stop) + +-- source include/binlog_start_pos.inc + if (`select @@binlog_format = 'ROW'`) { - --exec $MYSQL_BINLOG --start-position=524 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mix_innodb_myisam_binlog.output + let $start_pos= `select @binlog_start_pos + 418`; + --exec $MYSQL_BINLOG --start-position=$start_pos $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mix_innodb_myisam_binlog.output } if (`select @@binlog_format = 'STATEMENT' || @@binlog_format = 'MIXED'`) { - --exec $MYSQL_BINLOG --start-position=555 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mix_innodb_myisam_binlog.output + let $start_pos= `select @binlog_start_pos + 449`; + --exec $MYSQL_BINLOG --start-position=$start_pos $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mix_innodb_myisam_binlog.output } --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR diff --git a/mysql-test/extra/rpl_tests/rpl_deadlock.test b/mysql-test/extra/rpl_tests/rpl_deadlock.test index 607348ae97b..bf7c1e05b24 100644 --- a/mysql-test/extra/rpl_tests/rpl_deadlock.test +++ b/mysql-test/extra/rpl_tests/rpl_deadlock.test @@ -72,7 +72,7 @@ connection slave; --source include/stop_slave.inc DELETE FROM t2; # Set slave position to the BEGIN log event ---replace_result $master_pos_begin MASTER_POS_BEGIN +--replace_result $master_pos_begin eval CHANGE MASTER TO MASTER_LOG_POS=$master_pos_begin; BEGIN; # Hold lock @@ -103,7 +103,7 @@ SET global max_relay_log_size=0; --source include/stop_slave.inc DELETE FROM t2; # Set slave position to the BEGIN log event ---replace_result $master_pos_begin MASTER_POS_BEGIN +--replace_result $master_pos_begin eval CHANGE MASTER TO MASTER_LOG_POS=$master_pos_begin; BEGIN; # Hold lock diff --git a/mysql-test/extra/rpl_tests/rpl_log.test b/mysql-test/extra/rpl_tests/rpl_log.test index e714d84a51b..9effeee1292 100644 --- a/mysql-test/extra/rpl_tests/rpl_log.test +++ b/mysql-test/extra/rpl_tests/rpl_log.test @@ -14,6 +14,7 @@ source include/stop_slave.inc; reset master; reset slave; source include/start_slave.inc; +source include/binlog_start_pos.inc; let $VERSION=`select version()`; diff --git a/mysql-test/extra/rpl_tests/rpl_row_annotate.test b/mysql-test/extra/rpl_tests/rpl_row_annotate.test new file mode 100644 index 00000000000..290d0a89b46 --- /dev/null +++ b/mysql-test/extra/rpl_tests/rpl_row_annotate.test @@ -0,0 +1,155 @@ +######################################################################## +# WL47: Store in binlog text of statements that caused RBR events +# new event : ANNOTATE_ROWS_EVENT +# new master option : --binlog-annotate-rows-events +# new slave option : --replicate-annotate-rows-events +######################################################################## +--source include/master-slave.inc +connect (master2,127.0.0.1,root,,test,$MASTER_MYPORT,); + +connection master; +--disable_query_log + +--disable_warnings +DROP DATABASE IF EXISTS test1; +--enable_warnings + +CREATE DATABASE test1; +USE test1; + +CREATE TABLE t1(a int primary key, b int); +CREATE TABLE t2(a int, b int); +CREATE TABLE t3(a int, b int); +CREATE TABLE t4(a int, b int); +CREATE TABLE xt1(a int, b int); +CREATE TABLE xt2(a int, b int); + +CREATE TABLE t5 ( + a INT PRIMARY KEY AUTO_INCREMENT, + b VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_bin +); + +SET SESSION binlog_annotate_rows_events = OFF; + +INSERT INTO t1 VALUES (0,0), (1,1); + +SET SESSION binlog_annotate_rows_events = ON; + +UPDATE t1 SET b = b + 1; +REPLACE t1 VALUES (1,1), (2,2), (3,3); + +INSERT INTO t2 VALUES (1,1), (2,2), (3,3); +INSERT INTO t3 VALUES (1,1), (2,2), (3,3); +DELETE t1, t2 FROM t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.a=t2.a AND t2.a=t3.a; + +INSERT INTO xt1 VALUES (1,1), (2,2), (3,3); +INSERT INTO t2 VALUES (1,1), (2,2), (3,3); +DELETE xt1, t2 FROM xt1 INNER JOIN t2 INNER JOIN t3 WHERE xt1.a=t2.a AND t2.a=t3.a; + +INSERT INTO xt1 VALUES (1,1), (2,2), (3,3); +INSERT INTO xt2 VALUES (1,1), (2,2), (3,3); +DELETE xt1, xt2 FROM xt1 INNER JOIN xt2 INNER JOIN t3 WHERE xt1.a=xt2.a AND xt2.a=t3.a; + +INSERT INTO t5(b) VALUES ('foo'), ('bar'), ('baz'); +SET NAMES latin1; +INSERT INTO t5(b) VALUES ('gås'); +SET NAMES utf8; +INSERT INTO t5(b) VALUES ('gÃ¥s'); +SET NAMES latin1; + +FLUSH LOGS; + +--echo ######################################################################## +--echo # TABLES ON MASTER +--echo ######################################################################## +--enable_query_log + +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; +SELECT * FROM t5 ORDER BY a; + +sync_slave_with_master; +--echo ######################################################################## +--echo # TABLES ON SLAVE: should be the same as on master +--echo ######################################################################## +--disable_query_log +USE test1; +--enable_query_log + +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; +SELECT * FROM t5 ORDER BY a; + +--echo ######################################################################## +--echo # EVENTS ON SLAVE +let $annotate= `select @@global.replicate_annotate_rows_events`; +if ($annotate) +{ + --echo # The following Annotate_rows events should appear below: + --echo # - UPDATE t1 SET b = b + 1; + --echo # - REPLACE t1 VALUES (1,1), (2,2), (3,3); + --echo # - INSERT INTO t2 VALUES (1,1), (2,2), (3,3) + --echo # - INSERT INTO t3 VALUES (1,1), (2,2), (3,3) + --echo # - DELETE t1, t2 FROM <...> + --echo # - INSERT INTO t2 VALUES (1,1), (2,2), (3,3) + --echo # - DELETE xt1, t2 FROM <...> + --echo # - INSERT INTO t5(b) VALUES <...> (3 instances) +} +if (!$annotate) +{ + --echo # No Annotate_rows events should appear below +} +--echo ######################################################################## +FLUSH LOGS; + +--source include/binlog_start_pos.inc +let $start_pos= `select @binlog_start_pos`; +--replace_column 2 # 5 # +--replace_result $start_pos +--replace_regex /table_id: [0-9]+/table_id: #/ /\/\* xid=.* \*\//\/* xid= *\// +--eval show binlog events in 'slave-bin.000001' from $start_pos + +--echo # +--echo ######################################################################## +--echo # INSERTs DELAYED ON MASTERs +--echo ######################################################################## +connection master; +SET SESSION binlog_annotate_rows_events = ON; +INSERT DELAYED INTO test1.t4 VALUES (1,1); +FLUSH TABLES; +SELECT * FROM test1.t4 ORDER BY a; + +sync_slave_with_master; +connection master; +sync_slave_with_master; + +--echo ######################################################################## +--echo # ON SLAVE +--echo # No Annotate_rows events should appear below +--echo ######################################################################## +FLUSH LOGS; + +--exec $MYSQL --host=127.0.0.1 --port=$SLAVE_MYPORT test -e "show binlog events in 'slave-bin.000002'" > $MYSQLTEST_VARDIR/tmp/annotated_events.txt +perl; + open F, '<', "$ENV{MYSQLTEST_VARDIR}/tmp/annotated_events.txt" or die; + binmode STDOUT; + while (defined ($_ = )) { + if (/Annotate_rows/) { + s/[0-9]+\sAnnotate_rows\s[0-9]+\s[0-9]+/# Annotate_rows # #/; + print($_); + $_ = ; + s/[0-9]+\sTable_map\s[0-9]+\s[0-9]+\stable_id:\s[0-9]+/# Table_map # # table_id: #/; + print($_); + } + } +EOF + +# Clean-up +connection master; +--disable_query_log +DROP DATABASE test1; +sync_slave_with_master; +--enable_query_log + diff --git a/mysql-test/include/binlog_start_pos.inc b/mysql-test/include/binlog_start_pos.inc new file mode 100644 index 00000000000..f6d1c4c68a3 --- /dev/null +++ b/mysql-test/include/binlog_start_pos.inc @@ -0,0 +1,26 @@ +############################################################################## +# +# binlog_start_pos is the postion of the the first event in the binary log +# which follows the Format description event. Intended to reduce test suite +# dependance on the Format description event length changes (e.g. in case +# of adding new events). Evaluated as: +# +# binlog_start_pos = 4 /* binlog header */ + +# (Format_description_log_event length) +# +# Format_description_log_event length = +# 19 /* event common header */ + +# 57 /* misc stuff in the Format description header */ + +# number of events. +# +# With current number of events = 160, +# +# binlog_start_pos = 4 + 19 + 57 + 160 = 240. +# +############################################################################## + +let $binlog_start_pos=240; +--disable_query_log +SET @binlog_start_pos=240; +--enable_query_log + diff --git a/mysql-test/include/show_binlog_events.inc b/mysql-test/include/show_binlog_events.inc index 6d8c8196102..a55ac983a52 100644 --- a/mysql-test/include/show_binlog_events.inc +++ b/mysql-test/include/show_binlog_events.inc @@ -3,7 +3,7 @@ # # Useage: # let $binlog_file= master-bin.000002; -# let $binlog_start= 106; +# let $binlog_start= 240; # let $binlog_limit= 1, 3; # source include/show_binlog_events.inc; # diff --git a/mysql-test/include/show_binlog_events2.inc b/mysql-test/include/show_binlog_events2.inc index 5dd272c562d..96e42e93f90 100644 --- a/mysql-test/include/show_binlog_events2.inc +++ b/mysql-test/include/show_binlog_events2.inc @@ -1,4 +1,4 @@ ---let $binlog_start=106 +--let $binlog_start=240 --replace_result $binlog_start --replace_column 2 # 5 # --replace_regex /\/\* xid=.* \*\//\/* XID *\// /table_id: [0-9]+/table_id: #/ diff --git a/mysql-test/r/ctype_cp932_binlog_stm.result b/mysql-test/r/ctype_cp932_binlog_stm.result index 8854a835e25..20eb73f4c42 100644 --- a/mysql-test/r/ctype_cp932_binlog_stm.result +++ b/mysql-test/r/ctype_cp932_binlog_stm.result @@ -44,8 +44,6 @@ master-bin.000001 # Query # # use `test`; INSERT INTO t4 VALUES ( NAME_CONST('in master-bin.000001 # Query # # use `test`; DROP PROCEDURE bug18293 master-bin.000001 # Query # # use `test`; DROP TABLE t4 End of 5.0 tests -SHOW BINLOG EVENTS FROM 365; -ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error Bug#44352 UPPER/LOWER function doesn't work correctly on cp932 and sjis environment. CREATE TABLE t1 (a varchar(16)) character set cp932; INSERT INTO t1 VALUES (0x8372835E),(0x8352835E); diff --git a/mysql-test/r/mysqlbinlog.result b/mysql-test/r/mysqlbinlog.result index 69d19b5e87f..545ef6e8a7e 100644 --- a/mysql-test/r/mysqlbinlog.result +++ b/mysql-test/r/mysqlbinlog.result @@ -1,15 +1,17 @@ reset master; +SET @save_binlog_size= @@global.max_binlog_size; +SET @@global.max_binlog_size= 4096; set timestamp=1000000000; drop table if exists t1,t2,t3,t4,t5,t03,t04; create table t1 (word varchar(20)); create table t2 (id int auto_increment not null primary key); insert into t1 values ("abirvalg"); insert into t2 values (); -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; insert into t1 values ("Alas"); flush logs; @@ -255,6 +257,7 @@ DELIMITER ; ROLLBACK /* added by mysqlbinlog */; /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; drop table t1,t2; +SET @@global.max_binlog_size= @save_binlog_size; flush logs; flush logs; select * from t5 /* must be (1),(1) */; @@ -377,14 +380,14 @@ LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-#-#' INTO TABLE `t1` FI /*!*/; SET TIMESTAMP=1000000000/*!*/; SET @@session.collation_database=7/*!*/; -LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-a-0' INTO TABLE `t1` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) +LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-#-#' INTO TABLE `t1` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) /*!*/; SET TIMESTAMP=1000000000/*!*/; SET @@session.collation_database=DEFAULT/*!*/; -LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-b-0' INTO TABLE `t1` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) +LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-#-#' INTO TABLE `t1` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) /*!*/; SET TIMESTAMP=1000000000/*!*/; -LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-c-0' INTO TABLE `t1` CHARACTER SET koi8r FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) +LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/SQL_LOAD_MB-#-#' INTO TABLE `t1` CHARACTER SET koi8r FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`) /*!*/; SET TIMESTAMP=1000000000/*!*/; drop table t1 diff --git a/mysql-test/std_data/words3.dat b/mysql-test/std_data/words3.dat new file mode 100644 index 00000000000..dca0819721d --- /dev/null +++ b/mysql-test/std_data/words3.dat @@ -0,0 +1,66 @@ +Aarhus +Aaron +Ababa +aback +abaft +abandon +abandoned +abandoning +abandonment +abandons +Aarhus +Aaron +Ababa +aback +abaft +abandon +abandoned +abandoning +abandonment +abandons +abase +abased +abasement +abasements +abases +abash +abashed +abashes +abashing +abasing +abate +abated +abatement +abatements +abater +abates +abating +Abba +abbe +abbey +abbeys +abbot +abbots +Abbott +abbreviate +abbreviated +abbreviates +abbreviating +abbreviation +abbreviations +Abby +abdomen +abdomens +abdominal +abduct +abducted +abduction +abductions +abductor +abductors +abducts +Abe +abed +Abel +Abelian +Abelson diff --git a/mysql-test/suite/binlog/r/binlog_row_annotate.result b/mysql-test/suite/binlog/r/binlog_row_annotate.result new file mode 100644 index 00000000000..647c07af2f8 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_row_annotate.result @@ -0,0 +1,1219 @@ +##################################################################################### +# The following Annotate_rows events should appear below: +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - INSERT INTO test3.t3 VALUES (1), (2), (3) +# - DELETE test1.t1, test2.t2 FROM <...> +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - DELETE xtest1.xt1, test2.t2 FROM <...> +##################################################################################### +show binlog events in 'master-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # DROP DATABASE IF EXISTS test1 +master-bin.000001 # Query 1 # DROP DATABASE IF EXISTS test2 +master-bin.000001 # Query 1 # DROP DATABASE IF EXISTS test3 +master-bin.000001 # Query 1 # CREATE DATABASE test1 +master-bin.000001 # Query 1 # CREATE DATABASE test2 +master-bin.000001 # Query 1 # CREATE DATABASE test3 +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Table_map 1 # table_id: # (test1.t1) +master-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Annotate_rows 1 # INSERT INTO test2.t2 VALUES (1), (2), (3) +master-bin.000001 # Table_map 1 # table_id: # (test2.t2) +master-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Annotate_rows 1 # INSERT INTO test3.t3 VALUES (1), (2), (3) +master-bin.000001 # Table_map 1 # table_id: # (test3.t3) +master-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Annotate_rows 1 # DELETE test1.t1, test2.t2 +FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 +WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3.a +master-bin.000001 # Table_map 1 # table_id: # (test1.t1) +master-bin.000001 # Table_map 1 # table_id: # (test2.t2) +master-bin.000001 # Delete_rows 1 # table_id: # +master-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Annotate_rows 1 # INSERT INTO test2.v2 VALUES (1), (2), (3) +master-bin.000001 # Table_map 1 # table_id: # (test2.t2) +master-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Annotate_rows 1 # DELETE xtest1.xt1, test2.t2 +FROM xtest1.xt1 INNER JOIN test2.t2 INNER JOIN test3.t3 +WHERE xtest1.xt1.a=test2.t2.a AND test2.t2.a=test3.t3.a +master-bin.000001 # Table_map 1 # table_id: # (test2.t2) +master-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Rotate 1 # master-bin.000002;pos=4 +# +##################################################################################### +# mysqlbinlog +# The following Annotates should appear in this output: +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - INSERT INTO test3.t3 VALUES (1), (2), (3) +# - DELETE test1.t1, test2.t2 FROM <...> (with two subsequent Table maps) +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - DELETE xtest1.xt1, test2.t2 FROM <...> (with one subsequent Table map) +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test2.t2 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test3.t3 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test3`.`t3` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test3.t3 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE test1.t1, test2.t2 +#Q> FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test2.v2 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE xtest1.xt1, test2.t2 +#Q> FROM xtest1.xt1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE xtest1.xt1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +# +##################################################################################### +# mysqlbinlog --database=test1 +# The following Annotate should appear in this output: +# - DELETE test1.t1, test2.t2 FROM <...> +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE test1.t1, test2.t2 +#Q> FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +# +##################################################################################### +# mysqlbinlog --skip-annotate-rows-events +# No Annotates should appear in this output +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test3`.`t3` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test3.t3 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +# +##################################################################################### +# mysqlbinlog --read-from-remote-server +# The following Annotates should appear in this output: +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - INSERT INTO test3.t3 VALUES (1), (2), (3) +# - DELETE test1.t1, test2.t2 FROM <...> (with two subsequent Table maps) +# - INSERT INTO test2.t2 VALUES (1), (2), (3) +# - DELETE xtest1.xt1, test2.t2 FROM <...> (with one subsequent Table map) +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test2.t2 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test3.t3 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test3`.`t3` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test3.t3 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE test1.t1, test2.t2 +#Q> FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> INSERT INTO test2.v2 VALUES (1), (2), (3) +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE xtest1.xt1, test2.t2 +#Q> FROM xtest1.xt1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE xtest1.xt1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +# +##################################################################################### +# mysqlbinlog --read-from-remote-server --database=test1 +# The following Annotate should appear in this output: +# - DELETE test1.t1, test2.t2 FROM <...> +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Annotate_rows: +#Q> DELETE test1.t1, test2.t2 +#Q> FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 +#Q> WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3 +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +# +##################################################################################### +# mysqlbinlog --read-from-remote-server --skip-annotate-rows-events +# No Annotates should appear in this output +##################################################################################### +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Start: binlog v 4, server v #.##.## created 010909 4:46:40 at startup +ROLLBACK/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +SET @@session.pseudo_thread_id=#/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=0/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +DROP DATABASE IF EXISTS test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +DROP DATABASE IF EXISTS test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test1 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test2 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +CREATE DATABASE test3 +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test1.t1 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test1.t1 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test3`.`t3` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test3.t3 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test3.t3 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test1`.`t1` mapped to number # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test1.t1 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test1.t1 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Write_rows: table id # flags: STMT_END_F +### INSERT INTO test2.t2 +### SET +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### INSERT INTO test2.t2 +### SET +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +BEGIN +/*!*/; +# at # +# at # +#010909 4:46:40 server id # end_log_pos # Table_map: `test2`.`t2` mapped to number # +#010909 4:46:40 server id # end_log_pos # Delete_rows: table id # flags: STMT_END_F +### DELETE FROM test2.t2 +### WHERE +### @1=3 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=2 /* INT meta=0 nullable=1 is_null=0 */ +### DELETE FROM test2.t2 +### WHERE +### @1=1 /* INT meta=0 nullable=1 is_null=0 */ +# at # +#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0 +SET TIMESTAMP=1000000000/*!*/; +COMMIT +/*!*/; +# at # +#010909 4:46:40 server id # end_log_pos # Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; diff --git a/mysql-test/suite/binlog/r/binlog_stm_binlog.result b/mysql-test/suite/binlog/r/binlog_stm_binlog.result index abfd6a33a7d..2c8ccf9c846 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_binlog.result +++ b/mysql-test/suite/binlog/r/binlog_stm_binlog.result @@ -333,7 +333,7 @@ master-bin.000001 # Query # # use `test`; insert into t1 values( 244 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 243 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 242 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 241 ) -master-bin.000001 # Query # # use `test`; insert into t1 values( 240 ) +master-bin.000001 # Query # # use `test`; insert into t1 values( ) master-bin.000001 # Query # # use `test`; insert into t1 values( 239 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 238 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 237 ) @@ -467,7 +467,7 @@ master-bin.000001 # Query # # use `test`; insert into t1 values( 110 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 109 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 108 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 107 ) -master-bin.000001 # Query # # use `test`; insert into t1 values( ) +master-bin.000001 # Query # # use `test`; insert into t1 values( 106 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 105 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 104 ) master-bin.000001 # Query # # use `test`; insert into t1 values( 103 ) diff --git a/mysql-test/suite/binlog/t/binlog_incident.test b/mysql-test/suite/binlog/t/binlog_incident.test index 901ac49ea24..1c526ca5980 100644 --- a/mysql-test/suite/binlog/t/binlog_incident.test +++ b/mysql-test/suite/binlog/t/binlog_incident.test @@ -4,6 +4,7 @@ source include/have_log_bin.inc; source include/have_debug.inc; +source include/binlog_start_pos.inc; let $MYSQLD_DATADIR= `select @@datadir`; RESET MASTER; @@ -20,7 +21,7 @@ REPLACE INTO t1 VALUES (4); DROP TABLE t1; FLUSH LOGS; -exec $MYSQL_BINLOG --start-position=106 $MYSQLD_DATADIR/master-bin.000001 >$MYSQLTEST_VARDIR/tmp/binlog_incident-bug44442.sql; +exec $MYSQL_BINLOG --start-position=$binlog_start_pos $MYSQLD_DATADIR/master-bin.000001 >$MYSQLTEST_VARDIR/tmp/binlog_incident-bug44442.sql; --disable_query_log eval SELECT cont LIKE '%RELOAD DATABASE; # Shall generate syntax error%' AS `Contain RELOAD DATABASE` FROM (SELECT load_file('$MYSQLTEST_VARDIR/tmp/binlog_incident-bug44442.sql') AS cont) AS tbl; --enable_query_log diff --git a/mysql-test/suite/binlog/t/binlog_killed.test b/mysql-test/suite/binlog/t/binlog_killed.test index ce6d344af32..a2bd7d8095a 100644 --- a/mysql-test/suite/binlog/t/binlog_killed.test +++ b/mysql-test/suite/binlog/t/binlog_killed.test @@ -1,5 +1,6 @@ -- source include/have_innodb.inc -- source include/have_binlog_format_statement.inc +-- source include/binlog_start_pos.inc # You cannot use `KILL' with the Embedded MySQL Server library, # because the embedded server merely runs inside the threads of the host @@ -51,7 +52,8 @@ reap; let $rows= `select count(*) from t2 /* must be 2 or 0 */`; let $MYSQLD_DATADIR= `select @@datadir`; ---exec $MYSQL_BINLOG --force-if-open --start-position=134 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/kill_query_calling_sp.binlog +let $start_pos= `select @binlog_start_pos + 28`; +--exec $MYSQL_BINLOG --force-if-open --start-position=$start_pos $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/kill_query_calling_sp.binlog --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval select (@a:=load_file("$MYSQLTEST_VARDIR/tmp/kill_query_calling_sp.binlog")) diff --git a/mysql-test/suite/binlog/t/binlog_killed_simulate.test b/mysql-test/suite/binlog/t/binlog_killed_simulate.test index b87d47559fe..c5c14137fbb 100644 --- a/mysql-test/suite/binlog/t/binlog_killed_simulate.test +++ b/mysql-test/suite/binlog/t/binlog_killed_simulate.test @@ -1,5 +1,6 @@ -- source include/have_debug.inc -- source include/have_binlog_format_statement.inc +-- source include/binlog_start_pos.inc # # bug#27571 asynchronous setting mysql_$query()'s local error and # Query_log_event::error_code @@ -24,7 +25,7 @@ update t1 set a=2 /* will be "killed" after work has been done */; # for some constants like the offset of the first real event # that is different between severs versions. let $MYSQLD_DATADIR= `select @@datadir`; ---exec $MYSQL_BINLOG --force-if-open --start-position=106 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--exec $MYSQL_BINLOG --force-if-open --start-position=$binlog_start_pos $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval select (@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) diff --git a/mysql-test/suite/binlog/t/binlog_row_annotate-master.opt b/mysql-test/suite/binlog/t/binlog_row_annotate-master.opt new file mode 100644 index 00000000000..24d88646b3b --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_row_annotate-master.opt @@ -0,0 +1 @@ +--timezone=GMT-3 --binlog-do-db=test1 --binlog-do-db=test2 --binlog-do-db=test3 diff --git a/mysql-test/suite/binlog/t/binlog_row_annotate.test b/mysql-test/suite/binlog/t/binlog_row_annotate.test new file mode 100644 index 00000000000..c5db9ef2148 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_row_annotate.test @@ -0,0 +1,189 @@ +############################################################################### +# WL47: Store in binlog text of statements that caused RBR events +# new event: ANNOTATE_ROWS_EVENT +# new master option: --binlog-annotate-rows-events +# new mysqlbinlog option: --skip-annotate-rows-events +# +# Intended to test that: +# *** If the --binlog-annotate-rows-events option is switched on on master +# then Annotate_rows events: +# - are generated; +# - are genrated only once for "multi-table-maps" rbr queries; +# - are not generated when the corresponding queries are filtered away; +# - are generated when the corresponding queries are filtered away partialy +# (e.g. in case of multi-delete). +# *** Annotate_rows events are printed by mysqlbinlog started without +# --skip-annotate-rows-events options both in remote and local cases. +# *** Annotate_rows events are not printed by mysqlbinlog started with +# --skip-annotate-rows-events options both in remote and local cases. +############################################################################### + +--source include/have_log_bin.inc +--source include/have_binlog_format_row.inc +--source include/binlog_start_pos.inc + +--disable_query_log + +# Fix timestamp to avoid varying results +SET timestamp=1000000000; + +# Delete all existing binary logs +RESET MASTER; + +--disable_warnings +DROP DATABASE IF EXISTS test1; +DROP DATABASE IF EXISTS test2; +DROP DATABASE IF EXISTS test3; +DROP DATABASE IF EXISTS xtest1; +DROP DATABASE IF EXISTS xtest2; +--enable_warnings + +CREATE DATABASE test1; +CREATE TABLE test1.t1(a int); + +CREATE DATABASE test2; +CREATE TABLE test2.t2(a int); +CREATE VIEW test2.v2 AS SELECT * FROM test2.t2; + +CREATE DATABASE test3; +CREATE TABLE test3.t3(a int); + +CREATE DATABASE xtest1; +CREATE TABLE xtest1.xt1(a int); + +CREATE DATABASE xtest2; +CREATE TABLE xtest2.xt2(a int); + +# By default SESSION binlog_annotate_rows_events = OFF + +INSERT INTO test1.t1 VALUES (1), (2), (3); + +SET SESSION binlog_annotate_rows_events = ON; + +INSERT INTO test2.t2 VALUES (1), (2), (3); +INSERT INTO test3.t3 VALUES (1), (2), (3); + +# This query generates two Table maps but the Annotate +# event should appear only once before the first Table map +DELETE test1.t1, test2.t2 + FROM test1.t1 INNER JOIN test2.t2 INNER JOIN test3.t3 + WHERE test1.t1.a=test2.t2.a AND test2.t2.a=test3.t3.a; + +# This event should be filtered out together with Annotate event +INSERT INTO xtest1.xt1 VALUES (1), (2), (3); + +# This event should pass the filter +INSERT INTO test2.v2 VALUES (1), (2), (3); + +# This event should pass the filter only for test2.t2 part +DELETE xtest1.xt1, test2.t2 + FROM xtest1.xt1 INNER JOIN test2.t2 INNER JOIN test3.t3 + WHERE xtest1.xt1.a=test2.t2.a AND test2.t2.a=test3.t3.a; + +# These events should be filtered out together with Annotate events +INSERT INTO xtest1.xt1 VALUES (1), (2), (3); +INSERT INTO xtest2.xt2 VALUES (1), (2), (3); +DELETE xtest1.xt1, xtest2.xt2 + FROM xtest1.xt1 INNER JOIN xtest2.xt2 INNER JOIN test3.t3 + WHERE xtest1.xt1.a=xtest2.xt2.a AND xtest2.xt2.a=test3.t3.a; + +FLUSH LOGS; +--enable_query_log + +--echo ##################################################################################### +--echo # The following Annotate_rows events should appear below: +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - INSERT INTO test3.t3 VALUES (1), (2), (3) +--echo # - DELETE test1.t1, test2.t2 FROM <...> +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - DELETE xtest1.xt1, test2.t2 FROM <...> +--echo ##################################################################################### + +let $start_pos= `select @binlog_start_pos`; +--replace_column 2 # 5 # +--replace_result $start_pos +--replace_regex /table_id: [0-9]+/table_id: #/ /\/\* xid=.* \*\//\/* xid= *\// +--eval show binlog events in 'master-bin.000001' from $start_pos + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog +--echo # The following Annotates should appear in this output: +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - INSERT INTO test3.t3 VALUES (1), (2), (3) +--echo # - DELETE test1.t1, test2.t2 FROM <...> (with two subsequent Table maps) +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - DELETE xtest1.xt1, test2.t2 FROM <...> (with one subsequent Table map) +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows -v -v $MYSQLD_DATADIR/master-bin.000001 + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog --database=test1 +--echo # The following Annotate should appear in this output: +--echo # - DELETE test1.t1, test2.t2 FROM <...> +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows --database=test1 -v -v $MYSQLD_DATADIR/master-bin.000001 + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog --skip-annotate-rows-events +--echo # No Annotates should appear in this output +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows --skip-annotate-rows-events -v -v $MYSQLD_DATADIR/master-bin.000001 + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog --read-from-remote-server +--echo # The following Annotates should appear in this output: +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - INSERT INTO test3.t3 VALUES (1), (2), (3) +--echo # - DELETE test1.t1, test2.t2 FROM <...> (with two subsequent Table maps) +--echo # - INSERT INTO test2.t2 VALUES (1), (2), (3) +--echo # - DELETE xtest1.xt1, test2.t2 FROM <...> (with one subsequent Table map) +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows -v -v --read-from-remote-server --user=root --host=localhost --port=$MASTER_MYPORT master-bin.000001 + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog --read-from-remote-server --database=test1 +--echo # The following Annotate should appear in this output: +--echo # - DELETE test1.t1, test2.t2 FROM <...> +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows --database=test1 -v -v --read-from-remote-server --user=root --host=localhost --port=$MASTER_MYPORT master-bin.000001 + +--echo # +--echo ##################################################################################### +--echo # mysqlbinlog --read-from-remote-server --skip-annotate-rows-events +--echo # No Annotates should appear in this output +--echo ##################################################################################### + +let $MYSQLD_DATADIR= `select @@datadir`; +--replace_regex /server id [0-9]*/server id #/ /server v [^ ]*/server v #.##.##/ /exec_time=[0-9]*/exec_time=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ +--exec $MYSQL_BINLOG --base64-output=decode-rows --skip-annotate-rows-events -v -v --read-from-remote-server --user=root --host=localhost --port=$MASTER_MYPORT master-bin.000001 + +# Clean-up + +--disable_query_log +DROP DATABASE test1; +DROP DATABASE test2; +DROP DATABASE test3; +DROP DATABASE xtest1; +DROP DATABASE xtest2; +--enable_query_log + diff --git a/mysql-test/suite/maria/r/maria-connect.result b/mysql-test/suite/maria/r/maria-connect.result index a02c29f3d5f..45675deb098 100644 --- a/mysql-test/suite/maria/r/maria-connect.result +++ b/mysql-test/suite/maria/r/maria-connect.result @@ -14,10 +14,10 @@ a 2 3 4 -SHOW BINLOG EVENTS FROM 106; +SHOW BINLOG EVENTS FROM ; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 106 Query 1 204 use `test`; CREATE TABLE t1 (a int primary key) -master-bin.000001 204 Query 1 295 use `test`; insert t1 values (1),(2),(3) -master-bin.000001 295 Query 1 386 use `test`; insert t1 values (4),(2),(5) +master-bin.000001 # Query 1 # use `test`; CREATE TABLE t1 (a int primary key) +master-bin.000001 # Query 1 # use `test`; insert t1 values (1),(2),(3) +master-bin.000001 # Query 1 # use `test`; insert t1 values (4),(2),(5) drop table t1; set binlog_format=default; diff --git a/mysql-test/suite/maria/t/maria-connect.test b/mysql-test/suite/maria/t/maria-connect.test index 9efb5844bc2..a1e9bbce4f2 100644 --- a/mysql-test/suite/maria/t/maria-connect.test +++ b/mysql-test/suite/maria/t/maria-connect.test @@ -4,6 +4,9 @@ -- source include/have_maria.inc -- source include/have_log_bin.inc +-- source include/binlog_start_pos.inc + +let $start_pos= `select @binlog_start_pos`; let $default=`select @@global.storage_engine`; set global storage_engine=aria; @@ -27,7 +30,9 @@ insert t1 values (1),(2),(3); --error ER_DUP_ENTRY insert t1 values (4),(2),(5); select * from t1; -SHOW BINLOG EVENTS FROM 106; +--replace_result $start_pos +--replace_column 2 # 5 # +eval SHOW BINLOG EVENTS FROM $start_pos; drop table t1; set binlog_format=default; diff --git a/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result b/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result index 1485389204b..a894e1a4971 100644 --- a/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result +++ b/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result @@ -55,7 +55,7 @@ Checking that both slave threads are running. *** Test lock wait timeout *** include/stop_slave.inc DELETE FROM t2; -CHANGE MASTER TO MASTER_LOG_POS=MASTER_POS_BEGIN; +CHANGE MASTER TO MASTER_LOG_POS=; BEGIN; SELECT * FROM t1 FOR UPDATE; a @@ -81,7 +81,7 @@ SET @my_max_relay_log_size= @@global.max_relay_log_size; SET global max_relay_log_size=0; include/stop_slave.inc DELETE FROM t2; -CHANGE MASTER TO MASTER_LOG_POS=MASTER_POS_BEGIN; +CHANGE MASTER TO MASTER_LOG_POS=; BEGIN; SELECT * FROM t1 FOR UPDATE; a diff --git a/mysql-test/suite/rpl/r/rpl_row_annotate_do.result b/mysql-test/suite/rpl/r/rpl_row_annotate_do.result new file mode 100644 index 00000000000..34405b9cbba --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_annotate_do.result @@ -0,0 +1,144 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +######################################################################## +# TABLES ON MASTER +######################################################################## +SELECT * FROM t1 ORDER BY a; +a b +0 1 +SELECT * FROM t2 ORDER BY a; +a b +SELECT * FROM t3 ORDER BY a; +a b +1 1 +2 2 +3 3 +SELECT * FROM t5 ORDER BY a; +a b +1 foo +2 bar +3 baz +4 gås +5 gås +######################################################################## +# TABLES ON SLAVE: should be the same as on master +######################################################################## +SELECT * FROM t1 ORDER BY a; +a b +0 1 +SELECT * FROM t2 ORDER BY a; +a b +SELECT * FROM t3 ORDER BY a; +a b +1 1 +2 2 +3 3 +SELECT * FROM t5 ORDER BY a; +a b +1 foo +2 bar +3 baz +4 gås +5 gås +######################################################################## +# EVENTS ON SLAVE +# The following Annotate_rows events should appear below: +# - UPDATE t1 SET b = b + 1; +# - REPLACE t1 VALUES (1,1), (2,2), (3,3); +# - INSERT INTO t2 VALUES (1,1), (2,2), (3,3) +# - INSERT INTO t3 VALUES (1,1), (2,2), (3,3) +# - DELETE t1, t2 FROM <...> +# - INSERT INTO t2 VALUES (1,1), (2,2), (3,3) +# - DELETE xt1, t2 FROM <...> +# - INSERT INTO t5(b) VALUES <...> (3 instances) +######################################################################## +FLUSH LOGS; +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Query 1 # DROP DATABASE IF EXISTS test1 +slave-bin.000001 # Query 1 # CREATE DATABASE test1 +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t1(a int primary key, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t2(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t3(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t4(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t5 ( +a INT PRIMARY KEY AUTO_INCREMENT, +b VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_bin +) +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # UPDATE t1 SET b = b + 1 +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Update_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # REPLACE t1 VALUES (1,1), (2,2), (3,3) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Update_rows 1 # table_id: # +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t2 VALUES (1,1), (2,2), (3,3) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t3 VALUES (1,1), (2,2), (3,3) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t3) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # DELETE t1, t2 FROM t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.a=t2.a AND t2.a=t3.a +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Delete_rows 1 # table_id: # +slave-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t2 VALUES (1,1), (2,2), (3,3) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # DELETE xt1, t2 FROM xt1 INNER JOIN t2 INNER JOIN t3 WHERE xt1.a=t2.a AND t2.a=t3.a +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t5(b) VALUES ('foo'), ('bar'), ('baz') +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t5(b) VALUES ('gås') +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Annotate_rows 1 # INSERT INTO t5(b) VALUES ('gÃ¥s') +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Rotate 2 # slave-bin.000002;pos=4 +# +######################################################################## +# INSERTs DELAYED ON MASTERs +######################################################################## +SET SESSION binlog_annotate_rows_events = ON; +INSERT DELAYED INTO test1.t4 VALUES (1,1); +FLUSH TABLES; +SELECT * FROM test1.t4 ORDER BY a; +a b +1 1 +######################################################################## +# ON SLAVE +# No Annotate_rows events should appear below +######################################################################## +FLUSH LOGS; diff --git a/mysql-test/suite/rpl/r/rpl_row_annotate_dont.result b/mysql-test/suite/rpl/r/rpl_row_annotate_dont.result new file mode 100644 index 00000000000..0c04c6abf61 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_annotate_dont.result @@ -0,0 +1,126 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +######################################################################## +# TABLES ON MASTER +######################################################################## +SELECT * FROM t1 ORDER BY a; +a b +0 1 +SELECT * FROM t2 ORDER BY a; +a b +SELECT * FROM t3 ORDER BY a; +a b +1 1 +2 2 +3 3 +SELECT * FROM t5 ORDER BY a; +a b +1 foo +2 bar +3 baz +4 gås +5 gås +######################################################################## +# TABLES ON SLAVE: should be the same as on master +######################################################################## +SELECT * FROM t1 ORDER BY a; +a b +0 1 +SELECT * FROM t2 ORDER BY a; +a b +SELECT * FROM t3 ORDER BY a; +a b +1 1 +2 2 +3 3 +SELECT * FROM t5 ORDER BY a; +a b +1 foo +2 bar +3 baz +4 gås +5 gås +######################################################################## +# EVENTS ON SLAVE +# No Annotate_rows events should appear below +######################################################################## +FLUSH LOGS; +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Query 1 # DROP DATABASE IF EXISTS test1 +slave-bin.000001 # Query 1 # CREATE DATABASE test1 +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t1(a int primary key, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t2(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t3(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t4(a int, b int) +slave-bin.000001 # Query 1 # use `test1`; CREATE TABLE t5 ( +a INT PRIMARY KEY AUTO_INCREMENT, +b VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_bin +) +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Update_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Update_rows 1 # table_id: # +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t3) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Table_map 1 # table_id: # (test1.t1) +slave-bin.000001 # Delete_rows 1 # table_id: # +slave-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t2) +slave-bin.000001 # Delete_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Query 1 # BEGIN +slave-bin.000001 # Table_map 1 # table_id: # (test1.t5) +slave-bin.000001 # Write_rows 1 # table_id: # flags: STMT_END_F +slave-bin.000001 # Query 1 # COMMIT +slave-bin.000001 # Rotate 2 # slave-bin.000002;pos=4 +# +######################################################################## +# INSERTs DELAYED ON MASTERs +######################################################################## +SET SESSION binlog_annotate_rows_events = ON; +INSERT DELAYED INTO test1.t4 VALUES (1,1); +FLUSH TABLES; +SELECT * FROM test1.t4 ORDER BY a; +a b +1 1 +######################################################################## +# ON SLAVE +# No Annotate_rows events should appear below +######################################################################## +FLUSH LOGS; diff --git a/mysql-test/suite/rpl/r/rpl_row_conflicts.result b/mysql-test/suite/rpl/r/rpl_row_conflicts.result index 0f15bfc7156..70303bf0159 100644 --- a/mysql-test/suite/rpl/r/rpl_row_conflicts.result +++ b/mysql-test/suite/rpl/r/rpl_row_conflicts.result @@ -24,7 +24,7 @@ a 1 [on slave] ---- Wait until slave stops with an error ---- -Last_SQL_Error = Could not execute Write_rows event on table test.t1; Duplicate entry '1' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log master-bin.000001, end_log_pos 346 (expected "duplicate key" error) +Last_SQL_Error = Could not execute Write_rows event on table test.t1; Duplicate entry '1' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log master-bin.000001, end_log_pos 480 (expected "duplicate key" error) SELECT * FROM t1; a 1 @@ -50,7 +50,7 @@ SELECT * FROM t1; a [on slave] ---- Wait until slave stops with an error ---- -Last_SQL_Error = Could not execute Delete_rows event on table test.t1; Can't find record in 't1', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log master-bin.000001, end_log_pos 982 (expected "can't find record" error) +Last_SQL_Error = Could not execute Delete_rows event on table test.t1; Can't find record in 't1', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log master-bin.000001, end_log_pos 1116 (expected "can't find record" error) SELECT * FROM t1; a ---- Resolve the conflict on the slave and restart SQL thread ---- diff --git a/mysql-test/suite/rpl/t/rpl_row_annotate_do-slave.opt b/mysql-test/suite/rpl/t/rpl_row_annotate_do-slave.opt new file mode 100644 index 00000000000..aa3af621897 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_annotate_do-slave.opt @@ -0,0 +1 @@ +--log-slave-updates --replicate-annotate-rows-events --replicate-ignore-table=test1.xt1 --replicate-ignore-table=test1.xt2 \ No newline at end of file diff --git a/mysql-test/suite/rpl/t/rpl_row_annotate_do.test b/mysql-test/suite/rpl/t/rpl_row_annotate_do.test new file mode 100644 index 00000000000..b61ce0ab6d8 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_annotate_do.test @@ -0,0 +1,16 @@ +############################################################################### +# WL47: Store in binlog text of statements that caused RBR events +# Wrapper for extra/rpl/rpl_row_annotate.test. +# Intended to test that if the --replicate-annotate-rows-events option +# is switched on on slave then Annotate_events: +# - are reproduced on slave +# - are reproduced only once for "multi-table-maps" rbr queries +# - are not reproduced when the corresponding queries are filtered away +# on replication +# - are reproduced when the corresponding queries are filtered away partialy +# (e.g. in case of multi-delete) +# - are not generated on slave for queries that are not annotated on master. +############################################################################### + +--source include/have_binlog_format_row.inc +--source extra/rpl_tests/rpl_row_annotate.test diff --git a/mysql-test/suite/rpl/t/rpl_row_annotate_dont-slave.opt b/mysql-test/suite/rpl/t/rpl_row_annotate_dont-slave.opt new file mode 100644 index 00000000000..74ac3bfefcb --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_annotate_dont-slave.opt @@ -0,0 +1 @@ +--log-slave-updates --replicate-ignore-table=test1.xt1 --replicate-ignore-table=test1.xt2 \ No newline at end of file diff --git a/mysql-test/suite/rpl/t/rpl_row_annotate_dont.test b/mysql-test/suite/rpl/t/rpl_row_annotate_dont.test new file mode 100644 index 00000000000..56765c591aa --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_annotate_dont.test @@ -0,0 +1,9 @@ +############################################################################### +# WL47: Store in binlog text of statements that caused RBR events +# Wrapper for extra/rpl/rpl_row_annotate.test. +# Intended to test that if the --replicate-annotate-rows-events option +# is switched off on slave then Annotate_events are not reproduced. +############################################################################### + +--source include/have_binlog_format_row.inc +--source extra/rpl_tests/rpl_row_annotate.test diff --git a/mysql-test/suite/rpl/t/rpl_row_flsh_tbls.test b/mysql-test/suite/rpl/t/rpl_row_flsh_tbls.test index 064a7c89dbe..8764b35a537 100644 --- a/mysql-test/suite/rpl/t/rpl_row_flsh_tbls.test +++ b/mysql-test/suite/rpl/t/rpl_row_flsh_tbls.test @@ -1,7 +1,8 @@ # depends on the binlog output -- source include/have_binlog_format_row.inc +--source include/binlog_start_pos.inc -let $rename_event_pos= 925; +let $rename_event_pos= `select @binlog_start_pos + 819`; # Bug#18326: Do not lock table for writing during prepare of statement # The use of the ps protocol causes extra table maps in the binlog, so diff --git a/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test b/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test index 7515249fe0c..ecbca6fc077 100644 --- a/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test +++ b/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test @@ -164,15 +164,18 @@ connection master; remove_file $MYSQLTEST_VARDIR/tmp/master.sql; +--source include/binlog_start_pos.inc # this test for position option -# By setting this position to 416, we should only get the create of t3 +# By setting this position to start_binlog_pos + 310, we should only get the create of t3 +let $start_pos= `select @binlog_start_pos + 310`; +let $stop_pos= `select @binlog_start_pos + 463`; --disable_query_log select "--- Test 2 position test --" as ""; --enable_query_log let $MYSQLD_DATADIR= `select @@datadir;`; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --position=416 --stop-position=569 $MYSQLD_DATADIR/master-bin.000001 +--exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --position=$start_pos --stop-position=$stop_pos $MYSQLD_DATADIR/master-bin.000001 # These are tests for remote binlog. # They should return the same as previous test. @@ -183,7 +186,7 @@ select "--- Test 3 First Remote test --" as ""; # This is broken now --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --stop-position=569 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 +--exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --stop-position=$stop_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 # This part is disabled due to bug #17654 @@ -259,7 +262,7 @@ connection master; select "--- Test 5 LOAD DATA --" as ""; --enable_query_log --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --stop-position=106 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000002 +--exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --stop-position=$binlog_start_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000002 # Bug#7853 (mysqlbinlog does not accept input from stdin) @@ -267,14 +270,17 @@ select "--- Test 5 LOAD DATA --" as ""; select "--- Test 6 reading stdin --" as ""; --enable_query_log let $MYSQLD_DATADIR= `select @@datadir;`; +let $stop_pos= `select @binlog_start_pos + 463`; --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR ---exec $MYSQL_BINLOG --short-form --stop-position=569 - < $MYSQLD_DATADIR/master-bin.000001 +--exec $MYSQL_BINLOG --short-form --stop-position=$stop_pos - < $MYSQLD_DATADIR/master-bin.000001 --disable_query_log select "--- Test 7 reading stdin w/position --" as ""; --enable_query_log +let $start_pos= `select @binlog_start_pos + 310`; +let $stop_pos= `select @binlog_start_pos + 463`; --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR ---exec $MYSQL_BINLOG --short-form --position=416 --stop-position=569 - < $MYSQLD_DATADIR/master-bin.000001 +--exec $MYSQL_BINLOG --short-form --position=$start_pos --stop-position=$stop_pos - < $MYSQLD_DATADIR/master-bin.000001 # Bug#16217 (mysql client did not know how not switch its internal charset) --disable_query_log diff --git a/mysql-test/suite/rpl/t/rpl_stm_flsh_tbls.test b/mysql-test/suite/rpl/t/rpl_stm_flsh_tbls.test index dad61002be4..cffa70ac9ce 100644 --- a/mysql-test/suite/rpl/t/rpl_stm_flsh_tbls.test +++ b/mysql-test/suite/rpl/t/rpl_stm_flsh_tbls.test @@ -1,7 +1,8 @@ # depends on the binlog output --source include/have_binlog_format_mixed_or_statement.inc +--source include/binlog_start_pos.inc -let $rename_event_pos= 684; +let $rename_event_pos= `select @binlog_start_pos + 578`; -- source extra/rpl_tests/rpl_flsh_tbls.test # End of 4.1 tests diff --git a/mysql-test/t/ctype_cp932_binlog_stm.test b/mysql-test/t/ctype_cp932_binlog_stm.test index f3038ccfa61..b1f34ec22d4 100644 --- a/mysql-test/t/ctype_cp932_binlog_stm.test +++ b/mysql-test/t/ctype_cp932_binlog_stm.test @@ -28,14 +28,6 @@ delimiter ;| --echo End of 5.0 tests -# -# #28436: Incorrect position in SHOW BINLOG EVENTS causes server coredump -# Note: 364 is a magic position (found experimentally, depends on -# the log's contents) that caused the server crash. - ---error 1220 -SHOW BINLOG EVENTS FROM 365; - --echo Bug#44352 UPPER/LOWER function doesn't work correctly on cp932 and sjis environment. CREATE TABLE t1 (a varchar(16)) character set cp932; INSERT INTO t1 VALUES (0x8372835E),(0x8352835E); diff --git a/mysql-test/t/mysqlbinlog-master.opt b/mysql-test/t/mysqlbinlog-master.opt deleted file mode 100644 index ac1a87c73b3..00000000000 --- a/mysql-test/t/mysqlbinlog-master.opt +++ /dev/null @@ -1 +0,0 @@ ---max-binlog-size=4096 diff --git a/mysql-test/t/mysqlbinlog.test b/mysql-test/t/mysqlbinlog.test index 783708cc3b2..3c58affa0f3 100644 --- a/mysql-test/t/mysqlbinlog.test +++ b/mysql-test/t/mysqlbinlog.test @@ -3,10 +3,18 @@ -- source include/have_binlog_format_statement.inc -- source include/have_log_bin.inc +-- source include/binlog_start_pos.inc # Deletes all the binary logs reset master; +# We need small binlog size to break the last LOAD DATA INFILE below so that +# the corresponding Begin_load_query will be written to master-bin.000001 +# while the Execute_load_query will be written to master-bin.000002. + +SET @save_binlog_size= @@global.max_binlog_size; +SET @@global.max_binlog_size= 4096; + # we need this for getting fixed timestamps inside of this test set timestamp=1000000000; @@ -26,13 +34,15 @@ insert into t2 values (); # test for load data and load data distributed among the several # files (we need to fill up first binlog) -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; -load data infile '../../std_data/words.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; +load data infile '../../std_data/words3.dat' into table t1; # simple query to show more in second binlog insert into t1 values ("Alas"); + +### Starting master-bin.000003 flush logs; # delimiters are for easier debugging in future @@ -46,7 +56,7 @@ select "--- Local --" as ""; # let $MYSQLD_DATADIR= `select @@datadir`; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ $MYSQLD_DATADIR/master-bin.000001 # this should not fail but shouldn't produce any working statements @@ -54,7 +64,7 @@ let $MYSQLD_DATADIR= `select @@datadir`; select "--- Broken LOAD DATA --" as ""; --enable_query_log --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ $MYSQLD_DATADIR/master-bin.000002 2> /dev/null # this should show almost nothing @@ -62,17 +72,17 @@ select "--- Broken LOAD DATA --" as ""; select "--- --database --" as ""; --enable_query_log --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --database=nottest $MYSQLD_DATADIR/master-bin.000001 2> /dev/null # this test for position option --disable_query_log select "--- --position --" as ""; --enable_query_log +let $start_pos= `select @binlog_start_pos + 227`; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ ---exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --position=332 $MYSQLD_DATADIR/master-bin.000002 - +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ +--exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --position=$start_pos $MYSQLD_DATADIR/master-bin.000002 # These are tests for remote binlog. # They should return the same as previous test. @@ -83,7 +93,7 @@ select "--- Remote --" as ""; # This is broken now --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 # This is broken too @@ -91,7 +101,7 @@ select "--- Remote --" as ""; select "--- Broken LOAD DATA --" as ""; --enable_query_log --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000002 2> /dev/null # And this too ! (altough it is documented) @@ -99,34 +109,39 @@ select "--- Broken LOAD DATA --" as ""; select "--- --database --" as ""; --enable_query_log --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT --database=nottest master-bin.000001 2> /dev/null # Strangely but this works --disable_query_log select "--- --position --" as ""; --enable_query_log +let $start_pos= `select @binlog_start_pos + 227`; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ ---exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --read-from-remote-server --position=332 --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000002 +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ +--exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ --read-from-remote-server --position=$start_pos --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000002 # Bug#7853 mysqlbinlog does not accept input from stdin --disable_query_log select "--- reading stdin --" as ""; --enable_query_log --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form - < $MYSQL_TEST_DIR/std_data/trunc_binlog.000001 --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --position=79 - < $MYSQL_TEST_DIR/std_data/trunc_binlog.000001 drop table t1,t2; +SET @@global.max_binlog_size= @save_binlog_size; + # # Bug#14157 utf8 encoding in binlog without set character_set_client # +### Starting master-bin.000004 flush logs; + --write_file $MYSQLTEST_VARDIR/tmp/bug14157.sql create table if not exists t5 (a int); set names latin1; @@ -140,6 +155,8 @@ EOF # resulted binlog, parly consisting of multi-byte utf8 chars, # must be digestable for both client and server. In 4.1 the client # should use default-character-set same as the server. + +### Starting master-bin.000005 flush logs; --exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000004 | $MYSQL select * from t5 /* must be (1),(1) */; @@ -150,6 +167,8 @@ drop table t5; # Check that a dump created by mysqlbinlog reproduces # lc_time_names dependent values correctly # + +### Starting master-bin.000006 flush logs; create table t5 (c1 int, c2 varchar(128) character set latin1 not null); insert into t5 values (1, date_format('2001-01-01','%W')); @@ -158,7 +177,10 @@ insert into t5 values (2, date_format('2001-01-01','%W')); set lc_time_names=en_US; insert into t5 values (3, date_format('2001-01-01','%W')); select * from t5 order by c1; + +### Starting master-bin.000007 flush logs; + drop table t5; --exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000006 | $MYSQL select * from t5 order by c1; @@ -170,7 +192,10 @@ drop table t5; --disable_warnings drop procedure if exists p1; --enable_warnings + +### Starting master-bin.000008 flush logs; + delimiter //; create procedure p1() begin @@ -178,12 +203,15 @@ select 1; end; // delimiter ;// + +### Starting master-bin.000009 flush logs; + call p1(); drop procedure p1; --error ER_SP_DOES_NOT_EXIST call p1(); ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000008 --exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000008 | $MYSQL call p1(); @@ -202,7 +230,9 @@ drop procedure p1; # (LOAD DATA INFILE need it) # +### Starting master-bin.000010 flush logs; + create table t1 (a varchar(64) character set utf8); load data infile '../../std_data/loaddata6.dat' into table t1; set character_set_database=koi8r; @@ -217,9 +247,12 @@ load data infile '../../std_data/loaddata6.dat' into table t1; load data infile '../../std_data/loaddata6.dat' into table t1 character set koi8r; select hex(a) from t1; drop table t1; + +### Starting master-bin.000011 flush logs; + --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR ---replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ +--replace_regex /SQL_LOAD_MB-[0-9a-f]+-[0-9a-f]+/SQL_LOAD_MB-#-#/ --exec $MYSQL_BINLOG --short-form --local-load=$MYSQLTEST_VARDIR/tmp/ $MYSQLD_DATADIR/master-bin.000010 # @@ -229,9 +262,14 @@ flush logs; CREATE TABLE t1 (c1 CHAR(10)); # we need this for getting fixed timestamps inside of this test +### Starting master-bin.000012 FLUSH LOGS; + INSERT INTO t1 VALUES ('0123456789'); + +### Starting master-bin.000013 FLUSH LOGS; + DROP TABLE t1; # We create a table, patch, and load the output into it @@ -257,11 +295,16 @@ DROP TABLE patch; # # Bug#29928 incorrect connection_id() restoring from mysqlbinlog out # +### Starting master-bin.000014 FLUSH LOGS; + CREATE TABLE t1(a INT); INSERT INTO t1 VALUES(connection_id()); let $a= `SELECT a FROM t1`; + +### Starting master-bin.000015 FLUSH LOGS; + let $outfile= $MYSQLTEST_VARDIR/tmp/bug29928.sql; --exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000014 > $outfile DROP TABLE t1; @@ -281,11 +324,12 @@ error 1; exec $MYSQL_BINLOG $MYSQL_TEST_DIR/std_data/corrupt-relay-bin.000624 > $MYSQLTEST_VARDIR/tmp/bug31793.sql; --remove_file $MYSQLTEST_VARDIR/tmp/bug31793.sql - # # Test --disable-force-if-open and --force-if-open # +### Starting master-bin.000016 FLUSH LOGS; + --error 1 --exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000016 >/dev/null 2>/dev/null --exec $MYSQL_BINLOG --force-if-open $MYSQLD_DATADIR/master-bin.000016 >/dev/null 2>/dev/null @@ -300,9 +344,15 @@ GRANT SELECT ON mysqltest1.* TO untrusted@localhost; SHOW GRANTS FOR untrusted@localhost; USE mysqltest1; CREATE TABLE t1 (a INT, b CHAR(64)); + +### Starting master-bin.000017 flush logs; + INSERT INTO t1 VALUES (1,USER()); + +### Starting master-bin.000018 flush logs; + echo mysqlbinlog var/log/master-bin.000017 > var/tmp/bug31611.sql; exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000017 > $MYSQLTEST_VARDIR/tmp/bug31611.sql; connect (unsecure,localhost,untrusted,,mysqltest1); @@ -326,14 +376,20 @@ DROP USER untrusted@localhost; connection default; USE test; SET BINLOG_FORMAT = STATEMENT; + +### Starting master-bin.000019 FLUSH LOGS; + CREATE TABLE t1 (a_real FLOAT, an_int INT, a_decimal DECIMAL(5,2), a_string CHAR(32)); SET @a_real = rand(20) * 1000; SET @an_int = 1000; SET @a_decimal = CAST(rand(19) * 999 AS DECIMAL(5,2)); SET @a_string = 'Just a test'; INSERT INTO t1 VALUES (@a_real, @an_int, @a_decimal, @a_string); + +### Starting master-bin.000020 FLUSH LOGS; + query_vertical SELECT * FROM t1; DROP TABLE t1; @@ -357,6 +413,7 @@ eval SET @@global.server_id= $s_id_max; RESET MASTER; FLUSH LOGS; + --exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 > $binlog_file --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval SELECT diff --git a/mysql-test/t/mysqlbinlog2.test b/mysql-test/t/mysqlbinlog2.test index d6be029ea56..57adde7b975 100644 --- a/mysql-test/t/mysqlbinlog2.test +++ b/mysql-test/t/mysqlbinlog2.test @@ -3,7 +3,7 @@ # TODO: Need to look at making row based version once new binlog client is complete. -- source include/have_binlog_format_mixed_or_statement.inc - +-- source include/binlog_start_pos.inc --disable_warnings drop table if exists t1; @@ -50,15 +50,19 @@ select "--- offset --" as ""; --disable_query_log select "--- start-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 $MYSQLD_DATADIR/master-bin.000001 +let $start_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos $MYSQLD_DATADIR/master-bin.000001 --disable_query_log select "--- stop-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --stop-position=608 $MYSQLD_DATADIR/master-bin.000001 +let $stop_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --stop-position=$stop_pos $MYSQLD_DATADIR/master-bin.000001 --disable_query_log select "--- start and stop positions ---" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 --stop-position 725 $MYSQLD_DATADIR/master-bin.000001 +let $start_pos= `select @binlog_start_pos + 502`; +let $stop_pos= `select @binlog_start_pos + 619`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos --stop-position $stop_pos $MYSQLD_DATADIR/master-bin.000001 --disable_query_log select "--- start-datetime --" as ""; --enable_query_log @@ -84,11 +88,13 @@ select "--- offset --" as ""; --disable_query_log select "--- start-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 $MYSQLD_DATADIR/master-bin.000001 $MYSQLD_DATADIR/master-bin.000002 +let $start_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos $MYSQLD_DATADIR/master-bin.000001 $MYSQLD_DATADIR/master-bin.000002 --disable_query_log select "--- stop-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --stop-position=134 $MYSQLD_DATADIR/master-bin.000001 $MYSQLD_DATADIR/master-bin.000002 +let $stop_pos= `select @binlog_start_pos + 28`; +--exec $MYSQL_BINLOG --short-form --stop-position=$stop_pos $MYSQLD_DATADIR/master-bin.000001 $MYSQLD_DATADIR/master-bin.000002 --disable_query_log select "--- start-datetime --" as ""; --enable_query_log @@ -111,15 +117,19 @@ select "--- offset --" as ""; --disable_query_log select "--- start-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 +let $start_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 --disable_query_log select "--- stop-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --stop-position=608 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 +let $stop_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --stop-position=$stop_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 --disable_query_log select "--- start and stop positions ---" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 --stop-position 725 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 +let $start_pos= `select @binlog_start_pos + 502`; +let $stop_pos= `select @binlog_start_pos + 619`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos --stop-position $stop_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 --disable_query_log select "--- start-datetime --" as ""; --enable_query_log @@ -142,11 +152,13 @@ select "--- offset --" as ""; --disable_query_log select "--- start-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --start-position=608 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 master-bin.000002 +let $start_pos= `select @binlog_start_pos + 502`; +--exec $MYSQL_BINLOG --short-form --start-position=$start_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 master-bin.000002 --disable_query_log select "--- stop-position --" as ""; --enable_query_log ---exec $MYSQL_BINLOG --short-form --stop-position=134 --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 master-bin.000002 +let $stop_pos= `select @binlog_start_pos + 28`; +--exec $MYSQL_BINLOG --short-form --stop-position=$stop_pos --read-from-remote-server --user=root --host=127.0.0.1 --port=$MASTER_MYPORT master-bin.000001 master-bin.000002 --disable_query_log select "--- start-datetime --" as ""; --enable_query_log diff --git a/sql/handler.cc b/sql/handler.cc index e851d9248c2..9bfbc816d3f 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -4699,7 +4699,8 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) /** @brief Write table maps for all (manually or automatically) locked tables - to the binary log. + to the binary log. Also, if binlog_annotate_rows_events is ON, + write Annotate_rows event before the first table map. SYNOPSIS write_locked_table_maps() @@ -4736,6 +4737,9 @@ static int write_locked_table_maps(THD *thd) locks[0]= thd->extra_lock; locks[1]= thd->lock; locks[2]= thd->locked_tables; + my_bool with_annotate= thd->variables.binlog_annotate_rows_events && + thd->query() && thd->query_length(); + for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i ) { MYSQL_LOCK const *const lock= locks[i]; @@ -4753,7 +4757,8 @@ static int write_locked_table_maps(THD *thd) check_table_binlog_row_based(thd, table)) { int const has_trans= table->file->has_transactions(); - int const error= thd->binlog_write_table_map(table, has_trans); + int const error= thd->binlog_write_table_map(table, has_trans, + &with_annotate); /* If an error occurs, it is the responsibility of the caller to roll back the transaction. diff --git a/sql/log.cc b/sql/log.cc index e853fedc3d1..4e07bd76739 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4175,10 +4175,12 @@ void THD::binlog_set_stmt_begin() { /* - Write a table map to the binary log. + Write a table map to the binary log. If with_annotate != NULL and + *with_annotate = TRUE write also Annotate_rows before the table map. */ -int THD::binlog_write_table_map(TABLE *table, bool is_trans) +int THD::binlog_write_table_map(TABLE *table, bool is_trans, + my_bool *with_annotate) { int error; DBUG_ENTER("THD::binlog_write_table_map"); @@ -4196,7 +4198,7 @@ int THD::binlog_write_table_map(TABLE *table, bool is_trans) if (is_trans && binlog_table_maps == 0) binlog_start_trans_and_stmt(); - if ((error= mysql_bin_log.write(&the_event))) + if ((error= mysql_bin_log.write(&the_event, with_annotate))) DBUG_RETURN(error); binlog_table_maps++; @@ -4326,10 +4328,12 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, } /** - Write an event to the binary log. + Write an event to the binary log. If with_annotate != NULL and + *with_annotate = TRUE write also Annotate_rows before the event + (this should happen only if the event is a Table_map). */ -bool MYSQL_BIN_LOG::write(Log_event *event_info) +bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) { THD *thd= event_info->thd; bool error= 1; @@ -4448,6 +4452,16 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) of the SQL command */ + if (with_annotate && *with_annotate) + { + DBUG_ASSERT(event_info->get_type_code() == TABLE_MAP_EVENT); + Annotate_rows_log_event anno(thd); + /* Annotate event should be written not more than once */ + *with_annotate= 0; + if (anno.write(file)) + goto err; + } + /* If row-based binlogging, Insert_id, Rand and other kind of "setting context" events are not needed. diff --git a/sql/log.h b/sql/log.h index 8ee94ab5807..491be35bd04 100644 --- a/sql/log.h +++ b/sql/log.h @@ -357,7 +357,8 @@ public: void new_file(); void reset_gathered_updates(THD *thd); - bool write(Log_event* event_info); // binary log write + bool write(Log_event* event_info, + my_bool *with_annotate= 0); // binary log write bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident); bool write_incident(THD *thd, bool lock); int write_cache(THD *thd, IO_CACHE *cache, bool lock_log, diff --git a/sql/log_event.cc b/sql/log_event.cc index fb4fc5b79b3..d4f71194121 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -649,6 +649,7 @@ const char* Log_event::get_type_str(Log_event_type type) case BEGIN_LOAD_QUERY_EVENT: return "Begin_load_query"; case EXECUTE_LOAD_QUERY_EVENT: return "Execute_load_query"; case INCIDENT_EVENT: return "Incident"; + case ANNOTATE_ROWS_EVENT: return "Annotate_rows"; default: return "Unknown"; /* impossible */ } } @@ -728,7 +729,7 @@ Log_event::Log_event(const char* buf, logs are in 4.0 format, until it finds a Format_desc). */ if (description_event->binlog_version==3 && - buf[EVENT_TYPE_OFFSET]= ENUM_END_EVENT || + (uchar)buf[EVENT_TYPE_OFFSET] >= ENUM_END_EVENT || (uint) event_len != uint4korr(buf+EVENT_LEN_OFFSET)) { *error="Sanity check failed"; // Needed to free buffer DBUG_RETURN(NULL); // general sanity check - will fail on a partial read } - uint event_type= buf[EVENT_TYPE_OFFSET]; + uint event_type= (uchar)buf[EVENT_TYPE_OFFSET]; if (event_type > description_event->number_of_event_types && event_type != FORMAT_DESCRIPTION_EVENT) { @@ -1297,6 +1298,9 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, case INCIDENT_EVENT: ev = new Incident_log_event(buf, event_len, description_event); break; + case ANNOTATE_ROWS_EVENT: + ev = new Annotate_rows_log_event(buf, event_len, description_event); + break; default: DBUG_PRINT("error",("Unknown event code: %d", (int) buf[EVENT_TYPE_OFFSET])); @@ -3795,6 +3799,13 @@ Format_description_log_event(uint8 binlog_ver, const char* server_ver) post_header_len[DELETE_ROWS_EVENT-1]= 6;); post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN; + // Set header length of the reserved events to 0 + memset(post_header_len + MYSQL_EVENTS_END - 1, 0, + (MARIA_EVENTS_BEGIN - MYSQL_EVENTS_END)*sizeof(uint8)); + + // Set header lengths of Maria events + post_header_len[ANNOTATE_ROWS_EVENT-1]= ANNOTATE_ROWS_HEADER_LEN; + // Sanity-check that all post header lengths are initialized. IF_DBUG({ int i; @@ -4439,8 +4450,8 @@ Load_log_event::Load_log_event(const char *buf, uint event_len, */ if (event_len) copy_log_event(buf, event_len, - ((buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ? - LOAD_HEADER_LEN + + (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ? + LOAD_HEADER_LEN + description_event->common_header_len : LOAD_HEADER_LEN + LOG_EVENT_HEADER_LEN), description_event); @@ -4477,7 +4488,7 @@ int Load_log_event::copy_log_event(const char *buf, ulong event_len, */ if (!(field_lens= (uchar*)sql_ex.init((char*)buf + body_offset, buf_end, - buf[EVENT_TYPE_OFFSET] != LOAD_EVENT))) + (uchar)buf[EVENT_TYPE_OFFSET] != LOAD_EVENT))) DBUG_RETURN(1); data_len = event_len - body_offset; @@ -6172,7 +6183,7 @@ Create_file_log_event::Create_file_log_event(const char* buf, uint len, uint8 create_file_header_len= description_event->post_header_len[CREATE_FILE_EVENT-1]; if (!(event_buf= (char*) my_memdup(buf, len, MYF(MY_WME))) || copy_log_event(event_buf,len, - ((buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ? + (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ? load_header_len + header_len : (fake_base ? (header_len+load_header_len) : (header_len+load_header_len) + @@ -7924,6 +7935,141 @@ void Rows_log_event::print_helper(FILE *file, } #endif +/************************************************************************** + Annotate_rows_log_event member functions +**************************************************************************/ + +#ifndef MYSQL_CLIENT +Annotate_rows_log_event::Annotate_rows_log_event(THD *thd) + : Log_event(thd, 0, true), + m_save_thd_query_txt(0), + m_save_thd_query_len(0) +{ + m_query_txt= thd->query(); + m_query_len= thd->query_length(); +} +#endif + +Annotate_rows_log_event::Annotate_rows_log_event(const char *buf, + uint event_len, + const Format_description_log_event *desc) + : Log_event(buf, desc), + m_save_thd_query_txt(0), + m_save_thd_query_len(0) +{ + m_query_len= event_len - desc->common_header_len; + m_query_txt= (char*) buf + desc->common_header_len; +} + +Annotate_rows_log_event::~Annotate_rows_log_event() +{ +#ifndef MYSQL_CLIENT + if (m_save_thd_query_txt) + thd->set_query(m_save_thd_query_txt, m_save_thd_query_len); +#endif +} + +int Annotate_rows_log_event::get_data_size() +{ + return m_query_len; +} + +Log_event_type Annotate_rows_log_event::get_type_code() +{ + return ANNOTATE_ROWS_EVENT; +} + +bool Annotate_rows_log_event::is_valid() const +{ + return (m_query_txt != NULL && m_query_len != 0); +} + +#ifndef MYSQL_CLIENT +bool Annotate_rows_log_event::write_data_header(IO_CACHE *file) +{ + return 0; +} +#endif + +#ifndef MYSQL_CLIENT +bool Annotate_rows_log_event::write_data_body(IO_CACHE *file) +{ + return my_b_safe_write(file, (uchar*) m_query_txt, m_query_len); +} +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +void Annotate_rows_log_event::pack_info(Protocol* protocol) +{ + if (m_query_txt && m_query_len) + protocol->store(m_query_txt, m_query_len, &my_charset_bin); +} +#endif + +#ifdef MYSQL_CLIENT +void Annotate_rows_log_event::print(FILE *file, PRINT_EVENT_INFO *pinfo) +{ + if (pinfo->short_form) + return; + + print_header(&pinfo->head_cache, pinfo, TRUE); + my_b_printf(&pinfo->head_cache, "\tAnnotate_rows:\n"); + + char *pbeg; // beginning of the next line + char *pend; // end of the next line + uint cnt= 0; // characters counter + + for (pbeg= m_query_txt; ; pbeg= pend) + { + // skip all \r's and \n's at the beginning of the next line + for (;; pbeg++) + { + if (++cnt > m_query_len) + return; + + if (*pbeg != '\r' && *pbeg != '\n') + break; + } + + // find end of the next line + for (pend= pbeg + 1; + ++cnt <= m_query_len && *pend != '\r' && *pend != '\n'; + pend++); + + // print next line + my_b_write(&pinfo->head_cache, (const uchar*) "#Q> ", 4); + my_b_write(&pinfo->head_cache, (const uchar*) pbeg, pend - pbeg); + my_b_write(&pinfo->head_cache, (const uchar*) "\n", 1); + } +} +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +int Annotate_rows_log_event::do_apply_event(Relay_log_info const *rli) +{ + m_save_thd_query_txt= thd->query(); + m_save_thd_query_len= thd->query_length(); + thd->set_query(m_query_txt, m_query_len); + return 0; +} +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +int Annotate_rows_log_event::do_update_pos(Relay_log_info *rli) +{ + rli->inc_event_relay_log_pos(); + return 0; +} +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +Log_event::enum_skip_reason +Annotate_rows_log_event::do_shall_skip(Relay_log_info *rli) +{ + return continue_group(rli); +} +#endif + /************************************************************************** Table_map_log_event member functions and support functions **************************************************************************/ diff --git a/sql/log_event.h b/sql/log_event.h index 770fb29301b..6593929eb91 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -250,6 +250,7 @@ struct sql_ex_info #define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 + 1) #define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN) #define INCIDENT_HEADER_LEN 2 +#define ANNOTATE_ROWS_HEADER_LEN 0 /* Max number of possible extra bytes in a replication event compared to a packet (i.e. a query) sent from client to master; @@ -582,8 +583,14 @@ enum Log_event_type */ INCIDENT_EVENT= 26, + /* New MySQL/Sun events are to be added right above this comment */ + MYSQL_EVENTS_END, + + MARIA_EVENTS_BEGIN= 160, + /* New Maria event numbers start from here */ + ANNOTATE_ROWS_EVENT= 160, + /* - Add new events here - right above this comment! Existing events (except ENUM_END_EVENT) should never change their numbers */ @@ -2987,6 +2994,59 @@ public: #endif char *str_to_hex(char *to, const char *from, uint len); +/** + @class Annotate_rows_log_event + + In row-based mode, if binlog_annotate_rows_events = ON, each group of + Table_map_log_events is preceded by an Annotate_rows_log_event which + contains the query which caused the subsequent rows operations. + + The Annotate_rows_log_event has no post-header and its body contains + the corresponding query (without trailing zero). Note. The query length + is to be calculated as a difference between the whole event length and + the common header length. +*/ +class Annotate_rows_log_event: public Log_event +{ +public: +#ifndef MYSQL_CLIENT + Annotate_rows_log_event(THD*); +#endif + Annotate_rows_log_event(const char *buf, uint event_len, + const Format_description_log_event*); + ~Annotate_rows_log_event(); + + virtual int get_data_size(); + virtual Log_event_type get_type_code(); + virtual bool is_valid() const; + +#ifndef MYSQL_CLIENT + virtual bool write_data_header(IO_CACHE*); + virtual bool write_data_body(IO_CACHE*); +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) + virtual void pack_info(Protocol*); +#endif + +#ifdef MYSQL_CLIENT + virtual void print(FILE*, PRINT_EVENT_INFO*); +#endif + +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +private: + virtual int do_apply_event(Relay_log_info const*); + virtual int do_update_pos(Relay_log_info*); + virtual enum_skip_reason do_shall_skip(Relay_log_info*); +#endif + +private: + char *m_query_txt; + uint m_query_len; + char *m_save_thd_query_txt; + uint m_save_thd_query_len; +}; + /** @class Table_map_log_event diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3f836a5ab1f..1b3c56ceae4 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -611,7 +611,11 @@ protected: /* BINLOG_DUMP options */ #define BINLOG_DUMP_NON_BLOCK 1 +#endif /* !MYSQL_CLIENT */ +#define BINLOG_SEND_ANNOTATE_ROWS_EVENT 2 + +#ifndef MYSQL_CLIENT /* sql_show.cc:show_log_files() */ #define SHOW_LOG_STATUS_FREE "FREE" #define SHOW_LOG_STATUS_INUSE "IN USE" diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 3d6879e5b23..bc03beaf46d 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -512,6 +512,7 @@ my_bool opt_local_infile, opt_slave_compressed_protocol; my_bool opt_safe_user_create = 0, opt_no_mix_types = 0; my_bool opt_show_slave_auth_info, opt_sql_bin_update = 0; my_bool opt_log_slave_updates= 0; +my_bool opt_replicate_annotate_rows_events= 0; bool slave_warning_issued = false; /* @@ -5870,6 +5871,8 @@ enum options_mysqld OPT_REPLICATE_IGNORE_DB, OPT_LOG_SLAVE_UPDATES, OPT_BINLOG_DO_DB, OPT_BINLOG_IGNORE_DB, OPT_BINLOG_FORMAT, + OPT_BINLOG_ANNOTATE_ROWS_EVENTS, + OPT_REPLICATE_ANNOTATE_ROWS_EVENTS, #ifndef DBUG_OFF OPT_BINLOG_SHOW_XID, #endif @@ -6096,6 +6099,18 @@ struct my_option my_long_options[] = #endif , &opt_binlog_format, &opt_binlog_format, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"binlog-annotate-rows-events", OPT_BINLOG_ANNOTATE_ROWS_EVENTS, + "Tells the master to annotate RBR events with the statement that " + "caused these events.", + (uchar**) &global_system_variables.binlog_annotate_rows_events, + (uchar**) &max_system_variables.binlog_annotate_rows_events, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"replicate-annotate-rows-events", OPT_REPLICATE_ANNOTATE_ROWS_EVENTS, + "Tells the slave to write annotate rows events recieved from the master " + "to its own binary log. Sensible only in pair with log-slave-updates option.", + (uchar**) &opt_replicate_annotate_rows_events, + (uchar**) &opt_replicate_annotate_rows_events, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"binlog-do-db", OPT_BINLOG_DO_DB, "Tells the master it should log updates for the specified database, " "and exclude all others not explicitly mentioned.", diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index c37c4735e37..a2d0b1e4904 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -38,7 +38,8 @@ Relay_log_info::Relay_log_info() inited(0), abort_slave(0), slave_running(0), until_condition(UNTIL_NONE), until_log_pos(0), retried_trans(0), tables_to_lock(0), tables_to_lock_count(0), - last_event_start_time(0), m_flags(0) + last_event_start_time(0), m_flags(0), + m_annotate_event(0) { DBUG_ENTER("Relay_log_info::Relay_log_info"); @@ -72,6 +73,7 @@ Relay_log_info::~Relay_log_info() pthread_cond_destroy(&stop_cond); pthread_cond_destroy(&log_space_cond); relay_log.cleanup(); + free_annotate_event(); DBUG_VOID_RETURN; } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 5cafcf47086..0ea3c23bfd8 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -423,8 +423,46 @@ public: (m_flags & (1UL << IN_STMT)); } + /** + Save pointer to Annotate_rows event and switch on the + binlog_annotate_rows_events for this sql thread. + To be called when sql thread recieves an Annotate_rows event. + */ + inline void set_annotate_event(Annotate_rows_log_event *event) + { + free_annotate_event(); + m_annotate_event= event; + sql_thd->variables.binlog_annotate_rows_events= 1; + } + + /** + Returns pointer to the saved Annotate_rows event or NULL if there is + no saved event. + */ + inline Annotate_rows_log_event* get_annotate_event() + { + return m_annotate_event; + } + + /** + Delete saved Annotate_rows event (if any) and switch off the + binlog_annotate_rows_events for this sql thread. + To be called when sql thread has applied the last (i.e. with + STMT_END_F flag) rbr event. + */ + inline void free_annotate_event() + { + if (m_annotate_event) + { + sql_thd->variables.binlog_annotate_rows_events= 0; + delete m_annotate_event; + m_annotate_event= 0; + } + } + private: uint32 m_flags; + Annotate_rows_log_event *m_annotate_event; }; diff --git a/sql/set_var.cc b/sql/set_var.cc index 910deafc432..894b6519127 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -182,6 +182,9 @@ static sys_var_const sys_back_log(&vars, "back_log", OPT_GLOBAL, SHOW_LONG, (uchar*) &back_log); static sys_var_const_os_str sys_basedir(&vars, "basedir", mysql_home); +static sys_var_thd_bool +sys_binlog_annotate_rows_events(&vars, "binlog_annotate_rows_events", + &SV::binlog_annotate_rows_events); static sys_var_long_ptr sys_binlog_cache_size(&vars, "binlog_cache_size", &binlog_cache_size); static sys_var_thd_binlog_format sys_binlog_format(&vars, "binlog_format", diff --git a/sql/slave.cc b/sql/slave.cc index 0e41d897dd4..e159645db38 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1867,6 +1867,9 @@ static int request_dump(MYSQL* mysql, Master_info* mi, *suppress_warnings= FALSE; + if (opt_log_slave_updates && opt_replicate_annotate_rows_events) + binlog_flags|= BINLOG_SEND_ANNOTATE_ROWS_EVENT; + // TODO if big log files: Change next to int8store() int4store(buf, (ulong) mi->master_log_pos); int2store(buf + 4, binlog_flags); @@ -2261,17 +2264,41 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) } exec_res= apply_event_and_update_pos(ev, thd, rli); - /* - Format_description_log_event should not be deleted because it will be - used to read info about the relay log's format; it will be deleted when - the SQL thread does not need it, i.e. when this thread terminates. - */ - if (ev->get_type_code() != FORMAT_DESCRIPTION_EVENT) - { - DBUG_PRINT("info", ("Deleting the event after it has been executed")); - delete ev; + switch (ev->get_type_code()) { + case FORMAT_DESCRIPTION_EVENT: + /* + Format_description_log_event should not be deleted because it + will be used to read info about the relay log's format; + it will be deleted when the SQL thread does not need it, + i.e. when this thread terminates. + */ + break; + case ANNOTATE_ROWS_EVENT: + /* + Annotate_rows event should not be deleted because after it has + been applied, thd->query points to the string inside this event. + The thd->query will be used to generate new Annotate_rows event + during applying the subsequent Rows events. + */ + rli->set_annotate_event((Annotate_rows_log_event*) ev); + break; + case DELETE_ROWS_EVENT: + case UPDATE_ROWS_EVENT: + case WRITE_ROWS_EVENT: + /* + After the last Rows event has been applied, the saved Annotate_rows + event (if any) is not needed anymore and can be deleted. + */ + if (((Rows_log_event*)ev)->get_flags(Rows_log_event::STMT_END_F)) + rli->free_annotate_event(); + /* fall through */ + default: + DBUG_PRINT("info", ("Deleting the event after it has been executed")); + delete ev; + break; } + /* update_log_pos failed: this should not happen, so we don't retry. @@ -2899,6 +2926,12 @@ pthread_handler_t handle_slave_sql(void *arg) thd->init_for_queries(); thd->temporary_tables = rli->save_temporary_tables; // restore temp tables set_thd_in_use_temporary_tables(rli); // (re)set sql_thd in use for saved temp tables + /* + binlog_annotate_rows_events must be TRUE only after an Annotate_rows event + has been recieved and only till the last corresponding rbr event has been + applied. In all other cases it must be FALSE. + */ + thd->variables.binlog_annotate_rows_events= 0; pthread_mutex_lock(&LOCK_thread_count); threads.append(thd); pthread_mutex_unlock(&LOCK_thread_count); @@ -3382,7 +3415,7 @@ static int queue_binlog_ver_1_event(Master_info *mi, const char *buf, If we get Load event, we need to pass a non-reusable buffer to read_log_event, so we do a trick */ - if (buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) + if ((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) { if (unlikely(!(tmp_buf=(char*)my_malloc(event_len+1,MYF(MY_WME))))) { @@ -3589,13 +3622,13 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) LINT_INIT(inc_pos); if (mi->rli.relay_log.description_event_for_queue->binlog_version<4 && - buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT /* a way to escape */) + (uchar)buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT /* a way to escape */) DBUG_RETURN(queue_old_event(mi,buf,event_len)); LINT_INIT(inc_pos); pthread_mutex_lock(&mi->data_lock); - switch (buf[EVENT_TYPE_OFFSET]) { + switch ((uchar)buf[EVENT_TYPE_OFFSET]) { case STOP_EVENT: /* We needn't write this event to the relay log. Indeed, it just indicates a @@ -3698,9 +3731,9 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) the master's binlog (i.e. Format_desc, Rotate & Stop) should not increment mi->master_log_pos. */ - if (buf[EVENT_TYPE_OFFSET]!=FORMAT_DESCRIPTION_EVENT && - buf[EVENT_TYPE_OFFSET]!=ROTATE_EVENT && - buf[EVENT_TYPE_OFFSET]!=STOP_EVENT) + if ((uchar)buf[EVENT_TYPE_OFFSET]!=FORMAT_DESCRIPTION_EVENT && + (uchar)buf[EVENT_TYPE_OFFSET]!=ROTATE_EVENT && + (uchar)buf[EVENT_TYPE_OFFSET]!=STOP_EVENT) { mi->master_log_pos+= inc_pos; memcpy(rli->ign_master_log_name_end, mi->master_log_name, FN_REFLEN); diff --git a/sql/slave.h b/sql/slave.h index 1a1cfcebd9b..63dd3afc6b8 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -106,6 +106,7 @@ extern MYSQL_PLUGIN_IMPORT char *relay_log_info_file; extern char *opt_relay_logname, *opt_relaylog_index_name; extern my_bool opt_skip_slave_start, opt_reckless_slave; extern my_bool opt_log_slave_updates; +extern my_bool opt_replicate_annotate_rows_events; extern ulonglong relay_log_space_limit; /* diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index da582c37ae9..9a7aac5774b 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -167,7 +167,7 @@ void mysql_client_binlog_statement(THD* thd) */ if (!have_fd_event) { - int type = bufptr[EVENT_TYPE_OFFSET]; + int type = (uchar)bufptr[EVENT_TYPE_OFFSET]; if (type == FORMAT_DESCRIPTION_EVENT || type == START_EVENT_V3) have_fd_event= TRUE; else diff --git a/sql/sql_class.h b/sql/sql_class.h index 2fe579713e1..ae7994c65c9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -367,6 +367,7 @@ struct system_variables ulong ndb_index_stat_cache_entries; ulong ndb_index_stat_update_freq; ulong binlog_format; // binlog format for this thd (see enum_binlog_format) + my_bool binlog_annotate_rows_events; my_bool binlog_direct_non_trans_update; /* In slave thread we need to know in behalf of which @@ -1478,7 +1479,8 @@ public: */ void binlog_start_trans_and_stmt(); void binlog_set_stmt_begin(); - int binlog_write_table_map(TABLE *table, bool is_transactional); + int binlog_write_table_map(TABLE *table, bool is_transactional, + my_bool *with_annotate= 0); int binlog_write_row(TABLE* table, bool is_transactional, MY_BITMAP const* cols, size_t colcnt, const uchar *buf); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 72ca7310ee1..48105cf9f3c 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1966,6 +1966,11 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) pthread_mutex_lock(&LOCK_thread_count); thread_count++; pthread_mutex_unlock(&LOCK_thread_count); + /* + Annotating delayed inserts is not supported. + */ + di->thd.variables.binlog_annotate_rows_events= 0; + di->thd.set_db(table_list->db, (uint) strlen(table_list->db)); di->thd.set_query(my_strdup(table_list->table_name, MYF(MY_WME)), 0); if (di->thd.db == NULL || di->thd.query() == NULL) diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 95e48c531be..51361fdf6e4 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -489,7 +489,7 @@ impossible position"; DBUG_PRINT("info", ("Looked for a Format_description_log_event, found event type %d", (*packet)[EVENT_TYPE_OFFSET+1])); - if ((*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) + if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) { binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+1] & LOG_EVENT_BINLOG_IN_USE_F); @@ -557,31 +557,36 @@ impossible position"; #endif if ((*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) + if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) { binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+1] & LOG_EVENT_BINLOG_IN_USE_F); (*packet)[FLAGS_OFFSET+1] &= ~LOG_EVENT_BINLOG_IN_USE_F; } - else if ((*packet)[EVENT_TYPE_OFFSET+1] == STOP_EVENT) + else if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] == STOP_EVENT) binlog_can_be_corrupted= FALSE; - if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] != ANNOTATE_ROWS_EVENT || + (flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) { - errmsg = "Failed on my_net_write()"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } + if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + { + errmsg = "Failed on my_net_write()"; + my_errno= ER_UNKNOWN_ERROR; + goto err; + } - DBUG_PRINT("info", ("log event code %d", - (*packet)[LOG_EVENT_OFFSET+1] )); - if ((*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) - { - if (send_file(thd)) - { - errmsg = "failed in send_file()"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } + DBUG_PRINT("info", ("log event code %d", + (*packet)[LOG_EVENT_OFFSET+1] )); + if ((uchar)(*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) + { + if (send_file(thd)) + { + errmsg = "failed in send_file()"; + my_errno= ER_UNKNOWN_ERROR; + goto err; + } + } } packet->set("\0", 1, &my_charset_bin); } @@ -677,23 +682,27 @@ impossible position"; if (read_packet) { - thd_proc_info(thd, "Sending binlog event to slave"); - if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) ) - { - errmsg = "Failed on my_net_write()"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } - - if ((*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) - { - if (send_file(thd)) + if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] != ANNOTATE_ROWS_EVENT || + (flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) + { + thd_proc_info(thd, "Sending binlog event to slave"); + if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) ) { - errmsg = "failed in send_file()"; + errmsg = "Failed on my_net_write()"; my_errno= ER_UNKNOWN_ERROR; goto err; } - } + + if ((uchar)(*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) + { + if (send_file(thd)) + { + errmsg = "failed in send_file()"; + my_errno= ER_UNKNOWN_ERROR; + goto err; + } + } + } packet->set("\0", 1, &my_charset_bin); /* No need to net_flush because we will get to flush later when @@ -1774,6 +1783,11 @@ static sys_var_chain vars = { NULL, NULL }; static sys_var_const sys_log_slave_updates(&vars, "log_slave_updates", OPT_GLOBAL, SHOW_MY_BOOL, (uchar*) &opt_log_slave_updates); +static sys_var_const +sys_replicate_annotate_rows_events(&vars, + "replicate_annotate_rows_events", + OPT_GLOBAL, SHOW_MY_BOOL, + (uchar*) &opt_replicate_annotate_rows_events); static sys_var_const sys_relay_log(&vars, "relay_log", OPT_GLOBAL, SHOW_CHAR_PTR, (uchar*) &opt_relay_logname); From bacb41a6b701e06371248b39244a67ec58b709f9 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 11 Jan 2011 09:06:54 +0100 Subject: [PATCH 21/35] Fix two compiler warnings from Buildbot (including one bad merge). --- sql/log_event.cc | 3 ++- sql/sql_repl.cc | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/log_event.cc b/sql/log_event.cc index a7d3f7dee5c..6981ce8b51e 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8036,7 +8036,8 @@ void Annotate_rows_log_event::print(FILE *file, PRINT_EVENT_INFO *pinfo) // find end of the next line for (pend= pbeg + 1; ++cnt <= m_query_len && *pend != '\r' && *pend != '\n'; - pend++); + pend++) + ; // print next line my_b_write(&pinfo->head_cache, (const uchar*) "#Q> ", 4); diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 51361fdf6e4..596f0f5c1e6 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -556,7 +556,6 @@ impossible position"; } #endif - if ((*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) if ((uchar)(*packet)[EVENT_TYPE_OFFSET+1] == FORMAT_DESCRIPTION_EVENT) { binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+1] & From a5b05b45f20cf48be5594e2aa59f03737500c097 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 Jan 2011 14:15:07 +0100 Subject: [PATCH 22/35] Make mysqlbinlog omit redundant `use` around BEGIN/SAVEPOINT/COMMIT/ROLLBACK in 5.0 binlogs This is a merge of percona patch mysqlbinlog_fix.patch. Apparently, MySQL 5.0 does not flag BEGIN/SAVEPOINT/COMMIT/ROLLBACK statements in its binlogs with LOG_EVENT_SUPPRESS_USE_F like 5.1+ does. This causes unnecessary `use` statements around such statements when the binlog is dumped by mysqlbinlog. Fix by always suppressing the output of `use` for these statements. --- client/mysqlbinlog.cc | 15 +++++++++++++-- mysql-test/r/mysqlbinlog.result | 8 ++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 4018cd3908f..d5cbc769f0a 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -842,8 +842,19 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, case QUERY_EVENT: { Query_log_event *qe= (Query_log_event*)ev; - if (!qe->is_trans_keyword() && shall_skip_database(qe->db)) - goto end; + if (!qe->is_trans_keyword()) + { + if (shall_skip_database(qe->db)) + goto end; + } + else + { + /* + In case the event for one of these statements is obtained + from binary log 5.0, make it compatible with 5.1 + */ + qe->flags|= LOG_EVENT_SUPPRESS_USE_F; + } print_use_stmt(print_event_info, qe->db, qe->db_len); if (opt_base64_output_mode == BASE64_OUTPUT_ALWAYS) { diff --git a/mysql-test/r/mysqlbinlog.result b/mysql-test/r/mysqlbinlog.result index 545ef6e8a7e..010f7a17d57 100644 --- a/mysql-test/r/mysqlbinlog.result +++ b/mysql-test/r/mysqlbinlog.result @@ -222,7 +222,6 @@ ROLLBACK /* added by mysqlbinlog */; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; DELIMITER /*!*/; ROLLBACK/*!*/; -use test/*!*/; SET TIMESTAMP=1108844556/*!*/; SET @@session.pseudo_thread_id=999999999/*!*/; SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; @@ -230,6 +229,7 @@ SET @@session.lc_time_names=0/*!*/; SET @@session.collation_database=DEFAULT/*!*/; BEGIN /*!*/; +use test/*!*/; SET TIMESTAMP=1108844555/*!*/; insert t1 values (1) /*!*/; @@ -241,7 +241,6 @@ Warning: The option '--position' is deprecated and will be removed in a future r /*!40019 SET @@session.max_insert_delayed_threads=0*/; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; DELIMITER /*!*/; -use test/*!*/; SET TIMESTAMP=1108844556/*!*/; SET @@session.pseudo_thread_id=999999999/*!*/; SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; @@ -249,6 +248,7 @@ SET @@session.lc_time_names=0/*!*/; SET @@session.collation_database=DEFAULT/*!*/; BEGIN /*!*/; +use test/*!*/; SET TIMESTAMP=1108844555/*!*/; insert t1 values (1) /*!*/; @@ -584,7 +584,6 @@ SET @@session.lc_time_names=0/*!*/; SET @@session.collation_database=DEFAULT/*!*/; BEGIN /*!*/; -use test/*!*/; SET TIMESTAMP=1266652094/*!*/; SavePoint mixed_cases /*!*/; @@ -595,11 +594,9 @@ INSERT INTO db1.t2 VALUES("in savepoint mixed_cases") SET TIMESTAMP=1266652094/*!*/; INSERT INTO db1.t1 VALUES(40) /*!*/; -use test/*!*/; SET TIMESTAMP=1266652094/*!*/; ROLLBACK TO mixed_cases /*!*/; -use db1/*!*/; SET TIMESTAMP=1266652094/*!*/; INSERT INTO db1.t2 VALUES("after rollback to") /*!*/; @@ -627,7 +624,6 @@ SET @@session.lc_time_names=0/*!*/; SET @@session.collation_database=DEFAULT/*!*/; BEGIN /*!*/; -use test/*!*/; SET TIMESTAMP=1266652094/*!*/; SavePoint mixed_cases /*!*/; From 79a8e5f61ba345af4f188075f6af60dc59ffee25 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jan 2011 14:25:34 +0100 Subject: [PATCH 23/35] Fix incorrect DBUG_ASSERT(). --- sql/log_event.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/log_event.cc b/sql/log_event.cc index 6981ce8b51e..22dcce839db 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -9210,7 +9210,7 @@ int Rows_log_event::find_key() KEY *key, *best_key; ulong best_rec_per_key, tmp; DBUG_ENTER("Rows_log_event::find_key"); - DBUG_ASSERT(m_table && m_table->in_use != NULL); + DBUG_ASSERT(m_table); best_key_nr= MAX_KEY; LINT_INIT(best_key); From 2cbfdfc210ac5132de7c197c99b18990ec8c954a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jan 2011 14:52:39 +0100 Subject: [PATCH 24/35] Fix wrong printf() format. --- storage/xtradb/handler/ha_innodb.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 777ad522cfe..5f9f264ab86 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -3024,7 +3024,8 @@ innobase_rollback( if (innobase_release_locks_early && trx->conc_state == TRX_PREPARED && UT_LIST_GET_LEN(trx->trx_locks) == 0) { sql_print_error("Rollback after releasing locks! " - "errno=%d, dberr=%d", errno, trx->error_state); + "errno=%d, dberr="ULINTPF, + errno, trx->error_state); ut_error; } From fea75f404aa9231d441cf2e2a99539630ec9f0dc Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2011 15:32:39 +0100 Subject: [PATCH 25/35] MWL#136: Fix typo (wrong type) breaking compile. --- sql/log.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/log.cc b/sql/log.cc index fb42b42e0cb..36ddd6f2eb4 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6591,8 +6591,8 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) } else { - *out_pos= NULL; *out_file= NULL; + *out_pos= 0; } } #endif /* INNODB_COMPATIBILITY_HOOKS */ From bd8942070bf70decafa41996a2260da2c309b4e1 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2011 15:56:44 +0100 Subject: [PATCH 26/35] After-merge fixes for MWL#47 (which causes changes in binlog positions in .result files). Also fix one incorrect printf() format. --- mysql-test/r/group_commit_binlog_pos.result | 2 +- mysql-test/r/group_commit_crash.result | 6 +- .../r/innodb_release_row_locks_early.result | 48 ++++++++------- mysql-test/r/mysqldump-max.result | 8 +-- .../suite/binlog/r/binlog_consistent.result | 60 +++++++++---------- mysql-test/t/group_commit_crash.test | 1 + .../t/innodb_release_row_locks_early.test | 5 ++ storage/xtradb/fil/fil0fil.c | 3 +- 8 files changed, 71 insertions(+), 62 deletions(-) diff --git a/mysql-test/r/group_commit_binlog_pos.result b/mysql-test/r/group_commit_binlog_pos.result index 67ae30bbb79..bfeb2474edb 100644 --- a/mysql-test/r/group_commit_binlog_pos.result +++ b/mysql-test/r/group_commit_binlog_pos.result @@ -30,6 +30,6 @@ a 1 2 3 -InnoDB: Last MySQL binlog file position 0 767, file name ./master-bin.000001 +InnoDB: Last MySQL binlog file position 0 901, file name ./master-bin.000001 SET DEBUG_SYNC= 'RESET'; DROP TABLE t1; diff --git a/mysql-test/r/group_commit_crash.result b/mysql-test/r/group_commit_crash.result index 46b6a05a2da..a55d8f29dda 100644 --- a/mysql-test/r/group_commit_crash.result +++ b/mysql-test/r/group_commit_crash.result @@ -60,7 +60,7 @@ a b c d 9 a b c d 10 SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +master-bin.000001 # Query 1 # use `test`; insert into t1 select * from t2 delete from t1; SET binlog_format= mixed; RESET MASTER; @@ -83,7 +83,7 @@ a b c d 9 a b c d 10 SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +master-bin.000001 # Query 1 # use `test`; insert into t1 select * from t2 delete from t1; SET binlog_format= mixed; RESET MASTER; @@ -106,7 +106,7 @@ a b c d 9 a b c d 10 SHOW BINLOG EVENTS LIMIT 2,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +master-bin.000001 # Query 1 # use `test`; insert into t1 select * from t2 delete from t1; SET binlog_format= mixed; RESET MASTER; diff --git a/mysql-test/r/innodb_release_row_locks_early.result b/mysql-test/r/innodb_release_row_locks_early.result index eb40f73d97d..d544dc8df1f 100644 --- a/mysql-test/r/innodb_release_row_locks_early.result +++ b/mysql-test/r/innodb_release_row_locks_early.result @@ -7,11 +7,13 @@ INSERT INTO t1 (k, a, b, c) VALUES (4, 0, 0, 0); RESET MASTER; SET DEBUG_SYNC= 'RESET'; # Connection c1 +SET binlog_format= mixed; BEGIN; UPDATE t1 SET a=10 WHERE k=1; SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; COMMIT; # Connection c2 +SET binlog_format= mixed; SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; BEGIN; SELECT * FROM t1 WHERE k=1 FOR UPDATE; @@ -41,17 +43,17 @@ Warning 1639 debug sync point wait timed out # Connection c2 SHOW BINLOG EVENTS LIMIT 2,12; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 174 Query 1 265 use `test`; UPDATE t1 SET a=10 WHERE k=1 -master-bin.000001 265 Xid 1 292 COMMIT /* xid=XX */ -master-bin.000001 292 Query 1 360 BEGIN -master-bin.000001 360 Query 1 451 use `test`; UPDATE t1 SET a=20 WHERE k=1 -master-bin.000001 451 Xid 1 478 COMMIT /* xid=XX */ -master-bin.000001 478 Query 1 546 BEGIN -master-bin.000001 546 Query 1 637 use `test`; UPDATE t1 SET a=10 WHERE k=2 -master-bin.000001 637 Xid 1 664 COMMIT /* xid=XX */ -master-bin.000001 664 Query 1 732 BEGIN -master-bin.000001 732 Query 1 823 use `test`; UPDATE t1 SET a=20 WHERE k=2 -master-bin.000001 823 Xid 1 850 COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=10 WHERE k=1 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=20 WHERE k=1 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=10 WHERE k=2 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=20 WHERE k=2 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ # Connection c1 RESET MASTER; SET DEBUG_SYNC="commit_after_release_LOCK_prepare_ordered SIGNAL c1_prepared WAIT_FOR c2_committing"; @@ -80,18 +82,18 @@ Warning 1639 debug sync point wait timed out # Connection c2 SHOW BINLOG EVENTS LIMIT 1,12; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 106 Query 1 174 BEGIN -master-bin.000001 174 Query 1 265 use `test`; UPDATE t1 SET a=10 WHERE k=3 -master-bin.000001 265 Xid 1 292 COMMIT /* xid=XX */ -master-bin.000001 292 Query 1 360 BEGIN -master-bin.000001 360 Query 1 451 use `test`; UPDATE t1 SET a=20 WHERE k=3 -master-bin.000001 451 Xid 1 478 COMMIT /* xid=XX */ -master-bin.000001 478 Query 1 546 BEGIN -master-bin.000001 546 Query 1 637 use `test`; UPDATE t1 SET a=10 WHERE k=4 -master-bin.000001 637 Xid 1 664 COMMIT /* xid=XX */ -master-bin.000001 664 Query 1 732 BEGIN -master-bin.000001 732 Query 1 823 use `test`; UPDATE t1 SET a=20 WHERE k=4 -master-bin.000001 823 Xid 1 850 COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=10 WHERE k=3 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=20 WHERE k=3 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=10 WHERE k=4 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; UPDATE t1 SET a=20 WHERE k=4 +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ SELECT * FROM t1 ORDER BY k; k a b c 1 20 0 0 diff --git a/mysql-test/r/mysqldump-max.result b/mysql-test/r/mysqldump-max.result index d4509b51923..aaa31f8b45d 100644 --- a/mysql-test/r/mysqldump-max.result +++ b/mysql-test/r/mysqldump-max.result @@ -334,10 +334,10 @@ DROP TABLE t1; DROP TABLE t2; SHOW BINLOG EVENTS LIMIT 6,3; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 524 Query 1 592 BEGIN -master-bin.000001 592 Query 1 689 use `test`; INSERT INTO t2 VALUES (1,0), (2,0) -master-bin.000001 689 Xid 1 716 COMMIT /* XID */ --- CHANGE MASTER TO MASTER_LOG_FILE='./master-bin.000001', MASTER_LOG_POS=716; +master-bin.000001 658 Query 1 726 BEGIN +master-bin.000001 726 Query 1 823 use `test`; INSERT INTO t2 VALUES (1,0), (2,0) +master-bin.000001 823 Xid 1 850 COMMIT /* XID */ +-- CHANGE MASTER TO MASTER_LOG_FILE='./master-bin.000001', MASTER_LOG_POS=850; SELECT * FROM t1 ORDER BY a; a 1 diff --git a/mysql-test/suite/binlog/r/binlog_consistent.result b/mysql-test/suite/binlog/r/binlog_consistent.result index 8784d251c5f..4561bd70995 100644 --- a/mysql-test/suite/binlog/r/binlog_consistent.result +++ b/mysql-test/suite/binlog/r/binlog_consistent.result @@ -3,11 +3,11 @@ RESET MASTER; CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb; SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB -master-bin.000001 241 +master-bin.000001 375 SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value binlog_snapshot_file ./master-bin.000001 -binlog_snapshot_position 241 +binlog_snapshot_position 375 BEGIN; INSERT INTO t1 VALUES (0, ""); # Connection con1 @@ -38,10 +38,10 @@ a b SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value binlog_snapshot_file ./master-bin.000001 -binlog_snapshot_position 540 +binlog_snapshot_position 674 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB -master-bin.000001 727 +master-bin.000001 861 SELECT * FROM t2 ORDER BY a; a 2 @@ -60,40 +60,40 @@ a b SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value binlog_snapshot_file ./master-bin.000001 -binlog_snapshot_position 540 +binlog_snapshot_position 674 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB -master-bin.000002 106 +master-bin.000002 240 COMMIT; SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value binlog_snapshot_file ./master-bin.000002 -binlog_snapshot_position 106 +binlog_snapshot_position 240 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB -master-bin.000002 106 +master-bin.000002 240 SHOW BINLOG EVENTS; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 4 Format_desc 1 106 Server ver: #, Binlog ver: # -master-bin.000001 106 Query 1 241 use `test`; CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb -master-bin.000001 241 Query 1 353 use `test`; CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=myisam -master-bin.000001 353 Query 1 421 BEGIN -master-bin.000001 421 Query 1 513 use `test`; INSERT INTO t1 VALUES (0, "") -master-bin.000001 513 Xid 1 540 COMMIT /* XID */ -master-bin.000001 540 Query 1 608 BEGIN -master-bin.000001 608 Query 1 700 use `test`; INSERT INTO t1 VALUES (4, "") -master-bin.000001 700 Xid 1 727 COMMIT /* XID */ -master-bin.000001 727 Query 1 795 BEGIN -master-bin.000001 795 Query 1 887 use `test`; INSERT INTO t1 VALUES (1, "") -master-bin.000001 887 Xid 1 914 COMMIT /* XID */ -master-bin.000001 914 Query 1 982 BEGIN -master-bin.000001 982 Query 1 1079 use `test`; INSERT INTO t1 VALUES (2, "first") -master-bin.000001 1079 Query 1 1167 use `test`; INSERT INTO t2 VALUES (2) -master-bin.000001 1167 Query 1 1265 use `test`; INSERT INTO t1 VALUES (2, "second") -master-bin.000001 1265 Xid 1 1292 COMMIT /* XID */ -master-bin.000001 1292 Query 1 1360 BEGIN -master-bin.000001 1360 Query 1 1452 use `test`; INSERT INTO t1 VALUES (3, "") -master-bin.000001 1452 Query 1 1540 use `test`; INSERT INTO t2 VALUES (3) -master-bin.000001 1540 Xid 1 1567 COMMIT /* XID */ -master-bin.000001 1567 Rotate 1 1611 master-bin.000002;pos=4 +master-bin.000001 4 Format_desc 1 240 Server ver: #, Binlog ver: # +master-bin.000001 240 Query 1 375 use `test`; CREATE TABLE t1 (a INT, b VARCHAR(100), PRIMARY KEY (a,b)) ENGINE=innodb +master-bin.000001 375 Query 1 487 use `test`; CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=myisam +master-bin.000001 487 Query 1 555 BEGIN +master-bin.000001 555 Query 1 647 use `test`; INSERT INTO t1 VALUES (0, "") +master-bin.000001 647 Xid 1 674 COMMIT /* XID */ +master-bin.000001 674 Query 1 742 BEGIN +master-bin.000001 742 Query 1 834 use `test`; INSERT INTO t1 VALUES (4, "") +master-bin.000001 834 Xid 1 861 COMMIT /* XID */ +master-bin.000001 861 Query 1 929 BEGIN +master-bin.000001 929 Query 1 1021 use `test`; INSERT INTO t1 VALUES (1, "") +master-bin.000001 1021 Xid 1 1048 COMMIT /* XID */ +master-bin.000001 1048 Query 1 1116 BEGIN +master-bin.000001 1116 Query 1 1213 use `test`; INSERT INTO t1 VALUES (2, "first") +master-bin.000001 1213 Query 1 1301 use `test`; INSERT INTO t2 VALUES (2) +master-bin.000001 1301 Query 1 1399 use `test`; INSERT INTO t1 VALUES (2, "second") +master-bin.000001 1399 Xid 1 1426 COMMIT /* XID */ +master-bin.000001 1426 Query 1 1494 BEGIN +master-bin.000001 1494 Query 1 1586 use `test`; INSERT INTO t1 VALUES (3, "") +master-bin.000001 1586 Query 1 1674 use `test`; INSERT INTO t2 VALUES (3) +master-bin.000001 1674 Xid 1 1701 COMMIT /* XID */ +master-bin.000001 1701 Rotate 1 1745 master-bin.000002;pos=4 DROP TABLE t1,t2; diff --git a/mysql-test/t/group_commit_crash.test b/mysql-test/t/group_commit_crash.test index b21032d7993..772739a897a 100644 --- a/mysql-test/t/group_commit_crash.test +++ b/mysql-test/t/group_commit_crash.test @@ -65,6 +65,7 @@ while ($numtests) # table and binlog should be in sync. SELECT * FROM t1 ORDER BY id; +--replace_column 2 # 5 # SHOW BINLOG EVENTS LIMIT 2,1; delete from t1; diff --git a/mysql-test/t/innodb_release_row_locks_early.test b/mysql-test/t/innodb_release_row_locks_early.test index 0162f23ef7f..b4e99a1c4f8 100644 --- a/mysql-test/t/innodb_release_row_locks_early.test +++ b/mysql-test/t/innodb_release_row_locks_early.test @@ -28,6 +28,8 @@ connect(c2,127.0.0.1,root,,test,$MASTER_MYPORT,); connection c1; --echo # Connection c1 +# Fix binlog format (otherwise SHOW BINLOG EVENTS will fluctuate). +SET binlog_format= mixed; # First verify that row locks are released early. BEGIN; @@ -38,6 +40,7 @@ send COMMIT; connection c2; --echo # Connection c2 + SET binlog_format= mixed; SET DEBUG_SYNC="now WAIT_FOR c1_prepared"; BEGIN; SELECT * FROM t1 WHERE k=1 FOR UPDATE; @@ -75,6 +78,7 @@ connection c2; --echo # Connection c2 reap; +--replace_column 2 # 5 # --replace_regex /xid=[0-9]+/xid=XX/ SHOW BINLOG EVENTS LIMIT 2,12; @@ -121,6 +125,7 @@ connection c2; --echo # Connection c2 reap; +--replace_column 2 # 5 # --replace_regex /xid=[0-9]+/xid=XX/ SHOW BINLOG EVENTS LIMIT 1,12; diff --git a/storage/xtradb/fil/fil0fil.c b/storage/xtradb/fil/fil0fil.c index a8520187013..96396bc05b7 100644 --- a/storage/xtradb/fil/fil0fil.c +++ b/storage/xtradb/fil/fil0fil.c @@ -3313,7 +3313,8 @@ skip_info: } if (page_is_corrupt) { - fprintf(stderr, " [errp:%lld]", offset / UNIV_PAGE_SIZE); + fprintf(stderr, " [errp:%ld]", + (long)(offset / UNIV_PAGE_SIZE)); /* cannot treat corrupt page */ goto skip_write; From cf2a7c5b5e9ebdd3e2a4c598ea08cdd6949fd313 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2011 16:51:11 +0100 Subject: [PATCH 27/35] lp:697174: Do not put absolute path in status variable binlog_snapshot_file. Omit the directory part to make the output the same as SHOW MASTER STATUS. --- .../suite/binlog/r/binlog_consistent.result | 8 +++---- sql/log.cc | 24 ++++++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/binlog/r/binlog_consistent.result b/mysql-test/suite/binlog/r/binlog_consistent.result index 8784d251c5f..856943ebb86 100644 --- a/mysql-test/suite/binlog/r/binlog_consistent.result +++ b/mysql-test/suite/binlog/r/binlog_consistent.result @@ -6,7 +6,7 @@ File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000001 241 SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_file master-bin.000001 binlog_snapshot_position 241 BEGIN; INSERT INTO t1 VALUES (0, ""); @@ -37,7 +37,7 @@ a b 0 SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_file master-bin.000001 binlog_snapshot_position 540 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB @@ -59,7 +59,7 @@ a b 0 SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_snapshot_file ./master-bin.000001 +binlog_snapshot_file master-bin.000001 binlog_snapshot_position 540 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB @@ -67,7 +67,7 @@ master-bin.000002 106 COMMIT; SHOW STATUS LIKE 'binlog_snapshot_%'; Variable_name Value -binlog_snapshot_file ./master-bin.000002 +binlog_snapshot_file master-bin.000002 binlog_snapshot_position 106 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB diff --git a/sql/log.cc b/sql/log.cc index 36ddd6f2eb4..c136676e121 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6650,6 +6650,18 @@ static struct st_mysql_sys_var *binlog_sys_vars[]= #endif +/* + Copy out the non-directory part of binlog position filename for the + `binlog_snapshot_file' status variable, same way as it is done for + SHOW MASTER STATUS. +*/ +static void +set_binlog_snapshot_file(const char *src) +{ + int dir_len = dirname_length(src); + strmake(binlog_snapshot_file, src + dir_len, sizeof(binlog_snapshot_file)-1); +} + /* Copy out current values of status variables, for SHOW STATUS or information_schema.global_status. @@ -6666,21 +6678,21 @@ TC_LOG_BINLOG::set_status_variables(THD *thd) else trx_data= NULL; + bool have_snapshot= + (trx_data && 0 != strcmp(trx_data->last_commit_pos_file, "")); pthread_mutex_lock(&LOCK_commit_ordered); binlog_status_var_num_commits= this->num_commits; binlog_status_var_num_group_commits= this->num_group_commits; - if (!trx_data || 0 == strcmp(trx_data->last_commit_pos_file, "")) + if (!have_snapshot) { - strmake(binlog_snapshot_file, last_commit_pos_file, - sizeof(binlog_snapshot_file)-1); + set_binlog_snapshot_file(last_commit_pos_file); binlog_snapshot_position= last_commit_pos_offset; } pthread_mutex_unlock(&LOCK_commit_ordered); - if (trx_data && 0 != strcmp(trx_data->last_commit_pos_file, "")) + if (have_snapshot) { - strmake(binlog_snapshot_file, trx_data->last_commit_pos_file, - sizeof(binlog_snapshot_file)-1); + set_binlog_snapshot_file(trx_data->last_commit_pos_file); binlog_snapshot_position= trx_data->last_commit_pos_offset; } } From 1b63a9906047617a30ebb3bcec50c7921873b6eb Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2011 20:05:24 +0100 Subject: [PATCH 28/35] lp:697174, missing result file update. --- mysql-test/r/mysqldump-max.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/r/mysqldump-max.result b/mysql-test/r/mysqldump-max.result index d4509b51923..a561f0b880f 100644 --- a/mysql-test/r/mysqldump-max.result +++ b/mysql-test/r/mysqldump-max.result @@ -337,7 +337,7 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 524 Query 1 592 BEGIN master-bin.000001 592 Query 1 689 use `test`; INSERT INTO t2 VALUES (1,0), (2,0) master-bin.000001 689 Xid 1 716 COMMIT /* XID */ --- CHANGE MASTER TO MASTER_LOG_FILE='./master-bin.000001', MASTER_LOG_POS=716; +-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=716; SELECT * FROM t1 ORDER BY a; a 1 From ca5ca4b968297ac4dc5d0850752306b92570b6a1 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 23 Mar 2011 15:29:20 +0100 Subject: [PATCH 29/35] MWL#116: group commit Implement binlog_optimize_thread_scheduling option to allow benchmarking the effect of running commit_ordered() for multiple transactions all in one thread. --- ...ommit_binlog_pos_no_optimize_thread.result | 36 ++++++ ...oup_commit_crash_no_optimize_thread.result | 120 ++++++++++++++++++ .../r/group_commit_no_optimize_thread.result | 63 +++++++++ ...t_binlog_pos_no_optimize_thread-master.opt | 1 + ..._commit_binlog_pos_no_optimize_thread.test | 86 +++++++++++++ ...commit_crash_no_optimize_thread-master.opt | 1 + ...group_commit_crash_no_optimize_thread.test | 80 ++++++++++++ ...group_commit_no_optimize_thread-master.opt | 1 + .../t/group_commit_no_optimize_thread.test | 115 +++++++++++++++++ sql/log.cc | 68 +++++++++- sql/log.h | 8 ++ 11 files changed, 573 insertions(+), 6 deletions(-) create mode 100644 mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result create mode 100644 mysql-test/r/group_commit_crash_no_optimize_thread.result create mode 100644 mysql-test/r/group_commit_no_optimize_thread.result create mode 100644 mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt create mode 100644 mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test create mode 100644 mysql-test/t/group_commit_crash_no_optimize_thread-master.opt create mode 100644 mysql-test/t/group_commit_crash_no_optimize_thread.test create mode 100644 mysql-test/t/group_commit_no_optimize_thread-master.opt create mode 100644 mysql-test/t/group_commit_no_optimize_thread.test diff --git a/mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result b/mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result new file mode 100644 index 00000000000..02bbfd36143 --- /dev/null +++ b/mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result @@ -0,0 +1,36 @@ +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES (0); +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +INSERT INTO t1 VALUES (1); +SET DEBUG_SYNC= "now WAIT_FOR con1_waiting"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con2_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +INSERT INTO t1 VALUES (2); +SET DEBUG_SYNC= "now WAIT_FOR con2_queued"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +INSERT INTO t1 VALUES (3); +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +SELECT * FROM t1 ORDER BY a; +a +0 +1 +2 +SET SESSION debug="+d,crash_dispatch_command_before"; +SELECT 1; +Got one of the listed errors +Got one of the listed errors +SELECT * FROM t1 ORDER BY a; +a +0 +1 +2 +3 +InnoDB: Last MySQL binlog file position 0 767, file name ./master-bin.000001 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/r/group_commit_crash_no_optimize_thread.result b/mysql-test/r/group_commit_crash_no_optimize_thread.result new file mode 100644 index 00000000000..044161695e3 --- /dev/null +++ b/mysql-test/r/group_commit_crash_no_optimize_thread.result @@ -0,0 +1,120 @@ +CREATE TABLE t1(a CHAR(255), +b CHAR(255), +c CHAR(255), +d CHAR(255), +id INT AUTO_INCREMENT, +PRIMARY KEY(id)) ENGINE=InnoDB; +create table t2 like t1; +create procedure setcrash(IN i INT) +begin +CASE i +WHEN 1 THEN SET SESSION debug="d,crash_commit_after_prepare"; +WHEN 2 THEN SET SESSION debug="d,crash_commit_after_log"; +WHEN 3 THEN SET SESSION debug="d,crash_commit_before_unlog"; +WHEN 4 THEN SET SESSION debug="d,crash_commit_after"; +WHEN 5 THEN SET SESSION debug="d,crash_commit_before"; +ELSE BEGIN END; +END CASE; +end // +FLUSH TABLES; +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(5); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(4); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(3); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(2); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +a b c d 1 +a b c d 2 +a b c d 3 +a b c d 4 +a b c d 5 +a b c d 6 +a b c d 7 +a b c d 8 +a b c d 9 +a b c d 10 +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 174 Query 1 268 use `test`; insert into t1 select * from t2 +delete from t1; +RESET MASTER; +START TRANSACTION; +insert into t1 select * from t2; +call setcrash(1); +COMMIT; +Got one of the listed errors +SELECT * FROM t1 ORDER BY id; +a b c d id +SHOW BINLOG EVENTS LIMIT 2,1; +Log_name Pos Event_type Server_id End_log_pos Info +delete from t1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE setcrash; diff --git a/mysql-test/r/group_commit_no_optimize_thread.result b/mysql-test/r/group_commit_no_optimize_thread.result new file mode 100644 index 00000000000..7c2636fb472 --- /dev/null +++ b/mysql-test/r/group_commit_no_optimize_thread.result @@ -0,0 +1,63 @@ +CREATE TABLE t1 (a VARCHAR(10) PRIMARY KEY) ENGINE=innodb; +SELECT variable_value INTO @commits FROM information_schema.global_status +WHERE variable_name = 'binlog_commits'; +SELECT variable_value INTO @group_commits FROM information_schema.global_status +WHERE variable_name = 'binlog_group_commits'; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group1_running WAIT_FOR group2_queued"; +INSERT INTO t1 VALUES ("con1"); +set DEBUG_SYNC= "now WAIT_FOR group1_running"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; +SET DEBUG_SYNC= "commit_after_release_LOCK_log WAIT_FOR group3_committed"; +INSERT INTO t1 VALUES ("con2"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con2"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con3"; +INSERT INTO t1 VALUES ("con3"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con3"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con4"; +SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; +INSERT INTO t1 VALUES ("con4"); +SET DEBUG_SYNC= "now WAIT_FOR group2_con4"; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM t1 ORDER BY a; +a +SET DEBUG_SYNC= "now SIGNAL group2_queued"; +SELECT * FROM t1 ORDER BY a; +a +con1 +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con5_leader WAIT_FOR con6_queued"; +INSERT INTO t1 VALUES ("con5"); +SET DEBUG_SYNC= "now WAIT_FOR con5_leader"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con6_queued"; +INSERT INTO t1 VALUES ("con6"); +SET DEBUG_SYNC= "now WAIT_FOR group3_con5"; +SELECT * FROM t1 ORDER BY a; +a +con1 +SET DEBUG_SYNC= "now SIGNAL group3_committed"; +SET DEBUG_SYNC= "now WAIT_FOR group2_visible"; +SELECT * FROM t1 ORDER BY a; +a +con1 +con2 +con3 +con4 +SET DEBUG_SYNC= "now SIGNAL group2_checked"; +SELECT * FROM t1 ORDER BY a; +a +con1 +con2 +con3 +con4 +con5 +con6 +SELECT variable_value - @commits FROM information_schema.global_status +WHERE variable_name = 'binlog_commits'; +variable_value - @commits +6 +SELECT variable_value - @group_commits FROM information_schema.global_status +WHERE variable_name = 'binlog_group_commits'; +variable_value - @group_commits +3 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt new file mode 100644 index 00000000000..18d43988ffd --- /dev/null +++ b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt @@ -0,0 +1 @@ +--binlog-optimize-thread-scheduling=0 --skip-stack-trace --skip-core-file diff --git a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test new file mode 100644 index 00000000000..13f57dd247a --- /dev/null +++ b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test @@ -0,0 +1,86 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc +--source include/have_binlog_format_mixed_or_statement.inc + +# Need DBUG to crash the server intentionally +--source include/have_debug.inc +# Don't test this under valgrind, memory leaks will occur as we crash +--source include/not_valgrind.inc + +# XtraDB stores the binlog position corresponding to the last commit, and +# prints it during crash recovery. +# Test that we get the correct position when we group commit several +# transactions together. + +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=innodb; +INSERT INTO t1 VALUES (0); + +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); +connect(con3,localhost,root,,); + +# Queue up three commits for group commit. + +connection con1; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con1_waiting WAIT_FOR con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +send INSERT INTO t1 VALUES (1); + +connection con2; +SET DEBUG_SYNC= "now WAIT_FOR con1_waiting"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con2_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +send INSERT INTO t1 VALUES (2); + +connection con3; +SET DEBUG_SYNC= "now WAIT_FOR con2_queued"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con3_queued"; +SET DEBUG_SYNC= "commit_loop_entry_commit_ordered SIGNAL con1_loop WAIT_FOR con1_loop_cont"; +send INSERT INTO t1 VALUES (3); + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +# At this point, no transactions are committed. +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; +# At this point, 1 transaction is committed. +SET DEBUG_SYNC= "now SIGNAL con1_loop_cont"; +SET DEBUG_SYNC= "now WAIT_FOR con1_loop"; + +# At this point, 2 transactions are committed. +SELECT * FROM t1 ORDER BY a; + +connection con1; +reap; +connection con2; +reap; + +# Now crash the server with 1+2 in-memory committed, 3 only prepared. +connection default; +system echo wait-group_commit_binlog_pos_no_optimize_thread.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; +SET SESSION debug="+d,crash_dispatch_command_before"; +--error 2006,2013 +SELECT 1; + +connection con3; +--error 2006,2013 +reap; + +system echo restart-group_commit_binlog_pos_no_optimize_thread.test >> $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; + +connection default; +--enable_reconnect +--source include/wait_until_connected_again.inc + +# Crash recovery should recover all three transactions. +SELECT * FROM t1 ORDER BY a; + +# Check that the binlog position reported by InnoDB is the correct one +# for the end of the second transaction (as can be checked with +# mysqlbinlog). +let $MYSQLD_DATADIR= `SELECT @@datadir`; +--exec grep 'InnoDB: Last MySQL binlog file position' $MYSQLD_DATADIR/../../log/mysqld.1.err | tail -1 + +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/t/group_commit_crash_no_optimize_thread-master.opt b/mysql-test/t/group_commit_crash_no_optimize_thread-master.opt new file mode 100644 index 00000000000..18d43988ffd --- /dev/null +++ b/mysql-test/t/group_commit_crash_no_optimize_thread-master.opt @@ -0,0 +1 @@ +--binlog-optimize-thread-scheduling=0 --skip-stack-trace --skip-core-file diff --git a/mysql-test/t/group_commit_crash_no_optimize_thread.test b/mysql-test/t/group_commit_crash_no_optimize_thread.test new file mode 100644 index 00000000000..273cd6230eb --- /dev/null +++ b/mysql-test/t/group_commit_crash_no_optimize_thread.test @@ -0,0 +1,80 @@ +# Testing group commit by crashing a few times. +# Test adapted from the Facebook patch: lp:mysqlatfacebook +--source include/not_embedded.inc +# Don't test this under valgrind, memory leaks will occur +--source include/not_valgrind.inc + +# Binary must be compiled with debug for crash to occur +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc + +let $file_format_check=`SELECT @@innodb_file_format_check`; +CREATE TABLE t1(a CHAR(255), + b CHAR(255), + c CHAR(255), + d CHAR(255), + id INT AUTO_INCREMENT, + PRIMARY KEY(id)) ENGINE=InnoDB; +create table t2 like t1; +delimiter //; +create procedure setcrash(IN i INT) +begin + CASE i + WHEN 1 THEN SET SESSION debug="d,crash_commit_after_prepare"; + WHEN 2 THEN SET SESSION debug="d,crash_commit_after_log"; + WHEN 3 THEN SET SESSION debug="d,crash_commit_before_unlog"; + WHEN 4 THEN SET SESSION debug="d,crash_commit_after"; + WHEN 5 THEN SET SESSION debug="d,crash_commit_before"; + ELSE BEGIN END; + END CASE; +end // +delimiter ;// +# Avoid getting a crashed mysql.proc table. +FLUSH TABLES; + +let $numtests = 5; + +let $numinserts = 10; +while ($numinserts) +{ + dec $numinserts; + INSERT INTO t2(a, b, c, d) VALUES ('a', 'b', 'c', 'd'); +} + +--enable_reconnect + +while ($numtests) +{ + RESET MASTER; + + START TRANSACTION; + insert into t1 select * from t2; + # Write file to make mysql-test-run.pl expect crash + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + + eval call setcrash($numtests); + + # Run the crashing query + --error 2006,2013 + COMMIT; + + # Poll the server waiting for it to be back online again. + --source include/wait_until_connected_again.inc + + # table and binlog should be in sync. + SELECT * FROM t1 ORDER BY id; + SHOW BINLOG EVENTS LIMIT 2,1; + + delete from t1; + + dec $numtests; +} + +# final cleanup +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE setcrash; +--disable_query_log +eval SET GLOBAL innodb_file_format_check=$file_format_check; +--enable_query_log diff --git a/mysql-test/t/group_commit_no_optimize_thread-master.opt b/mysql-test/t/group_commit_no_optimize_thread-master.opt new file mode 100644 index 00000000000..97d8c106816 --- /dev/null +++ b/mysql-test/t/group_commit_no_optimize_thread-master.opt @@ -0,0 +1 @@ +--binlog-optimize-thread-scheduling=0 diff --git a/mysql-test/t/group_commit_no_optimize_thread.test b/mysql-test/t/group_commit_no_optimize_thread.test new file mode 100644 index 00000000000..38dab8aa37c --- /dev/null +++ b/mysql-test/t/group_commit_no_optimize_thread.test @@ -0,0 +1,115 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_log_bin.inc + +# Test some group commit code paths by using debug_sync to do controlled +# commits of 6 transactions: first 1 alone, then 3 as a group, then 2 as a +# group. +# +# Group 3 is allowed to race as far as possible ahead before group 2 finishes +# to check some edge case for concurrency control. + +CREATE TABLE t1 (a VARCHAR(10) PRIMARY KEY) ENGINE=innodb; + +SELECT variable_value INTO @commits FROM information_schema.global_status + WHERE variable_name = 'binlog_commits'; +SELECT variable_value INTO @group_commits FROM information_schema.global_status + WHERE variable_name = 'binlog_group_commits'; + +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); +connect(con3,localhost,root,,); +connect(con4,localhost,root,,); +connect(con5,localhost,root,,); +connect(con6,localhost,root,,); + +# Start group1 (with one thread) doing commit, waiting for +# group2 to queue up before finishing. + +connection con1; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group1_running WAIT_FOR group2_queued"; +send INSERT INTO t1 VALUES ("con1"); + +# Make group2 (with three threads) queue up. +# Make sure con2 is the group commit leader for group2. +# Make group2 wait with running commit_ordered() until group3 has committed. + +connection con2; +set DEBUG_SYNC= "now WAIT_FOR group1_running"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con2"; +SET DEBUG_SYNC= "commit_after_release_LOCK_log WAIT_FOR group3_committed"; +send INSERT INTO t1 VALUES ("con2"); +connection con3; +SET DEBUG_SYNC= "now WAIT_FOR group2_con2"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con3"; +send INSERT INTO t1 VALUES ("con3"); +connection con4; +SET DEBUG_SYNC= "now WAIT_FOR group2_con3"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL group2_con4"; +SET DEBUG_SYNC= "commit_after_group_run_commit_ordered SIGNAL group2_visible WAIT_FOR group2_checked"; +send INSERT INTO t1 VALUES ("con4"); + +# When group2 is queued, let group1 continue and queue group3. + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR group2_con4"; + +# At this point, trasaction 1 is still not visible as commit_ordered() has not +# been called yet. +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM t1 ORDER BY a; + +SET DEBUG_SYNC= "now SIGNAL group2_queued"; +connection con1; +reap; + +# Now transaction 1 is visible. +connection default; +SELECT * FROM t1 ORDER BY a; + +connection con5; +SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL group3_con5"; +SET DEBUG_SYNC= "commit_after_get_LOCK_log SIGNAL con5_leader WAIT_FOR con6_queued"; +send INSERT INTO t1 VALUES ("con5"); + +connection con6; +SET DEBUG_SYNC= "now WAIT_FOR con5_leader"; +SET DEBUG_SYNC= "commit_after_prepare_ordered SIGNAL con6_queued"; +send INSERT INTO t1 VALUES ("con6"); + +connection default; +SET DEBUG_SYNC= "now WAIT_FOR group3_con5"; +# Still only transaction 1 visible, as group2 have not yet run commit_ordered(). +SELECT * FROM t1 ORDER BY a; +SET DEBUG_SYNC= "now SIGNAL group3_committed"; +SET DEBUG_SYNC= "now WAIT_FOR group2_visible"; +# Now transactions 1-4 visible. +SELECT * FROM t1 ORDER BY a; +SET DEBUG_SYNC= "now SIGNAL group2_checked"; + +connection con2; +reap; + +connection con3; +reap; + +connection con4; +reap; + +connection con5; +reap; + +connection con6; +reap; + +connection default; +# Check all transactions finally visible. +SELECT * FROM t1 ORDER BY a; + +SELECT variable_value - @commits FROM information_schema.global_status + WHERE variable_name = 'binlog_commits'; +SELECT variable_value - @group_commits FROM information_schema.global_status + WHERE variable_name = 'binlog_group_commits'; + +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/sql/log.cc b/sql/log.cc index d18ded1f24e..c1e13452733 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2510,7 +2510,8 @@ const char *MYSQL_LOG::generate_name(const char *log_name, MYSQL_BIN_LOG::MYSQL_BIN_LOG() :bytes_written(0), prepared_xids(0), file_id(1), open_count(1), need_start_event(TRUE), - group_commit_queue(0), num_commits(0), num_group_commits(0), + group_commit_queue(0), group_commit_queue_busy(FALSE), + num_commits(0), num_group_commits(0), is_relay_log(0), description_event_for_exec(0), description_event_for_queue(0) { @@ -2567,6 +2568,7 @@ void MYSQL_BIN_LOG::init_pthread_objects() (void) my_pthread_mutex_init(&LOCK_index, MY_MUTEX_INIT_SLOW, "LOCK_index", MYF_NO_DEADLOCK_DETECTION); (void) pthread_cond_init(&update_cond, 0); + (void) pthread_cond_init(&COND_queue_busy, 0); } @@ -3989,6 +3991,7 @@ err: } +static my_bool opt_optimize_thread_scheduling= TRUE; #ifndef DBUG_OFF static ulong opt_binlog_dbug_fsync_sleep= 0; #endif @@ -4925,6 +4928,32 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) else trx_group_commit_leader(entry); + if (!opt_optimize_thread_scheduling) + { + /* For the leader, trx_group_commit_leader() already took the lock. */ + if (orig_queue != NULL) + pthread_mutex_lock(&LOCK_commit_ordered); + + DEBUG_SYNC(entry->thd, "commit_loop_entry_commit_ordered"); + ++num_commits; + if (entry->trx_data->using_xa && !entry->error) + run_commit_ordered(entry->thd, entry->all); + + group_commit_entry *next= entry->next; + if (!next) + { + group_commit_queue_busy= FALSE; + pthread_cond_signal(&COND_queue_busy); + DEBUG_SYNC(entry->thd, "commit_after_group_run_commit_ordered"); + } + pthread_mutex_unlock(&LOCK_commit_ordered); + + if (next) + { + next->thd->signal_wakeup_ready(); + } + } + if (!entry->error) return 0; @@ -5088,6 +5117,24 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log"); ++num_group_commits; + if (!opt_optimize_thread_scheduling) + { + /* + If we want to run commit_ordered() each in the transaction's own thread + context, then we need to mark the queue reserved; we need to finish all + threads in one group commit before the next group commit can be allowed + to proceed, and we cannot unlock a simple pthreads mutex in a different + thread from the one that locked it. + */ + + while (group_commit_queue_busy) + pthread_cond_wait(&COND_queue_busy, &LOCK_commit_ordered); + group_commit_queue_busy= TRUE; + + /* Note that we return with LOCK_commit_ordered locked! */ + DBUG_VOID_RETURN; + } + /* Wakeup each participant waiting for our group commit, first calling the commit_ordered() methods for any transactions doing 2-phase commit. @@ -6578,6 +6625,16 @@ static SHOW_VAR binlog_status_vars_top[]= { {NullS, NullS, SHOW_LONG} }; +static MYSQL_SYSVAR_BOOL( + optimize_thread_scheduling, + opt_optimize_thread_scheduling, + PLUGIN_VAR_READONLY, + "Run fast part of group commit in a single thread, to optimize kernel\n" + "thread scheduling", + NULL, + NULL, + 1); + #ifndef DBUG_OFF static MYSQL_SYSVAR_ULONG( dbug_fsync_sleep, @@ -6590,13 +6647,16 @@ static MYSQL_SYSVAR_ULONG( 0, ULONG_MAX, 0); +#endif static struct st_mysql_sys_var *binlog_sys_vars[]= { + MYSQL_SYSVAR(optimize_thread_scheduling), +#ifndef DBUG_OFF MYSQL_SYSVAR(dbug_fsync_sleep), +#endif NULL }; -#endif /* @@ -6634,11 +6694,7 @@ mysql_declare_plugin(binlog) NULL, /* Plugin Deinit */ 0x0100 /* 1.0 */, binlog_status_vars_top, /* status variables */ -#ifndef DBUG_OFF binlog_sys_vars, /* system variables */ -#else - NULL, /* system variables */ -#endif NULL /* config options */ } mysql_declare_plugin_end; diff --git a/sql/log.h b/sql/log.h index fcc9d5a711b..492a735c0ae 100644 --- a/sql/log.h +++ b/sql/log.h @@ -370,6 +370,14 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG bool no_auto_events; /* Queue of transactions queued up to participate in group commit. */ group_commit_entry *group_commit_queue; + /* + Condition variable to mark that the group commit queue is busy. + Used when each thread does it's own commit_ordered() (when + binlog_optimize_thread_scheduling=1). + Used with the LOCK_commit_ordered mutex. + */ + my_bool group_commit_queue_busy; + pthread_cond_t COND_queue_busy; /* Total number of committed transactions. */ ulonglong num_commits; /* Number of group commits done. */ From bc9f6021ff424ff7724183bd4ad95796a3b14053 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Mar 2011 14:29:23 +0200 Subject: [PATCH 30/35] MWL#116: After-review fixes. --- include/my_sys.h | 2 + mysql-test/r/xa_binlog.result | 32 +++ mysql-test/suite/binlog/t/binlog_ioerr.test | 3 +- mysql-test/suite/pbxt/r/pbxt_xa_binlog.result | 1 + mysql-test/suite/pbxt/t/pbxt_xa_binlog.test | 1 + mysql-test/t/group_commit_binlog_pos.test | 4 + mysql-test/t/xa_binlog.test | 32 +++ sql/handler.cc | 21 +- sql/handler.h | 9 +- sql/log.cc | 191 ++++++++---------- sql/sql_class.cc | 2 +- storage/xtradb/handler/ha_innodb.cc | 7 + storage/xtradb/include/srv0srv.h | 2 +- storage/xtradb/srv/srv0srv.c | 2 +- 14 files changed, 186 insertions(+), 123 deletions(-) create mode 100644 mysql-test/r/xa_binlog.result create mode 100644 mysql-test/t/xa_binlog.test diff --git a/include/my_sys.h b/include/my_sys.h index 2f8a44fedc0..7333c10df35 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -554,6 +554,8 @@ typedef int (*qsort2_cmp)(const void *, const void *, const void *); #define my_b_tell(info) ((info)->pos_in_file + \ (size_t) (*(info)->current_pos - (info)->request_pos)) +#define my_b_write_tell(info) ((info)->pos_in_file + \ + ((info)->write_pos - (info)->write_buffer)) #define my_b_get_buffer_start(info) (info)->request_pos #define my_b_get_bytes_in_buffer(info) (char*) (info)->read_end - \ diff --git a/mysql-test/r/xa_binlog.result b/mysql-test/r/xa_binlog.result new file mode 100644 index 00000000000..3ce64953902 --- /dev/null +++ b/mysql-test/r/xa_binlog.result @@ -0,0 +1,32 @@ +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; +SET binlog_format= mixed; +RESET MASTER; +XA START 'xatest'; +INSERT INTO t1 VALUES (1); +XA END 'xatest'; +XA PREPARE 'xatest'; +XA COMMIT 'xatest'; +XA START 'xatest'; +INSERT INTO t1 VALUES (2); +XA END 'xatest'; +XA COMMIT 'xatest' ONE PHASE; +BEGIN; +INSERT INTO t1 VALUES (3); +COMMIT; +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +SHOW BINLOG EVENTS LIMIT 1,9; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; INSERT INTO t1 VALUES (1) +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; INSERT INTO t1 VALUES (2) +master-bin.000001 # Query 1 # COMMIT +master-bin.000001 # Query 1 # BEGIN +master-bin.000001 # Query 1 # use `test`; INSERT INTO t1 VALUES (3) +master-bin.000001 # Xid 1 # COMMIT /* xid=XX */ +DROP TABLE t1; diff --git a/mysql-test/suite/binlog/t/binlog_ioerr.test b/mysql-test/suite/binlog/t/binlog_ioerr.test index e58cd93361a..e6f559c1a7b 100644 --- a/mysql-test/suite/binlog/t/binlog_ioerr.test +++ b/mysql-test/suite/binlog/t/binlog_ioerr.test @@ -21,7 +21,8 @@ SELECT * FROM t1; # Actually the output from this currently shows a bug. # The injected IO error leaves partially written transactions in the binlog in # the form of stray "BEGIN" events. -# These should disappear from the output if binlog error handling is improved. +# These should disappear from the output if binlog error handling is improved +# (see MySQL Bug#37148 and WL#1790). --replace_regex /\/\* xid=.* \*\//\/* XID *\// /Server ver: .*, Binlog ver: .*/Server ver: #, Binlog ver: #/ /table_id: [0-9]+/table_id: #/ --replace_column 1 BINLOG 2 POS 5 ENDPOS SHOW BINLOG EVENTS; diff --git a/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result b/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result index ec12a8a48ae..abfeebb5b96 100644 --- a/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result +++ b/mysql-test/suite/pbxt/r/pbxt_xa_binlog.result @@ -16,6 +16,7 @@ select * from t2; b 2 SET sql_log_bin = 0; +BEGIN; INSERT INTO t1 VALUES (3); INSERT INTO t2 VALUES (4); COMMIT; diff --git a/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test b/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test index e327c5a7b40..4a4578a5595 100644 --- a/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test +++ b/mysql-test/suite/pbxt/t/pbxt_xa_binlog.test @@ -20,6 +20,7 @@ select * from t2; # Test 2-phase commit when we disable binlogging. SET sql_log_bin = 0; +BEGIN; INSERT INTO t1 VALUES (3); INSERT INTO t2 VALUES (4); COMMIT; diff --git a/mysql-test/t/group_commit_binlog_pos.test b/mysql-test/t/group_commit_binlog_pos.test index 00cf6ab685f..c0ce382f0b4 100644 --- a/mysql-test/t/group_commit_binlog_pos.test +++ b/mysql-test/t/group_commit_binlog_pos.test @@ -8,6 +8,10 @@ # Don't test this under valgrind, memory leaks will occur as we crash --source include/not_valgrind.inc +# The test case currently uses grep and tail, which may be unavailable on +# some windows systems. But see MWL#191 for how to remove the need for grep. +--source include/not_windows.inc + # XtraDB stores the binlog position corresponding to the last commit, and # prints it during crash recovery. # Test that we get the correct position when we group commit several diff --git a/mysql-test/t/xa_binlog.test b/mysql-test/t/xa_binlog.test new file mode 100644 index 00000000000..48f1dc6dfaa --- /dev/null +++ b/mysql-test/t/xa_binlog.test @@ -0,0 +1,32 @@ +--source include/have_innodb.inc +--source include/have_log_bin.inc + +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; + +# Fix binlog format (otherwise SHOW BINLOG EVENTS will fluctuate). +SET binlog_format= mixed; + +RESET MASTER; + +XA START 'xatest'; +INSERT INTO t1 VALUES (1); +XA END 'xatest'; +XA PREPARE 'xatest'; +XA COMMIT 'xatest'; + +XA START 'xatest'; +INSERT INTO t1 VALUES (2); +XA END 'xatest'; +XA COMMIT 'xatest' ONE PHASE; + +BEGIN; +INSERT INTO t1 VALUES (3); +COMMIT; + +SELECT * FROM t1 ORDER BY a; + +--replace_column 2 # 5 # +--replace_regex /xid=[0-9]+/xid=XX/ +SHOW BINLOG EVENTS LIMIT 1,9; + +DROP TABLE t1; diff --git a/sql/handler.cc b/sql/handler.cc index 7db339f89e8..ca535f35c57 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1186,17 +1186,16 @@ int ha_commit_trans(THD *thd, bool all) Sic: we know that prepare() is not NULL since otherwise trans->no_2pc would have been set. */ - if ((err= ht->prepare(ht, thd, all))) - my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); + err= ht->prepare(ht, thd, all); status_var_increment(thd->status_var.ha_prepare_count); + if (err) + my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); if (err) goto err; - if (ht->prepare_ordered) - need_prepare_ordered= TRUE; - if (ht->commit_ordered) - need_commit_ordered= TRUE; + need_prepare_ordered|= (ht->prepare_ordered != NULL); + need_commit_ordered|= (ht->commit_ordered != NULL); } DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_ABORT();); @@ -1225,8 +1224,7 @@ int ha_commit_trans(THD *thd, bool all) /* Come here if error and we need to rollback. */ err: - if (!error) - error= 1; + error= 1; /* Transaction was rolled back */ ha_rollback_trans(thd, all); end: @@ -1877,8 +1875,11 @@ int ha_start_consistent_snapshot(THD *thd) bool warn= true; /* - Holding the LOCK_commit_ordered mutex ensures that for any transaction - we either see it committed in all engines, or in none. + Holding the LOCK_commit_ordered mutex ensures that we get the same + snapshot for all engines (including the binary log). This allows us + among other things to do backups with + START TRANSACTION WITH CONSISTENT SNAPSHOT and + have a consistent binlog position. */ pthread_mutex_lock(&LOCK_commit_ordered); plugin_foreach(thd, snapshot_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &warn); diff --git a/sql/handler.h b/sql/handler.h index e0a89f76271..6c515594ec3 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -670,7 +670,7 @@ struct handlerton Not that like prepare(), commit_ordered() is only called when 2-phase commit takes place. Ie. when no binary log and only a single engine participates in a transaction, one commit() is called, no - commit_orderd(). So engines must be prepared for this. + commit_ordered(). So engines must be prepared for this. The calls to commit_ordered() in multiple parallel transactions is guaranteed to happen in the same order in every participating @@ -683,7 +683,7 @@ struct handlerton transaction visible to other transactions, thereby making the order of transaction commits be defined by the order of commit_ordered() calls. - The intension is that commit_ordered() should do the minimal amount of + The intention is that commit_ordered() should do the minimal amount of work that needs to happen in consistent commit order among handlers. To preserve ordering, calls need to be serialised on a global mutex, so doing any time-consuming or blocking operations in commit_ordered() will @@ -727,7 +727,7 @@ struct handlerton order transactions will be eventually committed. Like commit_ordered(), prepare_ordered() calls are serialised to maintain - ordering, so the intension is that they should execute fast, with only + ordering, so the intention is that they should execute fast, with only the minimal amount of work needed to define commit order. Handlers can rely on this serialisation, and do not need to do any extra locking to avoid two prepare_ordered() calls running in parallel. @@ -747,8 +747,7 @@ struct handlerton require blocking all other commits for an indefinite time). When 2-phase commit is not used (eg. only one engine (and no binlog) in - transaction), prepare() is not called and in such cases prepare_ordered() - also is not called. + transaction), neither prepare() nor prepare_ordered() is called. */ void (*prepare_ordered)(handlerton *hton, THD *thd, bool all); int (*recover)(handlerton *hton, XID *xid_list, uint len); diff --git a/sql/log.cc b/sql/log.cc index d18ded1f24e..f78eb5890cf 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -63,6 +63,30 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all); static int binlog_rollback(handlerton *hton, THD *thd, bool all); static int binlog_prepare(handlerton *hton, THD *thd, bool all); +static LEX_STRING const write_error_msg= + { C_STRING_WITH_LEN("error writing to the binary log") }; + +#ifndef DBUG_OFF +static ulong opt_binlog_dbug_fsync_sleep= 0; +#endif + +static my_bool mutexes_inited; +pthread_mutex_t LOCK_prepare_ordered; +pthread_mutex_t LOCK_commit_ordered; + +static ulonglong binlog_status_var_num_commits; +static ulonglong binlog_status_var_num_group_commits; + +static SHOW_VAR binlog_status_vars_detail[]= +{ + {"commits", + (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, + {"group_commits", + (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, + {NullS, NullS, SHOW_LONG} +}; + + /** Silence all errors and warnings reported when performing a write to a log table. @@ -113,41 +137,6 @@ char *make_default_log_name(char *buff,const char* log_ext) MYF(MY_UNPACK_FILENAME|MY_REPLACE_EXT)); } -/* - Helper class to hold a mutex for the duration of the - block. - - Eliminates the need for explicit unlocking of mutexes on, e.g., - error returns. On passing a null pointer, the sentry will not do - anything. - */ -class Mutex_sentry -{ -public: - Mutex_sentry(pthread_mutex_t *mutex) - : m_mutex(mutex) - { - if (m_mutex) - pthread_mutex_lock(mutex); - } - - ~Mutex_sentry() - { - if (m_mutex) - pthread_mutex_unlock(m_mutex); -#ifndef DBUG_OFF - m_mutex= 0; -#endif - } - -private: - pthread_mutex_t *m_mutex; - - // It's not allowed to copy this object in any way - Mutex_sentry(Mutex_sentry const&); - void operator=(Mutex_sentry const&); -}; - /* Helper class to store binary log transaction data. */ @@ -155,7 +144,8 @@ class binlog_trx_data { public: binlog_trx_data() : at_least_one_stmt_committed(0), incident(FALSE), m_pending(0), - before_stmt_pos(MY_OFF_T_UNDEF), commit_bin_log_file_pos(0), using_xa(0) + before_stmt_pos(MY_OFF_T_UNDEF), commit_bin_log_file_pos(0), + using_xa(FALSE), xa_xid(0) { trans_log.end_of_file= max_binlog_cache_size; } @@ -271,6 +261,7 @@ public: XA, false if not. */ bool using_xa; + my_xid xa_xid; }; handlerton *binlog_hton; @@ -1415,6 +1406,8 @@ static int binlog_close_connection(handlerton *hton, THD *thd) thd The thread whose transaction should be ended trx_data Pointer to the transaction data to use + all True if the entire transaction should be ended, false if + only the statement transaction should be ended. end_ev The end event to use (COMMIT, ROLLBACK, or commit XID) DESCRIPTION @@ -1523,9 +1516,6 @@ binlog_truncate_trx_cache(THD *thd, binlog_trx_data *trx_data, bool all) DBUG_RETURN(error); } -static LEX_STRING const write_error_msg= - { C_STRING_WITH_LEN("error writing to the binary log") }; - static int binlog_prepare(handlerton *hton, THD *thd, bool all) { /* @@ -3989,10 +3979,6 @@ err: } -#ifndef DBUG_OFF -static ulong opt_binlog_dbug_fsync_sleep= 0; -#endif - bool MYSQL_BIN_LOG::flush_and_sync() { int err=0, fd=log_file.file; @@ -4004,9 +3990,8 @@ bool MYSQL_BIN_LOG::flush_and_sync() sync_binlog_counter= 0; err=my_sync(fd, MYF(MY_WME)); #ifndef DBUG_OFF - ulong usec_sleep= opt_binlog_dbug_fsync_sleep; - if (usec_sleep > 0) - my_sleep(usec_sleep); + if (opt_binlog_dbug_fsync_sleep > 0) + my_sleep(opt_binlog_dbug_fsync_sleep); #endif } return err; @@ -4444,7 +4429,19 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) #endif /* USING_TRANSACTIONS */ DBUG_PRINT("info",("event type: %d",event_info->get_type_code())); if (file == &log_file) + { pthread_mutex_lock(&LOCK_log); + /* + We did not want to take LOCK_log unless really necessary. + However, now that we hold LOCK_log, we must check is_open() again, lest + the log was closed just before. + */ + if (unlikely(!is_open())) + { + pthread_mutex_unlock(&LOCK_log); + DBUG_RETURN(error); + } + } /* No check for auto events flag here - this write method should @@ -4662,6 +4659,7 @@ uint MYSQL_BIN_LOG::next_file_id() int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache) { + safe_mutex_assert_owner(&LOCK_log); if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) return ER_ERROR_ON_WRITE; uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs; @@ -4821,11 +4819,14 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) Incident_log_event ev(thd, incident, write_error_msg); pthread_mutex_lock(&LOCK_log); - error= ev.write(&log_file); - if (!error && !(error= flush_and_sync())) + if (likely(is_open())) { - signal_update(); - rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); + error= ev.write(&log_file); + if (!error && !(error= flush_and_sync())) + { + signal_update(); + rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); + } } pthread_mutex_unlock(&LOCK_log); @@ -4869,6 +4870,9 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, binlog_trx_data *trx_data, entry.all= all; /* + Log "BEGIN" at the beginning of every transaction. Here, a transaction is + either a BEGIN..COMMIT block or a single statement in autocommit mode. + Create the necessary events here, where we have the correct THD (and thread context). @@ -4925,7 +4929,7 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) else trx_group_commit_leader(entry); - if (!entry->error) + if (likely(!entry->error)) return 0; switch (entry->error) @@ -4953,7 +4957,7 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) we need to mark it as not needed for recovery (unlog() is not called for a transaction if log_xid() fails). */ - if (entry->trx_data->using_xa) + if (entry->trx_data->using_xa && entry->trx_data->xa_xid) mark_xid_done(); return 1; @@ -5018,26 +5022,20 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) binlog_trx_data *trx_data= current->trx_data; IO_CACHE *cache= &trx_data->trans_log; - /* Skip log_xid for transactions without xid, marked by NULL end_event. */ - if (!current->end_event) - continue; - /* We only bother to write to the binary log if there is anything to write. */ if (my_b_tell(cache) > 0) { - current->error= write_transaction(current); - if (current->error) + if ((current->error= write_transaction(current))) current->commit_errno= errno; write_count++; } - trx_data->commit_bin_log_file_pos= - log_file.pos_in_file + (log_file.write_pos - log_file.write_buffer); - if (trx_data->using_xa) + trx_data->commit_bin_log_file_pos= my_b_write_tell(&log_file); + if (trx_data->using_xa && trx_data->xa_xid) xid_count++; } @@ -5095,6 +5093,8 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) current= queue; while (current != NULL) { + group_commit_entry *next; + DEBUG_SYNC(leader->thd, "commit_loop_entry_commit_ordered"); ++num_commits; if (current->trx_data->using_xa && !current->error) @@ -5104,7 +5104,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) Careful not to access current->next after waking up the other thread! As it may change immediately after wakeup. */ - group_commit_entry *next= current->next; + next= current->next; if (current != leader) // Don't wake up ourself current->thd->signal_wakeup_ready(); current= next; @@ -5120,19 +5120,7 @@ MYSQL_BIN_LOG::write_transaction(group_commit_entry *entry) { binlog_trx_data *trx_data= entry->trx_data; IO_CACHE *cache= &trx_data->trans_log; - /* - Log "BEGIN" at the beginning of every transaction. Here, a transaction is - either a BEGIN..COMMIT block or a single statement in autocommit mode. The - event was constructed in write_transaction_to_binlog(), in the thread - running the transaction. - Now this Query_log_event has artificial log_pos 0. It must be - adjusted to reflect the real position in the log. Not doing it - would confuse the slave: it would prevent this one from - knowing where he is in the master's binlog, which would result - in wrong positions being shown to the user, MASTER_POS_WAIT - undue waiting etc. - */ if (entry->begin_event->write(&log_file)) return ER_ERROR_ON_WRITE; @@ -5579,10 +5567,6 @@ void sql_print_information(const char *format, ...) } -static my_bool mutexes_inited; -pthread_mutex_t LOCK_prepare_ordered; -pthread_mutex_t LOCK_commit_ordered; - void TC_init() { @@ -5593,6 +5577,7 @@ TC_init() mutexes_inited= TRUE; } + void TC_destroy() { @@ -5604,39 +5589,42 @@ TC_destroy() } } + void TC_LOG::run_prepare_ordered(THD *thd, bool all) { Ha_trx_info *ha_info= all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list; + safe_mutex_assert_owner(&LOCK_prepare_ordered); for (; ha_info; ha_info= ha_info->next()) { handlerton *ht= ha_info->ht(); if (!ht->prepare_ordered) continue; - safe_mutex_assert_owner(&LOCK_prepare_ordered); ht->prepare_ordered(ht, thd, all); } } + void TC_LOG::run_commit_ordered(THD *thd, bool all) { Ha_trx_info *ha_info= all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list; + safe_mutex_assert_owner(&LOCK_commit_ordered); for (; ha_info; ha_info= ha_info->next()) { handlerton *ht= ha_info->ht(); if (!ht->commit_ordered) continue; - safe_mutex_assert_owner(&LOCK_commit_ordered); ht->commit_ordered(ht, thd, all); DEBUG_SYNC(thd, "commit_after_run_commit_ordered"); } } + int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) @@ -5666,10 +5654,9 @@ int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, pthread_mutex_unlock(&LOCK_prepare_ordered); } + cookie= 0; if (xid) cookie= log_one_transaction(xid); - else - cookie= 0; if (need_commit_ordered) { @@ -6391,13 +6378,23 @@ TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); trx_data->using_xa= TRUE; + trx_data->xa_xid= xid; if (xid) { Xid_log_event xid_event(thd, xid); err= binlog_flush_trx_cache(thd, trx_data, &xid_event, all); } else - err= binlog_flush_trx_cache(thd, trx_data, NULL, all); + { + /* + Empty xid occurs in XA COMMIT ... ONE PHASE. + In this case, we do not have a MySQL xid for the transaction, and the + external XA transaction coordinator will have to handle recovery if + needed. So we end the transaction with a plain COMMIT query event. + */ + Query_log_event end_event(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, 0); + err= binlog_flush_trx_cache(thd, trx_data, &end_event, all); + } DBUG_RETURN(!err); } @@ -6434,14 +6431,17 @@ TC_LOG_BINLOG::mark_xids_active(uint xid_count) void TC_LOG_BINLOG::mark_xid_done() { + my_bool send_signal; + DBUG_ENTER("TC_LOG_BINLOG::mark_xid_done"); pthread_mutex_lock(&LOCK_prep_xids); DBUG_ASSERT(prepared_xids > 0); - if (--prepared_xids == 0) { + send_signal= !--prepared_xids; + pthread_mutex_unlock(&LOCK_prep_xids); + if (send_signal) { DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids)); pthread_cond_signal(&COND_prep_xids); } - pthread_mutex_unlock(&LOCK_prep_xids); DBUG_VOID_RETURN; } @@ -6553,18 +6553,6 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) #endif /* INNODB_COMPATIBILITY_HOOKS */ -static ulonglong binlog_status_var_num_commits; -static ulonglong binlog_status_var_num_group_commits; - -static SHOW_VAR binlog_status_vars_detail[]= -{ - {"commits", - (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, - {"group_commits", - (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, - {NullS, NullS, SHOW_LONG} -}; - static int show_binlog_vars(THD *thd, SHOW_VAR *var, char *buff) { mysql_bin_log.set_status_variables(); @@ -6608,15 +6596,10 @@ static struct st_mysql_sys_var *binlog_sys_vars[]= void TC_LOG_BINLOG::set_status_variables() { - ulonglong num_commits, num_group_commits; - pthread_mutex_lock(&LOCK_commit_ordered); - num_commits= this->num_commits; - num_group_commits= this->num_group_commits; + binlog_status_var_num_commits= this->num_commits; + binlog_status_var_num_group_commits= this->num_group_commits; pthread_mutex_unlock(&LOCK_commit_ordered); - - binlog_status_var_num_commits= num_commits; - binlog_status_var_num_group_commits= num_group_commits; } struct st_mysql_storage_engine binlog_storage_engine= diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 563617f0a5b..0a656a799c8 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -4023,8 +4023,8 @@ THD::signal_wakeup_ready() { pthread_mutex_lock(&LOCK_wakeup_ready); wakeup_ready= true; - pthread_cond_signal(&COND_wakeup_ready); pthread_mutex_unlock(&LOCK_wakeup_ready); + pthread_cond_signal(&COND_wakeup_ready); } diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 7b9a4ee253c..d000c60d972 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -11643,6 +11643,12 @@ static MYSQL_SYSVAR_ENUM(adaptive_checkpoint, srv_adaptive_checkpoint, "Enable/Disable flushing along modified age. (none, reflex, [estimate])", NULL, innodb_adaptive_checkpoint_update, 2, &adaptive_checkpoint_typelib); +static MYSQL_SYSVAR_ULONG(enable_unsafe_group_commit, srv_deprecated_enable_unsafe_group_commit, + PLUGIN_VAR_RQCMDARG, + "Enable/Disable unsafe group commit when support_xa=OFF and use with binlog or other XA storage engine. " + "(Deprecated, and does nothing, group commit is always enabled in a safe way)", + NULL, NULL, 0, 0, 1, 0); + static MYSQL_SYSVAR_ULONG(expand_import, srv_expand_import, PLUGIN_VAR_RQCMDARG, "Enable/Disable converting automatically *.ibd files when import tablespace.", @@ -11748,6 +11754,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(read_ahead), MYSQL_SYSVAR(adaptive_checkpoint), MYSQL_SYSVAR(flush_log_at_trx_commit_session), + MYSQL_SYSVAR(enable_unsafe_group_commit), MYSQL_SYSVAR(expand_import), MYSQL_SYSVAR(extra_rsegments), MYSQL_SYSVAR(dict_size_limit), diff --git a/storage/xtradb/include/srv0srv.h b/storage/xtradb/include/srv0srv.h index dc455581350..1e0e45b83d8 100644 --- a/storage/xtradb/include/srv0srv.h +++ b/storage/xtradb/include/srv0srv.h @@ -230,7 +230,7 @@ extern ulong srv_ibuf_active_contract; extern ulong srv_ibuf_accel_rate; extern ulint srv_checkpoint_age_target; extern ulong srv_flush_neighbor_pages; -extern ulong srv_enable_unsafe_group_commit; +extern ulong srv_deprecated_enable_unsafe_group_commit; extern ulong srv_read_ahead; extern ulong srv_adaptive_checkpoint; diff --git a/storage/xtradb/srv/srv0srv.c b/storage/xtradb/srv/srv0srv.c index c1d0f255c64..3645360f9bc 100644 --- a/storage/xtradb/srv/srv0srv.c +++ b/storage/xtradb/srv/srv0srv.c @@ -407,7 +407,7 @@ UNIV_INTERN ulong srv_ibuf_accel_rate = 100; UNIV_INTERN ulint srv_checkpoint_age_target = 0; UNIV_INTERN ulong srv_flush_neighbor_pages = 1; /* 0:disable 1:enable */ -UNIV_INTERN ulong srv_enable_unsafe_group_commit = 0; /* 0:disable 1:enable */ +UNIV_INTERN ulong srv_deprecated_enable_unsafe_group_commit = 0; UNIV_INTERN ulong srv_read_ahead = 3; /* 1: random 2: linear 3: Both */ UNIV_INTERN ulong srv_adaptive_checkpoint = 0; /* 0: none 1: reflex 2: estimate */ From 6c8a23c8d78b688697b87606486ed75b251b8981 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 1 Apr 2011 10:12:59 +0200 Subject: [PATCH 31/35] MWL#136 after-review fixes. --- sql/log.cc | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index c136676e121..340d179f448 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -159,7 +159,7 @@ public: before_stmt_pos(MY_OFF_T_UNDEF), last_commit_pos_offset(0), using_xa(0) { trans_log.end_of_file= max_binlog_cache_size; - strcpy(last_commit_pos_file, ""); + last_commit_pos_file[0]= 0; } ~binlog_trx_data() @@ -217,7 +217,7 @@ public: incident= FALSE; trans_log.end_of_file= max_binlog_cache_size; using_xa= FALSE; - strcpy(last_commit_pos_file, ""); + last_commit_pos_file[0]= 0; last_commit_pos_offset= 0; DBUG_ASSERT(empty()); } @@ -4369,6 +4369,10 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + /* + Take mutex to protect against a reader seeing partial writes of 64-bit + offset on 32-bit CPUs. + */ pthread_mutex_lock(&LOCK_commit_ordered); last_commit_pos_offset= my_b_tell(&log_file); pthread_mutex_unlock(&LOCK_commit_ordered); @@ -4562,8 +4566,13 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) err_unlock: if (file == &log_file) { + my_off_t offset= my_b_tell(&log_file); + /* + Take mutex to protect against a reader seeing partial writes of 64-bit + offset on 32-bit CPUs. + */ pthread_mutex_lock(&LOCK_commit_ordered); - last_commit_pos_offset= my_b_tell(&log_file); + last_commit_pos_offset= offset; pthread_mutex_unlock(&LOCK_commit_ordered); pthread_mutex_unlock(&LOCK_log); } @@ -4856,6 +4865,7 @@ int query_error_code(THD *thd, bool not_killed) bool MYSQL_BIN_LOG::write_incident(THD *thd) { uint error= 0; + my_off_t offset; DBUG_ENTER("MYSQL_BIN_LOG::write_incident"); Incident incident= INCIDENT_LOST_EVENTS; Incident_log_event ev(thd, incident, write_error_msg); @@ -4867,8 +4877,13 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) signal_update(); rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } + offset= my_b_tell(&log_file); + /* + Take mutex to protect against a reader seeing partial writes of 64-bit + offset on 32-bit CPUs. + */ pthread_mutex_lock(&LOCK_commit_ordered); - last_commit_pos_offset= my_b_tell(&log_file); + last_commit_pos_offset= offset; pthread_mutex_unlock(&LOCK_commit_ordered); pthread_mutex_unlock(&LOCK_log); @@ -6678,8 +6693,7 @@ TC_LOG_BINLOG::set_status_variables(THD *thd) else trx_data= NULL; - bool have_snapshot= - (trx_data && 0 != strcmp(trx_data->last_commit_pos_file, "")); + bool have_snapshot= (trx_data && trx_data->last_commit_pos_file[0] != 0); pthread_mutex_lock(&LOCK_commit_ordered); binlog_status_var_num_commits= this->num_commits; binlog_status_var_num_group_commits= this->num_group_commits; From 288fdaca6dcb1e0d6d14d519b96c320cc117239a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Apr 2011 21:56:19 +0200 Subject: [PATCH 32/35] After-merge fixes. --- mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test index 13f57dd247a..e456884e8b7 100644 --- a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test +++ b/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test @@ -8,6 +8,10 @@ # Don't test this under valgrind, memory leaks will occur as we crash --source include/not_valgrind.inc +# The test case currently uses grep and tail, which may be unavailable on +# some windows systems. But see MWL#191 for how to remove the need for grep. +--source include/not_windows.inc + # XtraDB stores the binlog position corresponding to the last commit, and # prints it during crash recovery. # Test that we get the correct position when we group commit several From e3affa2a9af3c9853c424ccc7cd44c9fd5b27228 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Apr 2011 12:41:49 +0200 Subject: [PATCH 33/35] Change lock release to assert in XtraDB commit_ordered(); we never want to hold the latch across the 2-phase commit, and it is already released at start of prepare (if not before). Rename trx->active_trans to active_flag; since we changed the semantics of the field, renaming should help prevent silent merge errors. --- storage/xtradb/handler/ha_innodb.cc | 80 ++++++++++++++--------------- storage/xtradb/include/trx0trx.h | 4 +- storage/xtradb/trx/trx0trx.c | 2 +- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 29b74aa822b..f0e29b33f4c 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -32,7 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ /* TODO list for the InnoDB handler in 5.0: - - Remove the flag trx->active_trans and look at trx->conc_state + - Remove the flag trx->active_flag & TRX_ACTIVE_IN_MYSQL and look + at trx->conc_state - fix savepoint functions to use savepoint storage area - Find out what kind of problems the OS X case-insensitivity causes to table and database names; should we 'normalize' the names like we do @@ -1716,10 +1717,10 @@ innobase_query_caching_of_table_permitted( /* The call of row_search_.. will start a new transaction if it is not yet started */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(innodb_hton_ptr, thd); - trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } if (row_search_check_if_query_cache_permitted(trx, norm_name)) { @@ -1989,11 +1990,11 @@ ha_innobase::init_table_handle_for_HANDLER(void) /* Set the MySQL flag to mark that there is an active transaction */ - if ((prebuilt->trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((prebuilt->trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, user_thd); - prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } /* We did the necessary inits in this function, no need to repeat them @@ -2752,9 +2753,9 @@ innobase_start_trx_and_assign_read_view( /* Set the MySQL flag to mark that there is an active transaction */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(hton, thd); - trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } DBUG_RETURN(0); @@ -2887,14 +2888,13 @@ innobase_commit_ordered( trx = check_trx_exists(thd); - /* Since we will reserve the kernel mutex, we have to release - the search system latch first to obey the latching order. */ + /* Since we will reserve the kernel mutex, we must not be holding the + search system latch, or we will disobey the latching order. But we + already released it in innobase_xa_prepare() (if not before), so just + have an assert here.*/ + ut_ad(!trx->has_search_latch); - if (trx->has_search_latch) { - trx_search_latch_release_if_reserved(trx); - } - - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { /* We cannot throw error here; instead we will catch this error again in innobase_commit() and report it from there. */ @@ -2908,7 +2908,7 @@ innobase_commit_ordered( innobase_commit_ordered_2(trx, thd); - trx->active_trans |= TRX_ACTIVE_COMMIT_ORDERED; + trx->active_flag |= TRX_ACTIVE_COMMIT_ORDERED; DBUG_VOID_RETURN; } @@ -2939,11 +2939,11 @@ innobase_commit( the search system latch first to obey the latching order. */ if (trx->has_search_latch && - (trx->active_trans & TRX_ACTIVE_COMMIT_ORDERED) == 0) { + (trx->active_flag & TRX_ACTIVE_COMMIT_ORDERED) == 0) { trx_search_latch_release_if_reserved(trx); } - /* The flag TRX_ACTIVE_IN_MYSQL in trx->active_trans is set in + /* The flag TRX_ACTIVE_IN_MYSQL in trx->active_flag is set in 1. ::external_lock(), 2. ::start_stmt(), @@ -2958,18 +2958,18 @@ innobase_commit( For the time being, we play safe and do the cleanup though there should be nothing to clean up. */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but" - " trx->conc_state != TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL== 0," + " but trx->conc_state != TRX_NOT_STARTED"); } if (all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { /* Run the fast part of commit if we did not already. */ - if ((trx->active_trans & TRX_ACTIVE_COMMIT_ORDERED) == 0) { + if ((trx->active_flag & TRX_ACTIVE_COMMIT_ORDERED) == 0) { innobase_commit_ordered_2(trx, thd); } @@ -2979,7 +2979,7 @@ innobase_commit( /* We did the first part already in innobase_commit_ordered(), Now finish by doing a write + flush of logs. */ trx_commit_complete_for_mysql(trx); - trx->active_trans = 0; + trx->active_flag = 0; } else { /* We just mark the SQL statement ended and do not do a @@ -3062,7 +3062,7 @@ innobase_rollback( || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { error = trx_rollback_for_mysql(trx); - trx->active_trans = 0; + trx->active_flag = 0; } else { error = trx_rollback_last_sql_stat_for_mysql(trx); } @@ -3206,7 +3206,7 @@ innobase_savepoint( innobase_release_stat_resources(trx); /* cannot happen outside of transaction */ - DBUG_ASSERT(trx->active_trans & TRX_ACTIVE_IN_MYSQL); + DBUG_ASSERT(trx->active_flag & TRX_ACTIVE_IN_MYSQL); /* TODO: use provided savepoint data area to store savepoint data */ char name[64]; @@ -3236,11 +3236,11 @@ innobase_close_connection( ut_a(trx); - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but" - " trx->conc_state != TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL == 0," + " but trx->conc_state != TRX_NOT_STARTED"); } @@ -5165,7 +5165,7 @@ no_commit: /* Altering to InnoDB format */ innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; /* We will need an IX lock on the destination table. */ prebuilt->sql_stat_start = TRUE; } else { @@ -5181,7 +5181,7 @@ no_commit: locks, so they have to be acquired again. */ innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; /* Re-acquire the table lock on the source table. */ row_lock_table_for_mysql(prebuilt, src_table, mode); /* We will need an IX lock on the destination table. */ @@ -9095,10 +9095,10 @@ ha_innobase::start_stmt( trx->detailed_error[0] = '\0'; /* Set the MySQL flag to mark that there is an active transaction */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } else { innobase_register_stmt(ht, thd); } @@ -9196,10 +9196,10 @@ ha_innobase::external_lock( /* Set the MySQL flag to mark that there is an active transaction */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } else if (trx->n_mysql_tables_in_use == 0) { innobase_register_stmt(ht, thd); } @@ -9297,7 +9297,7 @@ ha_innobase::external_lock( prebuilt->used_in_HANDLER = FALSE; if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) != 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) != 0) { innobase_commit(ht, thd, TRUE); } } else { @@ -9382,10 +9382,10 @@ ha_innobase::transactional_table_lock( /* MySQL is setting a new transactional table lock */ /* Set the MySQL flag to mark that there is an active transaction */ - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans |= TRX_ACTIVE_IN_MYSQL; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } if (THDVAR(thd, table_locks) && thd_in_lock_tables(thd)) { @@ -10439,11 +10439,11 @@ innobase_xa_prepare( innobase_release_stat_resources(trx); - if ((trx->active_trans & TRX_ACTIVE_IN_MYSQL) == 0 && + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but trx->conc_state != " - "TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL == 0, but" + " trx->conc_state != TRX_NOT_STARTED"); } if (all @@ -10452,7 +10452,7 @@ innobase_xa_prepare( /* We were instructed to prepare the whole transaction, or this is an SQL statement end and autocommit is on */ - ut_ad(trx->active_trans & TRX_ACTIVE_IN_MYSQL); + ut_ad(trx->active_flag & TRX_ACTIVE_IN_MYSQL); error = (int) trx_prepare_for_mysql(trx); } else { diff --git a/storage/xtradb/include/trx0trx.h b/storage/xtradb/include/trx0trx.h index 36f9c464c2b..47b935a7efd 100644 --- a/storage/xtradb/include/trx0trx.h +++ b/storage/xtradb/include/trx0trx.h @@ -511,7 +511,7 @@ struct trx_struct{ in that case we must flush the log in trx_commit_complete_for_mysql() */ ulint duplicates; /*!< TRX_DUP_IGNORE | TRX_DUP_REPLACE */ - ulint active_trans; /*!< TRX_ACTIVE_IN_MYSQL - set if a + ulint active_flag; /*!< TRX_ACTIVE_IN_MYSQL - set if a transaction in MySQL is active. TRX_ACTIVE_COMMIT_ORDERED - set if innobase_commit_ordered has run */ @@ -825,7 +825,7 @@ Multiple flags can be combined with bitwise OR. */ #define TRX_SIG_OTHER_SESS 1 /* sent by another session (which must hold rights to this) */ -/* Flag bits for trx_struct.active_trans */ +/* Flag bits for trx_struct.active_flag */ #define TRX_ACTIVE_IN_MYSQL (1<<0) #define TRX_ACTIVE_COMMIT_ORDERED (1<<1) diff --git a/storage/xtradb/trx/trx0trx.c b/storage/xtradb/trx/trx0trx.c index 75bbe1b342a..06778bbbef7 100644 --- a/storage/xtradb/trx/trx0trx.c +++ b/storage/xtradb/trx/trx0trx.c @@ -121,7 +121,7 @@ trx_create( trx->table_id = ut_dulint_zero; trx->mysql_thd = NULL; - trx->active_trans = 0; + trx->active_flag = 0; trx->duplicates = 0; trx->n_mysql_tables_in_use = 0; From 7f650d4be5b0de743c57d01f79a99b81aa0ce349 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Apr 2011 13:55:18 +0200 Subject: [PATCH 34/35] MWL#116: After-review fixes. Also implement the InnoDB changes for group commit into innodb_plugin. --- .../r/binlog_consistent.result | 0 .../innodb_plugin}/r/group_commit.result | 0 .../r/group_commit_binlog_pos.result | 0 ...ommit_binlog_pos_no_optimize_thread.result | 0 .../r/group_commit_crash.result | 0 ...oup_commit_crash_no_optimize_thread.result | 0 .../r/group_commit_no_optimize_thread.result | 0 .../t/binlog_consistent.test | 2 +- .../innodb_plugin}/t/group_commit.test | 2 +- .../t/group_commit_binlog_pos-master.opt | 0 .../t/group_commit_binlog_pos.test | 2 +- ...t_binlog_pos_no_optimize_thread-master.opt | 0 ..._commit_binlog_pos_no_optimize_thread.test | 2 +- .../t/group_commit_crash-master.opt | 0 .../innodb_plugin}/t/group_commit_crash.test | 2 +- ...commit_crash_no_optimize_thread-master.opt | 0 ...group_commit_crash_no_optimize_thread.test | 2 +- ...group_commit_no_optimize_thread-master.opt | 0 .../t/group_commit_no_optimize_thread.test | 2 +- storage/innodb_plugin/handler/ha_innodb.cc | 271 ++++++++++-------- storage/innodb_plugin/handler/ha_innodb.h | 15 +- storage/innodb_plugin/include/trx0trx.h | 11 +- storage/innodb_plugin/trx/trx0trx.c | 2 +- 23 files changed, 171 insertions(+), 142 deletions(-) rename mysql-test/suite/{binlog => innodb_plugin}/r/binlog_consistent.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit_binlog_pos.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit_binlog_pos_no_optimize_thread.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit_crash.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit_crash_no_optimize_thread.result (100%) rename mysql-test/{ => suite/innodb_plugin}/r/group_commit_no_optimize_thread.result (100%) rename mysql-test/suite/{binlog => innodb_plugin}/t/binlog_consistent.test (97%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit.test (98%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_binlog_pos-master.opt (100%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_binlog_pos.test (98%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_binlog_pos_no_optimize_thread-master.opt (100%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_binlog_pos_no_optimize_thread.test (98%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_crash-master.opt (100%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_crash.test (98%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_crash_no_optimize_thread-master.opt (100%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_crash_no_optimize_thread.test (98%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_no_optimize_thread-master.opt (100%) rename mysql-test/{ => suite/innodb_plugin}/t/group_commit_no_optimize_thread.test (98%) diff --git a/mysql-test/suite/binlog/r/binlog_consistent.result b/mysql-test/suite/innodb_plugin/r/binlog_consistent.result similarity index 100% rename from mysql-test/suite/binlog/r/binlog_consistent.result rename to mysql-test/suite/innodb_plugin/r/binlog_consistent.result diff --git a/mysql-test/r/group_commit.result b/mysql-test/suite/innodb_plugin/r/group_commit.result similarity index 100% rename from mysql-test/r/group_commit.result rename to mysql-test/suite/innodb_plugin/r/group_commit.result diff --git a/mysql-test/r/group_commit_binlog_pos.result b/mysql-test/suite/innodb_plugin/r/group_commit_binlog_pos.result similarity index 100% rename from mysql-test/r/group_commit_binlog_pos.result rename to mysql-test/suite/innodb_plugin/r/group_commit_binlog_pos.result diff --git a/mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result b/mysql-test/suite/innodb_plugin/r/group_commit_binlog_pos_no_optimize_thread.result similarity index 100% rename from mysql-test/r/group_commit_binlog_pos_no_optimize_thread.result rename to mysql-test/suite/innodb_plugin/r/group_commit_binlog_pos_no_optimize_thread.result diff --git a/mysql-test/r/group_commit_crash.result b/mysql-test/suite/innodb_plugin/r/group_commit_crash.result similarity index 100% rename from mysql-test/r/group_commit_crash.result rename to mysql-test/suite/innodb_plugin/r/group_commit_crash.result diff --git a/mysql-test/r/group_commit_crash_no_optimize_thread.result b/mysql-test/suite/innodb_plugin/r/group_commit_crash_no_optimize_thread.result similarity index 100% rename from mysql-test/r/group_commit_crash_no_optimize_thread.result rename to mysql-test/suite/innodb_plugin/r/group_commit_crash_no_optimize_thread.result diff --git a/mysql-test/r/group_commit_no_optimize_thread.result b/mysql-test/suite/innodb_plugin/r/group_commit_no_optimize_thread.result similarity index 100% rename from mysql-test/r/group_commit_no_optimize_thread.result rename to mysql-test/suite/innodb_plugin/r/group_commit_no_optimize_thread.result diff --git a/mysql-test/suite/binlog/t/binlog_consistent.test b/mysql-test/suite/innodb_plugin/t/binlog_consistent.test similarity index 97% rename from mysql-test/suite/binlog/t/binlog_consistent.test rename to mysql-test/suite/innodb_plugin/t/binlog_consistent.test index 21e8ac111d4..1a2c84f3439 100644 --- a/mysql-test/suite/binlog/t/binlog_consistent.test +++ b/mysql-test/suite/innodb_plugin/t/binlog_consistent.test @@ -1,6 +1,6 @@ --source include/have_log_bin.inc --source include/have_binlog_format_mixed_or_statement.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc RESET MASTER; diff --git a/mysql-test/t/group_commit.test b/mysql-test/suite/innodb_plugin/t/group_commit.test similarity index 98% rename from mysql-test/t/group_commit.test rename to mysql-test/suite/innodb_plugin/t/group_commit.test index 7c87c166844..4c60b7de331 100644 --- a/mysql-test/t/group_commit.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit.test @@ -1,5 +1,5 @@ --source include/have_debug_sync.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc # Test some group commit code paths by using debug_sync to do controlled diff --git a/mysql-test/t/group_commit_binlog_pos-master.opt b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos-master.opt similarity index 100% rename from mysql-test/t/group_commit_binlog_pos-master.opt rename to mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos-master.opt diff --git a/mysql-test/t/group_commit_binlog_pos.test b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos.test similarity index 98% rename from mysql-test/t/group_commit_binlog_pos.test rename to mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos.test index c0ce382f0b4..0e5c60f0e94 100644 --- a/mysql-test/t/group_commit_binlog_pos.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos.test @@ -1,5 +1,5 @@ --source include/have_debug_sync.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc --source include/have_binlog_format_mixed_or_statement.inc diff --git a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos_no_optimize_thread-master.opt similarity index 100% rename from mysql-test/t/group_commit_binlog_pos_no_optimize_thread-master.opt rename to mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos_no_optimize_thread-master.opt diff --git a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos_no_optimize_thread.test similarity index 98% rename from mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test rename to mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos_no_optimize_thread.test index e456884e8b7..b044fa21ad3 100644 --- a/mysql-test/t/group_commit_binlog_pos_no_optimize_thread.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit_binlog_pos_no_optimize_thread.test @@ -1,5 +1,5 @@ --source include/have_debug_sync.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc --source include/have_binlog_format_mixed_or_statement.inc diff --git a/mysql-test/t/group_commit_crash-master.opt b/mysql-test/suite/innodb_plugin/t/group_commit_crash-master.opt similarity index 100% rename from mysql-test/t/group_commit_crash-master.opt rename to mysql-test/suite/innodb_plugin/t/group_commit_crash-master.opt diff --git a/mysql-test/t/group_commit_crash.test b/mysql-test/suite/innodb_plugin/t/group_commit_crash.test similarity index 98% rename from mysql-test/t/group_commit_crash.test rename to mysql-test/suite/innodb_plugin/t/group_commit_crash.test index 772739a897a..654ca313aab 100644 --- a/mysql-test/t/group_commit_crash.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit_crash.test @@ -6,7 +6,7 @@ # Binary must be compiled with debug for crash to occur --source include/have_debug.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc let $file_format_check=`SELECT @@innodb_file_format_check`; diff --git a/mysql-test/t/group_commit_crash_no_optimize_thread-master.opt b/mysql-test/suite/innodb_plugin/t/group_commit_crash_no_optimize_thread-master.opt similarity index 100% rename from mysql-test/t/group_commit_crash_no_optimize_thread-master.opt rename to mysql-test/suite/innodb_plugin/t/group_commit_crash_no_optimize_thread-master.opt diff --git a/mysql-test/t/group_commit_crash_no_optimize_thread.test b/mysql-test/suite/innodb_plugin/t/group_commit_crash_no_optimize_thread.test similarity index 98% rename from mysql-test/t/group_commit_crash_no_optimize_thread.test rename to mysql-test/suite/innodb_plugin/t/group_commit_crash_no_optimize_thread.test index 772739a897a..654ca313aab 100644 --- a/mysql-test/t/group_commit_crash_no_optimize_thread.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit_crash_no_optimize_thread.test @@ -6,7 +6,7 @@ # Binary must be compiled with debug for crash to occur --source include/have_debug.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc let $file_format_check=`SELECT @@innodb_file_format_check`; diff --git a/mysql-test/t/group_commit_no_optimize_thread-master.opt b/mysql-test/suite/innodb_plugin/t/group_commit_no_optimize_thread-master.opt similarity index 100% rename from mysql-test/t/group_commit_no_optimize_thread-master.opt rename to mysql-test/suite/innodb_plugin/t/group_commit_no_optimize_thread-master.opt diff --git a/mysql-test/t/group_commit_no_optimize_thread.test b/mysql-test/suite/innodb_plugin/t/group_commit_no_optimize_thread.test similarity index 98% rename from mysql-test/t/group_commit_no_optimize_thread.test rename to mysql-test/suite/innodb_plugin/t/group_commit_no_optimize_thread.test index 38dab8aa37c..ae3f1b516d4 100644 --- a/mysql-test/t/group_commit_no_optimize_thread.test +++ b/mysql-test/suite/innodb_plugin/t/group_commit_no_optimize_thread.test @@ -1,5 +1,5 @@ --source include/have_debug_sync.inc ---source include/have_innodb.inc +--source include/have_innodb_plugin.inc --source include/have_log_bin.inc # Test some group commit code paths by using debug_sync to do controlled diff --git a/storage/innodb_plugin/handler/ha_innodb.cc b/storage/innodb_plugin/handler/ha_innodb.cc index 5988a3fb431..9eebf9d30f0 100644 --- a/storage/innodb_plugin/handler/ha_innodb.cc +++ b/storage/innodb_plugin/handler/ha_innodb.cc @@ -32,7 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ /* TODO list for the InnoDB handler in 5.0: - - Remove the flag trx->active_trans and look at trx->conc_state + - Remove the flag trx->active_flag & TRX_ACTIVE_IN_MYSQL and look + at trx->conc_state - fix savepoint functions to use savepoint storage area - Find out what kind of problems the OS X case-insensitivity causes to table and database names; should we 'normalize' the names like we do @@ -102,8 +103,6 @@ bool check_global_access(THD *thd, ulong want_access); /** to protect innobase_open_files */ static pthread_mutex_t innobase_share_mutex; -/** to force correct commit order in binlog */ -static pthread_mutex_t prepare_commit_mutex; static ulong commit_threads = 0; static pthread_mutex_t commit_threads_m; static pthread_cond_t commit_cond; @@ -197,6 +196,7 @@ static const char* innobase_change_buffering_values[IBUF_USE_COUNT] = { static INNOBASE_SHARE *get_share(const char *table_name); static void free_share(INNOBASE_SHARE *share); static int innobase_close_connection(handlerton *hton, THD* thd); +static void innobase_commit_ordered(handlerton *hton, THD* thd, bool all); static int innobase_commit(handlerton *hton, THD* thd, bool all); static int innobase_rollback(handlerton *hton, THD* thd, bool all); static int innobase_rollback_to_savepoint(handlerton *hton, THD* thd, @@ -1320,7 +1320,6 @@ innobase_trx_init( trx_t* trx) /*!< in/out: InnoDB transaction handle */ { DBUG_ENTER("innobase_trx_init"); - DBUG_ASSERT(EQ_CURRENT_THD(thd)); DBUG_ASSERT(thd == trx->mysql_thd); trx->check_foreigns = !thd_test_options( @@ -1369,8 +1368,6 @@ check_trx_exists( { trx_t*& trx = thd_to_trx(thd); - ut_ad(EQ_CURRENT_THD(thd)); - if (trx == NULL) { trx = innobase_trx_allocate(thd); } else if (UNIV_UNLIKELY(trx->magic_n != TRX_MAGIC_N)) { @@ -1636,10 +1633,10 @@ innobase_query_caching_of_table_permitted( /* The call of row_search_.. will start a new transaction if it is not yet started */ - if (trx->active_trans == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(innodb_hton_ptr, thd); - trx->active_trans = 1; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } if (row_search_check_if_query_cache_permitted(trx, norm_name)) { @@ -1909,11 +1906,11 @@ ha_innobase::init_table_handle_for_HANDLER(void) /* Set the MySQL flag to mark that there is an active transaction */ - if (prebuilt->trx->active_trans == 0) { + if ((prebuilt->trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, user_thd); - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } /* We did the necessary inits in this function, no need to repeat them @@ -1964,6 +1961,8 @@ innobase_init( innobase_hton->savepoint_set=innobase_savepoint; innobase_hton->savepoint_rollback=innobase_rollback_to_savepoint; innobase_hton->savepoint_release=innobase_release_savepoint; + innobase_hton->prepare_ordered=NULL; + innobase_hton->commit_ordered=innobase_commit_ordered; innobase_hton->commit=innobase_commit; innobase_hton->rollback=innobase_rollback; innobase_hton->prepare=innobase_xa_prepare; @@ -2268,7 +2267,6 @@ innobase_change_buffering_inited_ok: innobase_open_tables = hash_create(200); pthread_mutex_init(&innobase_share_mutex, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&prepare_commit_mutex, MY_MUTEX_INIT_FAST); pthread_mutex_init(&commit_threads_m, MY_MUTEX_INIT_FAST); pthread_mutex_init(&commit_cond_m, MY_MUTEX_INIT_FAST); pthread_cond_init(&commit_cond, NULL); @@ -2323,7 +2321,6 @@ innobase_end( my_free(internal_innobase_data_file_path, MYF(MY_ALLOW_ZERO_PTR)); pthread_mutex_destroy(&innobase_share_mutex); - pthread_mutex_destroy(&prepare_commit_mutex); pthread_mutex_destroy(&commit_threads_m); pthread_mutex_destroy(&commit_cond_m); pthread_cond_destroy(&commit_cond); @@ -2422,14 +2419,118 @@ innobase_start_trx_and_assign_read_view( /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(hton, thd); - trx->active_trans = 1; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } DBUG_RETURN(0); } +static +void +innobase_commit_ordered_2( +/*============*/ + trx_t* trx, /*!< in: Innodb transaction */ + THD* thd) /*!< in: MySQL thread handle */ +{ + ulonglong tmp_pos; + DBUG_ENTER("innobase_commit_ordered"); + + /* We need current binlog position for ibbackup to work. + Note, the position is current because commit_ordered is guaranteed + to be called in same sequenece as writing to binlog. */ + +retry: + if (innobase_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads++; + + if (commit_threads > innobase_commit_concurrency) { + commit_threads--; + pthread_cond_wait(&commit_cond, + &commit_cond_m); + pthread_mutex_unlock(&commit_cond_m); + goto retry; + } + else { + pthread_mutex_unlock(&commit_cond_m); + } + } + + mysql_bin_log_commit_pos(thd, &tmp_pos, &(trx->mysql_log_file_name)); + trx->mysql_log_offset = (ib_int64_t) tmp_pos; + + /* Don't do write + flush right now. For group commit + to work we want to do the flush in the innobase_commit() + method, which runs without holding any locks. */ + trx->flush_log_later = TRUE; + innobase_commit_low(trx); + trx->flush_log_later = FALSE; + + if (innobase_commit_concurrency > 0) { + pthread_mutex_lock(&commit_cond_m); + commit_threads--; + pthread_cond_signal(&commit_cond); + pthread_mutex_unlock(&commit_cond_m); + } + + DBUG_VOID_RETURN; +} + +/*****************************************************************//** +Perform the first, fast part of InnoDB commit. + +Doing it in this call ensures that we get the same commit order here +as in binlog and any other participating transactional storage engines. + +Note that we want to do as little as really needed here, as we run +under a global mutex. The expensive fsync() is done later, in +innobase_commit(), without a lock so group commit can take place. + +Note also that this method can be called from a different thread than +the one handling the rest of the transaction. */ +static +void +innobase_commit_ordered( +/*============*/ + handlerton *hton, /*!< in: Innodb handlerton */ + THD* thd, /*!< in: MySQL thread handle of the user for whom + the transaction should be committed */ + bool all) /*!< in: TRUE - commit transaction + FALSE - the current SQL statement ended */ +{ + trx_t* trx; + DBUG_ENTER("innobase_commit_ordered"); + DBUG_ASSERT(hton == innodb_hton_ptr); + + trx = check_trx_exists(thd); + + /* Since we will reserve the kernel mutex, we must not be holding the + search system latch, or we will disobey the latching order. But we + already released it in innobase_xa_prepare() (if not before), so just + have an assert here.*/ + ut_ad(!trx->has_search_latch); + + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 + && trx->conc_state != TRX_NOT_STARTED) { + /* We cannot throw error here; instead we will catch this error + again in innobase_commit() and report it from there. */ + DBUG_VOID_RETURN; + } + + /* commit_ordered is only called when committing the whole transaction + (or an SQL statement when autocommit is on). */ + DBUG_ASSERT(all || + (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); + + innobase_commit_ordered_2(trx, thd); + + trx->active_flag |= TRX_ACTIVE_COMMIT_ORDERED; + + DBUG_VOID_RETURN; +} + /*****************************************************************//** Commits a transaction in an InnoDB database or marks an SQL statement ended. @@ -2455,11 +2556,12 @@ innobase_commit( /* Since we will reserve the kernel mutex, we have to release the search system latch first to obey the latching order. */ - if (trx->has_search_latch) { + if (trx->has_search_latch && + (trx->active_flag & TRX_ACTIVE_COMMIT_ORDERED) == 0) { trx_search_latch_release_if_reserved(trx); } - /* The flag trx->active_trans is set to 1 in + /* The flag TRX_ACTIVE_IN_MYSQL in trx->active_flag is set in 1. ::external_lock(), 2. ::start_stmt(), @@ -2469,81 +2571,33 @@ innobase_commit( 6. innobase_start_trx_and_assign_read_view(), 7. ::transactional_table_lock() - and it is only set to 0 in a commit or a rollback. If it is 0 we know + and it is only cleared in a commit or a rollback. If it is unset we know there cannot be resources to be freed and we could return immediately. For the time being, we play safe and do the cleanup though there should be nothing to clean up. */ - if (trx->active_trans == 0 + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but" - " trx->conc_state != TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL== 0," + " but trx->conc_state != TRX_NOT_STARTED"); } + if (all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { + /* Run the fast part of commit if we did not already. */ + if ((trx->active_flag & TRX_ACTIVE_COMMIT_ORDERED) == 0) { + innobase_commit_ordered_2(trx, thd); + } + /* We were instructed to commit the whole transaction, or this is an SQL statement end and autocommit is on */ - /* We need current binlog position for ibbackup to work. - Note, the position is current because of - prepare_commit_mutex */ -retry: - if (innobase_commit_concurrency > 0) { - pthread_mutex_lock(&commit_cond_m); - commit_threads++; - - if (commit_threads > innobase_commit_concurrency) { - commit_threads--; - pthread_cond_wait(&commit_cond, - &commit_cond_m); - pthread_mutex_unlock(&commit_cond_m); - goto retry; - } - else { - pthread_mutex_unlock(&commit_cond_m); - } - } - - /* The following calls to read the MySQL binary log - file name and the position return consistent results: - 1) Other InnoDB transactions cannot intervene between - these calls as we are holding prepare_commit_mutex. - 2) Binary logging of other engines is not relevant - to InnoDB as all InnoDB requires is that committing - InnoDB transactions appear in the same order in the - MySQL binary log as they appear in InnoDB logs. - 3) A MySQL log file rotation cannot happen because - MySQL protects against this by having a counter of - transactions in prepared state and it only allows - a rotation when the counter drops to zero. See - LOCK_prep_xids and COND_prep_xids in log.cc. */ - trx->mysql_log_file_name = mysql_bin_log_file_name(); - trx->mysql_log_offset = (ib_int64_t) mysql_bin_log_file_pos(); - - /* Don't do write + flush right now. For group commit - to work we want to do the flush after releasing the - prepare_commit_mutex. */ - trx->flush_log_later = TRUE; - innobase_commit_low(trx); - trx->flush_log_later = FALSE; - - if (innobase_commit_concurrency > 0) { - pthread_mutex_lock(&commit_cond_m); - commit_threads--; - pthread_cond_signal(&commit_cond); - pthread_mutex_unlock(&commit_cond_m); - } - - if (trx->active_trans == 2) { - - pthread_mutex_unlock(&prepare_commit_mutex); - } - - /* Now do a write + flush of logs. */ + /* We did the first part already in innobase_commit_ordered(), + Now finish by doing a write + flush of logs. */ trx_commit_complete_for_mysql(trx); - trx->active_trans = 0; + trx->active_flag = 0; } else { /* We just mark the SQL statement ended and do not do a @@ -2616,7 +2670,7 @@ innobase_rollback( || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { error = trx_rollback_for_mysql(trx); - trx->active_trans = 0; + trx->active_flag = 0; } else { error = trx_rollback_last_sql_stat_for_mysql(trx); } @@ -2760,7 +2814,7 @@ innobase_savepoint( innobase_release_stat_resources(trx); /* cannot happen outside of transaction */ - DBUG_ASSERT(trx->active_trans); + DBUG_ASSERT(trx->active_flag & TRX_ACTIVE_IN_MYSQL); /* TODO: use provided savepoint data area to store savepoint data */ char name[64]; @@ -2790,11 +2844,11 @@ innobase_close_connection( ut_a(trx); - if (trx->active_trans == 0 + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but" - " trx->conc_state != TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL == 0," + " but trx->conc_state != TRX_NOT_STARTED"); } @@ -4691,7 +4745,7 @@ no_commit: /* Altering to InnoDB format */ innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; /* We will need an IX lock on the destination table. */ prebuilt->sql_stat_start = TRUE; } else { @@ -4707,7 +4761,7 @@ no_commit: locks, so they have to be acquired again. */ innobase_commit(ht, user_thd, 1); /* Note that this transaction is still active. */ - prebuilt->trx->active_trans = 1; + prebuilt->trx->active_flag |= TRX_ACTIVE_IN_MYSQL; /* Re-acquire the table lock on the source table. */ row_lock_table_for_mysql(prebuilt, src_table, mode); /* We will need an IX lock on the destination table. */ @@ -8498,10 +8552,10 @@ ha_innobase::start_stmt( trx->detailed_error[0] = '\0'; /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } else { innobase_register_stmt(ht, thd); } @@ -8599,10 +8653,10 @@ ha_innobase::external_lock( /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } else if (trx->n_mysql_tables_in_use == 0) { innobase_register_stmt(ht, thd); } @@ -8681,7 +8735,7 @@ ha_innobase::external_lock( prebuilt->used_in_HANDLER = FALSE; if (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { - if (trx->active_trans != 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) != 0) { innobase_commit(ht, thd, TRUE); } } else { @@ -8762,10 +8816,10 @@ ha_innobase::transactional_table_lock( /* MySQL is setting a new transactional table lock */ /* Set the MySQL flag to mark that there is an active transaction */ - if (trx->active_trans == 0) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0) { innobase_register_trx_and_stmt(ht, thd); - trx->active_trans = 1; + trx->active_flag |= TRX_ACTIVE_IN_MYSQL; } if (THDVAR(thd, table_locks) && thd_in_lock_tables(thd)) { @@ -9821,10 +9875,11 @@ innobase_xa_prepare( innobase_release_stat_resources(trx); - if (trx->active_trans == 0 && trx->conc_state != TRX_NOT_STARTED) { + if ((trx->active_flag & TRX_ACTIVE_IN_MYSQL) == 0 && + trx->conc_state != TRX_NOT_STARTED) { - sql_print_error("trx->active_trans == 0, but trx->conc_state != " - "TRX_NOT_STARTED"); + sql_print_error("trx->active_flag & TRX_ACTIVE_IN_MYSQL == 0, but" + " trx->conc_state != TRX_NOT_STARTED"); } if (all @@ -9833,7 +9888,7 @@ innobase_xa_prepare( /* We were instructed to prepare the whole transaction, or this is an SQL statement end and autocommit is on */ - ut_ad(trx->active_trans); + ut_ad(trx->active_flag & TRX_ACTIVE_IN_MYSQL); error = (int) trx_prepare_for_mysql(trx); } else { @@ -9857,32 +9912,6 @@ innobase_xa_prepare( srv_active_wake_master_thread(); - if (thd_sql_command(thd) != SQLCOM_XA_PREPARE && - (all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) - { - - /* For ibbackup to work the order of transactions in binlog - and InnoDB must be the same. Consider the situation - - thread1> prepare; write to binlog; ... - - thread2> prepare; write to binlog; commit - thread1> ... commit - - To ensure this will not happen we're taking the mutex on - prepare, and releasing it on commit. - - Note: only do it for normal commits, done via ha_commit_trans. - If 2pc protocol is executed by external transaction - coordinator, it will be just a regular MySQL client - executing XA PREPARE and XA COMMIT commands. - In this case we cannot know how many minutes or hours - will be between XA PREPARE and XA COMMIT, and we don't want - to block for undefined period of time. */ - pthread_mutex_lock(&prepare_commit_mutex); - trx->active_trans = 2; - } - return(error); } diff --git a/storage/innodb_plugin/handler/ha_innodb.h b/storage/innodb_plugin/handler/ha_innodb.h index 7a8f29853de..1dcd7ce203f 100644 --- a/storage/innodb_plugin/handler/ha_innodb.h +++ b/storage/innodb_plugin/handler/ha_innodb.h @@ -238,16 +238,6 @@ LEX_STRING *thd_query_string(MYSQL_THD thd); char **thd_query(MYSQL_THD thd); #endif -/** Get the file name of the MySQL binlog. - * @return the name of the binlog file - */ -const char* mysql_bin_log_file_name(void); - -/** Get the current position of the MySQL binlog. - * @return byte offset from the beginning of the binlog - */ -ulonglong mysql_bin_log_file_pos(void); - /** Check if a user thread is a replication slave thread @param thd user thread @@ -288,6 +278,11 @@ bool thd_binlog_filter_ok(const MYSQL_THD thd); #endif /* MYSQL_VERSION_ID > 50140 */ } +/** Get the file name and position of the MySQL binlog corresponding to the + * current commit. + */ +extern void mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file); + typedef struct trx_struct trx_t; /********************************************************************//** @file handler/ha_innodb.h diff --git a/storage/innodb_plugin/include/trx0trx.h b/storage/innodb_plugin/include/trx0trx.h index abd175d365b..5ecebdda945 100644 --- a/storage/innodb_plugin/include/trx0trx.h +++ b/storage/innodb_plugin/include/trx0trx.h @@ -510,9 +510,10 @@ struct trx_struct{ in that case we must flush the log in trx_commit_complete_for_mysql() */ ulint duplicates; /*!< TRX_DUP_IGNORE | TRX_DUP_REPLACE */ - ulint active_trans; /*!< 1 - if a transaction in MySQL - is active. 2 - if prepare_commit_mutex - was taken */ + ulint active_flag; /*!< TRX_ACTIVE_IN_MYSQL - set if a + transaction in MySQL is active. + TRX_ACTIVE_COMMIT_ORDERED - set if + innobase_commit_ordered has run */ ulint has_search_latch; /* TRUE if this trx has latched the search system latch in S-mode */ @@ -797,6 +798,10 @@ Multiple flags can be combined with bitwise OR. */ #define TRX_SIG_OTHER_SESS 1 /* sent by another session (which must hold rights to this) */ +/* Flag bits for trx_struct.active_flag */ +#define TRX_ACTIVE_IN_MYSQL (1<<0) +#define TRX_ACTIVE_COMMIT_ORDERED (1<<1) + /** Commit node states */ enum commit_node_state { COMMIT_NODE_SEND = 1, /*!< about to send a commit signal to diff --git a/storage/innodb_plugin/trx/trx0trx.c b/storage/innodb_plugin/trx/trx0trx.c index ee744fd58b1..adece596e1b 100644 --- a/storage/innodb_plugin/trx/trx0trx.c +++ b/storage/innodb_plugin/trx/trx0trx.c @@ -119,7 +119,7 @@ trx_create( trx->table_id = ut_dulint_zero; trx->mysql_thd = NULL; - trx->active_trans = 0; + trx->active_flag = 0; trx->duplicates = 0; trx->n_mysql_tables_in_use = 0; From 86008e0ca2b864f1ab7a30e496d7c67c8fce06c2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Apr 2011 17:21:22 +0200 Subject: [PATCH 35/35] Fix merge error. --- sql/log.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index cf5237756ab..22f6b5a75a4 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6936,11 +6936,7 @@ maria_declare_plugin(binlog) NULL, /* Plugin Deinit */ 0x0100 /* 1.0 */, binlog_status_vars_top, /* status variables */ -#ifndef DBUG_OFF binlog_sys_vars, /* system variables */ -#else - NULL, /* system variables */ -#endif "1.0", /* string version */ MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ }