Bug #79867 unnecessary using temporary for update

Submitted: 7 Jan 2016 5:00 Modified: 5 Sep 2016 9:06
Reporter: zhang yingqiang (OCA) Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: Replication Severity:S3 (Non-critical)
Version:5.6.26 5.7.10, 5.6.28 OS:Any
Assigned to: CPU Architecture:Any
Tags: binlog image update temproray table

[7 Jan 2016 5:00] zhang yingqiang
Description:
when the binlog_row_image is FULL, we set read_set and write_set all before update. This leads to setting used_key_is_modified true, which lead to mysql_update use temporary at last.
Actually there is no need to use temporary in most cases.
The root cause is that we set read_set and write_set too early.

How to repeat:
Need to put the follow case in suite binlog.

--source include/have_log_bin.inc
--source include/have_binlog_format_row.inc

show variables like '%binlog_row_image%';

set sql_log_bin=0;
create table t1(id int primary key, a int) engine = innodb;

insert into t1 select 1,1;
insert into t1 select 2,1;
insert into t1 select 3,2;
insert into t1 select 4,2;

set binlog_row_image= FULL;
set sql_log_bin=1;
explain update t1 set a=a+1 where id < 3;
update t1 set a=a+1 where id < 3;

set binlog_row_image= MINIMAL;
explain update t1 set a=a+1 where id < 3;
update t1 set a=a+1 where id < 3;

set sql_log_bin=0;
flush logs;

let $MYSQLD_DATADIR=`select @@datadir`;
--exec $MYSQL_BINLOG --verbose $MYSQLD_DATADIR/master-bin.000001

drop table t1;

Suggested fix:
We should set the read_set and write_set just before write binlog.

diff --git a/sql/binlog.cc b/sql/binlog.cc
index e93fec8..c8c247b 100644
--- a/sql/binlog.cc
+++ b/sql/binlog.cc
@@ -8462,6 +8462,14 @@ int THD::binlog_write_row(TABLE* table, bool is_trans,
 { 
   DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
 
+  MY_BITMAP *old_read_set= table->read_set;
+  MY_BITMAP *old_write_set= table->write_set;
+
+  if (table->in_use->variables.binlog_row_image == BINLOG_ROW_IMAGE_FULL)
+  {
+    table->column_bitmaps_set_no_signal(&table->all_set,
+                                        &table->all_set);
+  }
   /*
     Pack records into format for transfer. We are allocating more
     memory than needed, but that doesn't matter.
@@ -8472,6 +8480,7 @@ int THD::binlog_write_row(TABLE* table, bool is_trans,
 
   uchar *row_data= memory.slot(0);
 
+
   size_t const len= pack_row(table, table->write_set, row_data, record);
 
   Rows_log_event* const ev=
@@ -8479,6 +8488,8 @@ int THD::binlog_write_row(TABLE* table, bool is_trans,
                                       static_cast<Write_rows_log_event*>(0),
                                       extra_row_info);
 
+  table->column_bitmaps_set_no_signal(old_read_set,
+                                      old_write_set);
   if (unlikely(ev == 0))
     return HA_ERR_OUT_OF_MEM;
 
@@ -8623,6 +8634,7 @@ void THD::binlog_prepare_row_images(TABLE *table)
       (thd->variables.binlog_row_image < BINLOG_ROW_IMAGE_FULL) &&
       !ha_check_storage_engine_flag(table->s->db_type(), HTON_NO_BINLOG_ROW_OPT))
   {
+    MY_BITMAP *tmp_write_set= table->write_set;
     /**
       Just to be sure that tmp_set is currently not in use as
       the read_set already.
@@ -8658,7 +8670,13 @@ void THD::binlog_prepare_row_images(TABLE *table)
 
     /* set the temporary read_set */
     table->column_bitmaps_set_no_signal(&table->tmp_set,
-                                        table->write_set);
+                                        tmp_write_set);
+  }
+
+  if (thd->variables.binlog_row_image == BINLOG_ROW_IMAGE_FULL)
+  {
+    table->column_bitmaps_set_no_signal(&table->all_set,
+                                        &table->all_set);
   }
 
   DBUG_PRINT_BITSET("debug", "table->read_set (after preparing): %s", table->read_set);
diff --git a/sql/table.cc b/sql/table.cc
index 7867a71..5bb2fff 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -2315,7 +2315,7 @@ partititon_err:
   /* Allocate bitmaps */
 
   bitmap_size= share->column_bitmap_size;
