From f9ac7032cbc4b7b9af9c8122e6ebfd91af9fbaf9 Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Sun, 20 Jan 2019 19:00:16 +0200 Subject: [PATCH] MDEV-14605 Changes to "ON UPDATE CURRENT_TIMESTAMP" fields are not always logged properly with binlog_row_image=MINIMAL There are two issues fixed in this commit. The first is an observation of a multi-table UPDATE binlogged in row-format in binlog_row_image=MINIMAL mode. While the UPDATE aims at a table with an ON-UPDATE attribute its binlog after-image misses to record also installed default value. The reason for that turns out missed marking of default-capable fields in TABLE::write_set. This is fixed to mark such fields similarly to 10.2's MDEV-10134 patch (db7edfed17efe6) that introduced it. The marking follows up 93d1e5ce0b841bed's idea to exploit TABLE:rpl_write_set introduced there though, and thus does not mess (in 10.1) with the actual MDEV-10134 agenda. The patch makes formerly arg-less TABLE::mark_default_fields_for_write() to accept an argument which would be TABLE:rpl_write_set. The 2nd issue is extra columns in in binlog_row_image=MINIMAL before-image while merely a packed primary key is enough. The test main.mysqlbinlog_row_minimal always had a wrong result recorded. This is fixed to invoke a function that intended for read_set possible filtering and which is called (supposed to) in all type of MDL, UPDATE including; the test results have gotten corrected. At *merging* from 10.1->10.2 the 1st "main" part of the patch is unnecessary since the bug is not observed in 10.2, so only hunks from sql/sql_class.cc are required. --- mysql-test/r/mysqlbinlog_row_minimal.result | 108 +++++++++++++++----- mysql-test/t/mysqlbinlog_row_minimal.test | 41 ++++++++ sql/sql_class.cc | 23 ++++- sql/table.cc | 6 +- sql/table.h | 6 +- 5 files changed, 155 insertions(+), 29 deletions(-) diff --git a/mysql-test/r/mysqlbinlog_row_minimal.result b/mysql-test/r/mysqlbinlog_row_minimal.result index 34bafd6301a..2e34b2885ca 100644 --- a/mysql-test/r/mysqlbinlog_row_minimal.result +++ b/mysql-test/r/mysqlbinlog_row_minimal.result @@ -214,39 +214,36 @@ BEGIN # at 1997 # server id 1 end_log_pos 2049 Table_map: `test`.`t2` mapped to number 32 # at 2049 -# server id 1 end_log_pos 2119 Update_rows: table id 32 flags: STMT_END_F +# server id 1 end_log_pos 2111 Update_rows: table id 32 flags: STMT_END_F ### UPDATE `test`.`t2` ### WHERE ### @1=10 /* INT meta=0 nullable=0 is_null=0 */ -### @5=4 /* INT meta=0 nullable=1 is_null=0 */ ### SET ### @5=5 /* INT meta=0 nullable=1 is_null=0 */ ### UPDATE `test`.`t2` ### WHERE ### @1=11 /* INT meta=0 nullable=0 is_null=0 */ -### @5=4 /* INT meta=0 nullable=1 is_null=0 */ ### SET ### @5=5 /* INT meta=0 nullable=1 is_null=0 */ ### UPDATE `test`.`t2` ### WHERE ### @1=12 /* INT meta=0 nullable=0 is_null=0 */ -### @5=NULL /* INT meta=0 nullable=1 is_null=1 */ ### SET ### @5=5 /* INT meta=0 nullable=1 is_null=0 */ -# at 2119 -# server id 1 end_log_pos 2188 Query thread_id=4 exec_time=x error_code=0 +# at 2111 +# server id 1 end_log_pos 2180 Query thread_id=4 exec_time=x error_code=0 SET TIMESTAMP=X/*!*/; COMMIT /*!*/; -# at 2188 -# server id 1 end_log_pos 2226 GTID 0-1-9 +# at 2180 +# server id 1 end_log_pos 2218 GTID 0-1-9 /*!100001 SET @@session.gtid_seq_no=9*//*!*/; BEGIN /*!*/; -# at 2226 -# server id 1 end_log_pos 2278 Table_map: `test`.`t1` mapped to number 31 -# at 2278 -# server id 1 end_log_pos 2328 Delete_rows: table id 31 flags: STMT_END_F +# at 2218 +# server id 1 end_log_pos 2270 Table_map: `test`.`t1` mapped to number 31 +# at 2270 +# server id 1 end_log_pos 2320 Delete_rows: table id 31 flags: STMT_END_F ### DELETE FROM `test`.`t1` ### WHERE ### @1=10 /* INT meta=0 nullable=0 is_null=0 */ @@ -259,20 +256,20 @@ BEGIN ### DELETE FROM `test`.`t1` ### WHERE ### @1=13 /* INT meta=0 nullable=0 is_null=0 */ -# at 2328 -# server id 1 end_log_pos 2397 Query thread_id=4 exec_time=x error_code=0 +# at 2320 +# server id 1 end_log_pos 2389 Query thread_id=4 exec_time=x error_code=0 SET TIMESTAMP=X/*!*/; COMMIT /*!*/; -# at 2397 -# server id 1 end_log_pos 2435 GTID 0-1-10 +# at 2389 +# server id 1 end_log_pos 2427 GTID 0-1-10 /*!100001 SET @@session.gtid_seq_no=10*//*!*/; BEGIN /*!*/; -# at 2435 -# server id 1 end_log_pos 2487 Table_map: `test`.`t2` mapped to number 32 -# at 2487 -# server id 1 end_log_pos 2537 Delete_rows: table id 32 flags: STMT_END_F +# at 2427 +# server id 1 end_log_pos 2479 Table_map: `test`.`t2` mapped to number 32 +# at 2479 +# server id 1 end_log_pos 2529 Delete_rows: table id 32 flags: STMT_END_F ### DELETE FROM `test`.`t2` ### WHERE ### @1=10 /* INT meta=0 nullable=0 is_null=0 */ @@ -285,13 +282,76 @@ BEGIN ### DELETE FROM `test`.`t2` ### WHERE ### @1=13 /* INT meta=0 nullable=0 is_null=0 */ -# at 2537 -# server id 1 end_log_pos 2606 Query thread_id=4 exec_time=x error_code=0 +# at 2529 +# server id 1 end_log_pos 2598 Query thread_id=4 exec_time=x error_code=0 SET TIMESTAMP=X/*!*/; COMMIT /*!*/; -# at 2606 -# server id 1 end_log_pos 2650 Rotate to master-bin.000002 pos: 4 +# at 2598 +# server id 1 end_log_pos 2642 Rotate to master-bin.000002 pos: 4 +DELIMITER ; +# End of log file +ROLLBACK /* added by mysqlbinlog */; +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +DROP TABLE t1,t2; +CREATE TABLE `t1` ( +`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, +`is_deleted` BIT(1) DEFAULT b'0', +`last_updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`ref_id` BIGINT(20) UNSIGNED NOT NULL, +PRIMARY KEY (`id`), +KEY `last_updated_KEY` (`last_updated`) +); +CREATE TABLE `t2` ( +`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, +`short_desc` VARCHAR(50) NOT NULL, +PRIMARY KEY (`id`) +); +INSERT INTO t2 (id, short_desc) VALUES (1, 'test'); +INSERT INTO t1 (id, is_deleted, ref_id) VALUES (1, b'0', 1); +FLUSH BINARY LOGS; +UPDATE t1 t1 INNER JOIN t2 t2 ON t1.ref_id = t2.id +SET t1.is_deleted = TRUE +WHERE t1.id = 1; +FLUSH BINARY LOGS; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_insert_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at 368 +# server id 1 end_log_pos 406 GTID 0-1-16 +/*!100101 SET @@session.skip_parallel_replication=0*//*!*/; +/*!100001 SET @@session.gtid_domain_id=0*//*!*/; +/*!100001 SET @@session.server_id=1*//*!*/; +/*!100001 SET @@session.gtid_seq_no=16*//*!*/; +BEGIN +/*!*/; +# at 406 +# server id 1 end_log_pos 453 Table_map: `test`.`t1` mapped to number 34 +# at 453 +# server id 1 end_log_pos 498 Update_rows: table id 34 flags: STMT_END_F +### UPDATE `test`.`t1` +### WHERE +### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */ +### SET +### @2=b'1' /* BIT(1) meta=1 nullable=1 is_null=0 */ +### @3=X /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */ +# at 498 +# server id 1 end_log_pos 576 Query thread_id=4 exec_time=x error_code=0 +SET TIMESTAMP=X/*!*/; +SET @@session.pseudo_thread_id=4/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=1342177280/*!*/; +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/*!*/; +COMMIT +/*!*/; +# at 576 +# server id 1 end_log_pos 620 Rotate to master-bin.000004 pos: 4 DELIMITER ; # End of log file ROLLBACK /* added by mysqlbinlog */; diff --git a/mysql-test/t/mysqlbinlog_row_minimal.test b/mysql-test/t/mysqlbinlog_row_minimal.test index 216cb43eb6f..39cae408d4e 100644 --- a/mysql-test/t/mysqlbinlog_row_minimal.test +++ b/mysql-test/t/mysqlbinlog_row_minimal.test @@ -26,8 +26,49 @@ DELETE FROM t2; --let $datadir = `SELECT @@datadir` FLUSH BINARY LOGS; + --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR --replace_regex /\d{6} *\d*:\d\d:\d\d// /Start:.*at startup/Start: xxx/ /SET TIMESTAMP=\d*/SET TIMESTAMP=X/ /exec_time=\d*/exec_time=x/ --exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog DROP TABLE t1,t2; + +# +# MDEV-14605 ON UPDATE CURRENT_TIMESTAMP fields by multi-table UPDATE are not logged with binlog_row_image=MINIMAL +# + +CREATE TABLE `t1` ( + + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `is_deleted` BIT(1) DEFAULT b'0', + `last_updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `ref_id` BIGINT(20) UNSIGNED NOT NULL, + PRIMARY KEY (`id`), + KEY `last_updated_KEY` (`last_updated`) +); + +CREATE TABLE `t2` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `short_desc` VARCHAR(50) NOT NULL, + PRIMARY KEY (`id`) +); + + +INSERT INTO t2 (id, short_desc) VALUES (1, 'test'); +INSERT INTO t1 (id, is_deleted, ref_id) VALUES (1, b'0', 1); +FLUSH BINARY LOGS; +--let $binlog_pos= query_get_value(SHOW MASTER STATUS, Position, 1) + +UPDATE t1 t1 INNER JOIN t2 t2 ON t1.ref_id = t2.id + SET t1.is_deleted = TRUE + WHERE t1.id = 1; + +--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1) + +FLUSH BINARY LOGS; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--replace_regex /\d{6} *\d*:\d\d:\d\d// /SET TIMESTAMP=\d*/SET TIMESTAMP=X/ /exec_time=\d*/exec_time=x/ /@3=\d*/@3=X/ /CRC32 0x[0-9a-f]+/CRC32 XXX/ +--exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog --start-position=$binlog_pos + +DROP TABLE t1,t2; + diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 8424b4477c3..0971d4fdaaa 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -6390,6 +6390,22 @@ int THD::binlog_update_row(TABLE* table, bool is_trans, DBUG_ASSERT(is_current_stmt_binlog_format_row() && ((WSREP(this) && wsrep_emulate_bin_log) || mysql_bin_log.is_open())); + /** + Save a reference to the original read bitmaps + We will need this to restore the bitmaps at the end as + binlog_prepare_row_images() may change table->read_set. + table->read_set is used by pack_row and deep in + binlog_prepare_pending_events(). + */ + MY_BITMAP *old_read_set= table->read_set; + + /** + This will remove spurious fields required during execution but + not needed for binlogging. This is done according to the: + binlog-row-image option. + */ + binlog_prepare_row_images(table); + size_t const before_maxlen = max_row_length(table, before_record); size_t const after_maxlen = max_row_length(table, after_record); @@ -6401,9 +6417,9 @@ int THD::binlog_update_row(TABLE* table, bool is_trans, uchar *after_row= row_data.slot(1); size_t const before_size= pack_row(table, table->read_set, before_row, - before_record); + before_record); size_t const after_size= pack_row(table, table->rpl_write_set, after_row, - after_record); + after_record); /* Ensure that all events in a GTID group are in the same cache */ if (variables.option_bits & OPTION_GTID_BEGIN) @@ -6431,6 +6447,9 @@ int THD::binlog_update_row(TABLE* table, bool is_trans, int error= ev->add_row_data(before_row, before_size) || ev->add_row_data(after_row, after_size); + /* restore read set for the rest of execution */ + table->column_bitmaps_set_no_signal(old_read_set, + table->write_set); return error; } diff --git a/sql/table.cc b/sql/table.cc index ca06eee077c..31f0d255847 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -6137,6 +6137,8 @@ void TABLE::mark_columns_per_binlog_row_image() mark_columns_used_by_index_no_reset(s->primary_key, read_set); /* Only write columns that have changed */ rpl_write_set= write_set; + if (default_field) + mark_default_fields_for_write(rpl_write_set); break; default: @@ -6283,7 +6285,7 @@ bool TABLE::has_default_function(bool is_update) Add all fields that have a default function to the table write set. */ -void TABLE::mark_default_fields_for_write() +void TABLE::mark_default_fields_for_write(MY_BITMAP* bset) { Field **dfield_ptr, *dfield; enum_sql_command cmd= in_use->lex->sql_command; @@ -6294,7 +6296,7 @@ void TABLE::mark_default_fields_for_write() dfield->has_insert_default_function()) || ((sql_command_flags[cmd] & CF_UPDATES_DATA) && dfield->has_update_default_function())) - bitmap_set_bit(write_set, dfield->field_index); + bitmap_set_bit(bset, dfield->field_index); } } diff --git a/sql/table.h b/sql/table.h index 1a24d786d82..de10a966d1b 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1353,7 +1353,11 @@ public: void mark_columns_per_binlog_row_image(void); bool mark_virtual_col(Field *field); void mark_virtual_columns_for_write(bool insert_fl); - void mark_default_fields_for_write(); + void mark_default_fields_for_write(MY_BITMAP* bset); + inline void mark_default_fields_for_write() + { + mark_default_fields_for_write(write_set); + } bool has_default_function(bool is_update); inline void column_bitmaps_set(MY_BITMAP *read_set_arg, MY_BITMAP *write_set_arg)