Bug #121035 Extra Table_map events are included in the binary log
Submitted: 30 Jul 8:50 Modified: 30 Jul 8:54
Reporter: Alex Zimnitski Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Replication Severity:S5 (Performance)
Version:8.0.46+, 8.4.10+ OS:Linux
Assigned to: CPU Architecture:x86

[30 Jul 8:50] Alex Zimnitski
Description:
When triggers are used, the binary log includes Table_map events for all tables, even those that are not actually modified.
Splitting the trigger into two separate ones does not help eliminate the extra Table_map events from the binary log.

In practice, this results in a significant overhead of up to 17% in binary log size.
The replica also has to acquire and release unnecessary MDL locks, which slows down replication.

binlog_format = ROW
binlog_row_image = FULL
binlog_row_metadata = MINIMAL

How to repeat:
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;

UPDATE binlog_test bt SET
	bt.name_1 = 'aaa'
WHERE bt.id = 1
LIMIT 1;

UPDATE binlog_test bt SET
	bt.name_1 = 'bbb'
WHERE bt.id = 2
LIMIT 1;

SELECT * FROM binlog_test;
#1	1,2	aaa	a	a
#2	0,12	bbb	b	b

SELECT * FROM binlog_test_val_change_log;
#1	1	2026-07-27 01:42:03	0,5	1
#2	1	2026-07-27 01:42:03	0,7	1
#3	2	2026-07-27 01:42:24	0,12	1

SELECT * FROM binlog_test_name_change_log;
#1	2	b	b	b
#2	1	a	a	a

SELECT @@global.binlog_format, @@global.binlog_row_image, @@global.binlog_row_metadata;
#ROW	FULL	MINIMAL

SHOW BINARY LOGS;
#binlog.000001	10687	No
SHOW BINLOG EVENTS IN 'binlog.000001';
/*
binlog.000001	7454	Anonymous_Gtid	1	7533	SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
binlog.000001	7533	Query	1	7638	BEGIN
binlog.000001	7638	Table_map	1	7732	table_id: 103 (db_name.binlog_test)
binlog.000001	7732	Table_map	1	7841	table_id: 104 (db_name.binlog_test_name_change_log)
binlog.000001	7841	Table_map	1	7940	table_id: 105 (db_name.binlog_test_val_change_log)
binlog.000001	7940	Update_rows	1	8014	table_id: 103
binlog.000001	8014	Write_rows	1	8072	table_id: 105 flags: STMT_END_F
binlog.000001	8072	Xid	1	8103	COMMIT /* xid=53 */
*/

Suggested fix:
sql/binlog.cc
int THD::binlog_write_table_map(TABLE *table, bool is_transactional, bool binlog_rows_query) 

eleminate unused mapping
[30 Jul 8:54] Alex Zimnitski
sql/handler.cc
static int write_locked_table_maps(THD *thd)