-  if (!(bitmaps= (uchar*) alloc_root(&outparam->mem_root, bitmap_size*3)))
+  if (!(bitmaps= (uchar*) alloc_root(&outparam->mem_root, bitmap_size*4)))
     goto err;
   bitmap_init(&outparam->def_read_set,
               (my_bitmap_map*) bitmaps, share->fields, FALSE);
@@ -2323,6 +2323,9 @@ partititon_err:
               (my_bitmap_map*) (bitmaps+bitmap_size), share->fields, FALSE);
   bitmap_init(&outparam->tmp_set,
               (my_bitmap_map*) (bitmaps+bitmap_size*2), share->fields, FALSE);
+  bitmap_init(&outparam->all_set,
+              (my_bitmap_map*) (bitmaps+bitmap_size*3), share->fields, FALSE);
+  bitmap_set_all(&outparam->all_set);
   outparam->default_column_bitmaps();
 
   /* The table struct is now initialized;  Open the table */
@@ -5437,11 +5440,13 @@ void TABLE::mark_columns_per_binlog_row_image()
 
     switch (thd->variables.binlog_row_image)
     {
+        /*
       case BINLOG_ROW_IMAGE_FULL:
         if (s->primary_key < MAX_KEY)
           bitmap_set_all(read_set);
         bitmap_set_all(write_set);
         break;
+        */
       case BINLOG_ROW_IMAGE_NOBLOB:
         /* for every field that is not set, mark it unless it is a blob */
         for (Field **ptr=field ; *ptr ; ptr++)
@@ -5464,6 +5469,7 @@ void TABLE::mark_columns_per_binlog_row_image()
             bitmap_set_bit(write_set, field->field_index);
         }
         break;
+      case BINLOG_ROW_IMAGE_FULL:
       case BINLOG_ROW_IMAGE_MINIMAL:
         /* mark the primary key if available in the read_set */
         if (s->primary_key < MAX_KEY)
diff --git a/sql/table.h b/sql/table.h
index fd8d898..df83b70 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -1056,7 +1056,7 @@ public:
   const char   *alias;                   /* alias or table name */
   uchar                *null_flags;
   my_bitmap_map        *bitmap_init_value;
-  MY_BITMAP     def_read_set, def_write_set, tmp_set; /* containers */
+  MY_BITMAP     def_read_set, def_write_set, tmp_set, all_set; /* containers */
   MY_BITMAP     *read_set, *write_set;          /* Active column sets */
   /*
    The ID of the query that opened and is using this table. Has different
[7 Jan 2016 12:15] MySQL Verification Team
Hello zhang yingqiang,

Thank you for the report and contribution.

Thanks,
Umesh
[7 Jan 2016 12:16] MySQL Verification Team
// 5.6.28

worker[1] Using MTR_BUILD_THREAD 300, with reserved ports 13000..13009
binlog.rpl_79867 'mix'                   [ skipped ]  Doesn't support --binlog-format='mixed'
show variables like '%binlog_row_image%';
Variable_name   Value
binlog_row_image        FULL
set sql_log_bin=0;
create table t1(id int primary key, a int) engine = innodb;
insert into t1 select 1,1;
insert into t1 select 2,1;
insert into t1 select 3,2;
insert into t1 select 4,2;
set binlog_row_image= FULL;
set sql_log_bin=1;
explain update t1 set a=a+1 where id < 3;
id      select_type     table   type    possible_keys   key     key_len ref     rows    Extra
1       SIMPLE  t1      range   PRIMARY PRIMARY 4       const   2       Using where; Using temporary
update t1 set a=a+1 where id < 3;
set binlog_row_image= MINIMAL;
explain update t1 set a=a+1 where id < 3;
id      select_type     table   type    possible_keys   key     key_len ref     rows    Extra
1       SIMPLE  t1      range   PRIMARY PRIMARY 4       const   2       Using where
update t1 set a=a+1 where id < 3;
set sql_log_bin=0;
flush 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 4
#160107 15:12:37 server id 1  end_log_pos 120 CRC32 0x99631b4f  Start: binlog v 4, server v 5.6.28-enterprise-commercial-advanced-log created 160107 15:12:37 at startup
ROLLBACK/*!*/;
BINLOG '
NVaOVg8BAAAAdAAAAHgAAAAAAAQANS42LjI4LWVudGVycHJpc2UtY29tbWVyY2lhbC1hZHZhbmNl
ZC1sb2cAAAAAAAAAAAA1Vo5WEzgNAAgAEgAEBAQEEgAAXAAEGggAAAAICAgCAAAACgoKGRkAAU8b
Y5k=
'/*!*/;
# at 120
#160107 15:12:38 server id 1  end_log_pos 192 CRC32 0xa96bc13b  Query   thread_id=2     exec_time=0     error_code=0
SET TIMESTAMP=1452168758/*!*/;
SET @@session.pseudo_thread_id=2/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1073741824/*!*/;
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/*!*/;
BEGIN
/*!*/;
# at 192
#160107 15:12:38 server id 1  end_log_pos 238 CRC32 0x69d472a4  Table_map: `test`.`t1` mapped to number 81
# at 238
#160107 15:12:38 server id 1  end_log_pos 310 CRC32 0xe49cfbc5  Update_rows: table id 81 flags: STMT_END_F

