Bug #121527 redundant Table_map events in the binlog when triggers are present
Submitted: 16 Oct 7:03
Reporter: Aaditya Dubey Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Utilities: Binlog Events Severity:S3 (Non-critical)
Version:8.4, 8.0 OS:Any
Assigned to: CPU Architecture:Any

[16 Oct 7:03] Aaditya Dubey
Description:
If a table has a trigger, Table_map events are written for all tables referenced in the trigger, even when those tables are not actually involved in the transaction.

Splitting the trigger into multiple triggers (one table per trigger) does not change this behavior.

Impact : 
Average event size: 618 bytes

Overhead per event: 109 bytes (~17%)

~95% of transactions affected

Resulting overhead:

Binlog size: >15%

Master network traffic: ~15%

Replica network traffic: ~15% (40+ replicas)

Additional parsing overhead on replicas

How to repeat:
1. Enable the binlog if it is not enabled.

2. SET GLOBAL general_log = 'ON';

3. Table Creation

DROP TABLE IF EXISTS binlog_test;
CREATE TABLE IF NOT EXISTS binlog_test (
	id int UNSIGNED AUTO_INCREMENT PRIMARY KEY,
	val double,
	name_1 varchar(50),
	name_2 varchar(50),
	name_3 varchar(50)
);
DROP TABLE IF EXISTS binlog_test_val_change_log;
CREATE TABLE IF NOT EXISTS binlog_test_val_change_log (
	id_ai int UNSIGNED AUTO_INCREMENT PRIMARY KEY,
	id int UNSIGNED,
	dt datetime,
	dx double,
	is_trigger tinyint UNSIGNED
);
DROP TABLE IF EXISTS binlog_test_name_change_log;
CREATE TABLE IF NOT EXISTS binlog_test_name_change_log (
	id_ai int UNSIGNED AUTO_INCREMENT PRIMARY KEY,
	id int UNSIGNED,
	name_1 varchar(50),
	name_2 varchar(50),
	name_3 varchar(50)
);
DELIMITER $$
CREATE TRIGGER binlog_test AFTER UPDATE ON binlog_test
FOR EACH ROW
BEGIN
	IF old.val != new.val THEN
		INSERT INTO binlog_test_val_change_log ( id, dt, dx, is_trigger )
			VALUES (old.id, NOW(), (new.val - old.val), 1);
	END IF;
	IF	(COALESCE(old.name_1, '') != COALESCE(new.name_1, '')	OR COALESCE(old.name_2, '') != COALESCE(new.name_2, '') OR COALESCE(old.name_2, '') != COALESCE(new.name_2, '') ) THEN
		INSERT IGNORE INTO binlog_test_name_change_log (id, name_1, name_2, name_3)
			VALUES (old.id, old.name_1, old.name_2, old.name_3);
	END IF;
END
$$
DELIMITER ;
INSERT INTO binlog_test (val, name_1, name_2, name_3)
	VALUES (0.0, 'a', 'a', 'a');
INSERT INTO binlog_test (val, name_1, name_2, name_3)
	VALUES (0.0, 'b', 'b', 'b');
SELECT * FROM binlog_test;
UPDATE binlog_test bt SET
	bt.val = bt.val + 0.5
WHERE bt.id = 1
LIMIT 1;
UPDATE binlog_test bt SET
	bt.val = bt.val + 0.7
WHERE bt.id = 1
LIMIT 1;
UPDATE binlog_test bt SET
	bt.val = bt.val + 0.12
WHERE bt.id = 2
LIMIT 1;

select * from binlog_test_val_change_log;

From General Log : 

/home/[REDACTED]/opt/mysql/8.4.7/bin/mysqld, Version: 8.4.7-7 ([REDACTED] Server (GPL), Release 7, Revision 9a19f1fd), Time: 2026-07-03T10:27:35.240029Z. started with:
Tcp port: 8409  Unix socket: /tmp/mysql_sandbox8409.sock
Time                 Id Command    Argument
2026-07-03T10:27:48.399067Z        44 Query     UPDATE binlog_test bt SET
bt.val = bt.val + 0.7
WHERE bt.id = 1
LIMIT 1
2026-07-03T10:27:48.399380Z        44 Query     INSERT INTO binlog_test_val_change_log ( id, dt, dx, is_trigger )
VALUES (old.id, NOW(), (new.val - old.val), 1)
2026-07-03T10:27:53.677505Z        44 Query     select * from binlog_test_val_change_log
2026-07-03T10:27:57.768474Z        44 Query     select * from binlog_test_name_change_log
select * from binlog_test_name_change_log;

From Binary Log File : 

BEGIN
/*!*/;
# at 3954
#260703 10:27:48 server id 1  end_log_pos 4027 CRC32 0xc3a866c4     Table_map: `test`.`binlog_test` mapped to number 104
# has_generated_invisible_primary_key=0
# at 4027
#260703 10:27:48 server id 1  end_log_pos 4115 CRC32 0x58710394     Table_map: `test`.`binlog_test_name_change_log` mapped to number 105
# has_generated_invisible_primary_key=0
# at 4115
#260703 10:27:48 server id 1  end_log_pos 4193 CRC32 0x94ec3630     Table_map: `test`.`binlog_test_val_change_log` mapped to number 106
# has_generated_invisible_primary_key=0
# at 4193
#260703 10:27:48 server id 1  end_log_pos 4267 CRC32 0x7611a665     Update_rows: table id 104
# at 4267
#260703 10:27:48 server id 1  end_log_pos 4325 CRC32 0xf8488805     Write_rows: table id 106 flags: STMT_END_F
### UPDATE `test`.`binlog_test`
### WHERE
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2=0.5 /* DOUBLE meta=8 nullable=1 is_null=0 */
###   @3='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
###   @4='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
###   @5='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### SET
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2=1.1999999999999999556 /* DOUBLE meta=8 nullable=1 is_null=0 */
###   @3='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
###   @4='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
###   @5='a' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### INSERT INTO `test`.`binlog_test_val_change_log`
### SET
###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3='2026-07-03 10:27:48' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
###   @4=0.69999999999999995559 /* DOUBLE meta=8 nullable=1 is_null=0 */
###   @5=1 /* TINYINT meta=0 nullable=1 is_null=0 */
# at 4325
#260703 10:27:48 server id 1  end_log_pos 4356 CRC32 0xd57cef3a     Xid = 112
COMMIT/*!*/;

As shown above, even though there is no INSERT operation for the binlog_test_name_change_log table, a Table_map event for this table is still written to the binary log, as shown below:

#260703 10:34:07 server id 1  end_log_pos 4680 CRC32 0x51475007     Table_map: `test`.`binlog_test_name_change_log` mapped to number 105

The same behavior can be observed in the opposite scenario. Although records are inserted only into the binlog_test_name_change_log table and no records are inserted into the binlog_test_val_change_log table, a Table_map event is still written for the binlog_test_val_change_log table. 

However, this behavior is expected and is by design. The purpose of this feature request is to introduce a new option or configuration parameter, or any improvement, that would suppress Table_map events for tables referenced by a trigger but not actually modified during trigger execution, thereby reducing unnecessary binary log entries.

More Info : https://github.com/xxxx/xxxx-server/blob/8.0/sql/binlog.cc#L9961 
i.e. Before writing row changes for this table, write a table-map event so replicas know what table_id means.