BINLOG '
NlaOVhMBAAAALgAAAO4AAAAAAFEAAAAAAAEABHRlc3QAAnQxAAIDAwACpHLUaQ==
NlaOVh8BAAAASAAAADYBAAAAAFEAAAAAAAEAAgAC///8AQAAAAEAAAD8AQAAAAIAAAD8AgAAAAEA
AAD8AgAAAAIAAADF+5zk
'/*!*/;
### UPDATE `test`.`t1`
### WHERE
###   @1=1
###   @2=1
### SET
###   @1=1
###   @2=2
### UPDATE `test`.`t1`
### WHERE
###   @1=2
###   @2=1
### SET
###   @1=2
###   @2=2
# at 310
#160107 15:12:38 server id 1  end_log_pos 341 CRC32 0x17b600a0  Xid = 37
COMMIT/*!*/;
# at 341
#160107 15:12:38 server id 1  end_log_pos 413 CRC32 0x69d65b92  Query   thread_id=2     exec_time=0     error_code=0
SET TIMESTAMP=1452168758/*!*/;
BEGIN
/*!*/;
# at 413
#160107 15:12:38 server id 1  end_log_pos 459 CRC32 0x8ae82807  Table_map: `test`.`t1` mapped to number 81
# at 459
#160107 15:12:38 server id 1  end_log_pos 515 CRC32 0x3bd7eb3c  Update_rows: table id 81 flags: STMT_END_F

BINLOG '
NlaOVhMBAAAALgAAAMsBAAAAAFEAAAAAAAEABHRlc3QAAnQxAAIDAwACByjoig==
NlaOVh8BAAAAOAAAAAMCAAAAAFEAAAAAAAEAAgACAQL+AQAAAP4DAAAA/gIAAAD+AwAAADzr1zs=
'/*!*/;
### UPDATE `test`.`t1`
### WHERE
###   @1=1
### SET
###   @2=3
### UPDATE `test`.`t1`
### WHERE
###   @1=2
### SET
###   @2=3
# at 515
#160107 15:12:38 server id 1  end_log_pos 546 CRC32 0xbedb603a  Xid = 40
COMMIT/*!*/;
# at 546
#160107 15:12:38 server id 1  end_log_pos 594 CRC32 0x68c6788b  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;
binlog.rpl_79867 'row'                   [ pass ]     18
[26 Jul 2016 7:06] Sujatha Sivakumar
Patch for Bug#79867 fix.

Attachment: Bug79867_mysql-5.6.diff (text/x-patch), 10.50 KiB.

[26 Jul 2016 7:07] Sujatha Sivakumar
Please find the above patch that fixes the issue.
Give it a try at your end and please let us know if you see any issues with it.
[5 Sep 2016 9:06] David Moss
Posted by developer:
 
Thank you for your feedback, this has been fixed in upcoming versions and the following was added to the 5.6.33 and 5.7.15 change logs:
With binlog_row_image=FULL, when updating single tables temporary tables were unnecessarily being used. The fix ensures single table update follows the same pattern as multi-table update. Thanks to Zhang Yingqiang for the contribution that this fix was based on.
[7 Sep 2016 10:07] David Moss
Posted by developer:
 
Sorry, made a mistake and actually the patch was not based on Zhang's contribution. Therefore the changelog entry was modified to:
With binlog_row_image=FULL, when updating single tables temporary tables were unnecessarily being used. The fix ensures single table update follows the same pattern as multi-table update.
[3 Jan 2017 11:47] Erlend Dahl
Bug#80424 EXPLAIN output depends on binlog_format setting

was marked as a duplicate.

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.061 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.