diff --git a/mysql-test/suite/rpl/r/rpl_sequential_table_id.result b/mysql-test/suite/rpl/r/rpl_sequential_table_id.result new file mode 100644 index 0000000000000..6927a85af9455 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_sequential_table_id.result @@ -0,0 +1,103 @@ +include/master-slave.inc +[connection master] +connection master; +SET @saved_sequential= @@session.binlog_sequential_table_ids; +SET @@session.binlog_sequential_table_ids= ON; +# +# Test 1: Single table INSERT — table_id should be 1 +# +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 'hello'); +Table_map event: table_id: 1 (test.t1) +connection slave; +connection slave; +SELECT * FROM t1; +a b +1 hello +# +# Test 2: Multi-table transaction (separate statements) — each resets to 1 +# +connection master; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=InnoDB; +BEGIN; +INSERT INTO t1 VALUES (2, 'world'); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); +COMMIT; +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 hello +2 world +SELECT * FROM t2; +a +1 +SELECT * FROM t3; +a +1 +# +# Test 3: Multi-table single statement (UPDATE t1, t2) — IDs 1, 2 +# +connection master; +INSERT INTO t2 VALUES (10); +UPDATE t1, t2 SET t1.b= 'updated', t2.a= 20 +WHERE t1.a= 1 AND t2.a= 10; +connection slave; +connection slave; +SELECT * FROM t1 WHERE a= 1; +a b +1 updated +SELECT * FROM t2 WHERE a= 20; +a +20 +# +# Test 4: Cross-database multi-table statement +# +connection master; +CREATE DATABASE IF NOT EXISTS db2; +CREATE TABLE db2.t4 (x INT PRIMARY KEY, y VARCHAR(50)) ENGINE=InnoDB; +INSERT INTO db2.t4 VALUES (1, 'original'); +UPDATE t1, db2.t4 SET t1.b= 'cross-db', db2.t4.y= 'cross-db' + WHERE t1.a= 1 AND db2.t4.x= 1; +connection slave; +connection slave; +SELECT * FROM t1 WHERE a= 1; +a b +1 cross-db +SELECT * FROM db2.t4 WHERE x= 1; +x y +1 cross-db +# +# Test 5: Multiple statements in transaction, verify slave correctness +# +connection master; +BEGIN; +INSERT INTO t1 VALUES (3, 'foo'); +UPDATE t2 SET a= 2 WHERE a= 20; +DELETE FROM t3 WHERE a= 1; +COMMIT; +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 cross-db +2 world +3 foo +SELECT * FROM t2; +a +1 +2 +SELECT * FROM t3; +a +# +# Cleanup +# +connection master; +SET @@session.binlog_sequential_table_ids= @saved_sequential; +DROP TABLE t1, t2, t3; +DROP TABLE db2.t4; +DROP DATABASE db2; +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_sequential_table_id_compat.result b/mysql-test/suite/rpl/r/rpl_sequential_table_id_compat.result new file mode 100644 index 0000000000000..d1e3ccdbd6dbf --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_sequential_table_id_compat.result @@ -0,0 +1,52 @@ +include/master-slave.inc +[connection master] +connection master; +SET @@session.binlog_sequential_table_ids= ON; +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=InnoDB; +# +# Insert data with sequential table IDs +# +INSERT INTO t1 VALUES (1, 'compat test'); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); +# +# Multi-table transaction +# +BEGIN; +INSERT INTO t1 VALUES (2, 'txn row'); +UPDATE t2 SET a= 2 WHERE a= 1; +INSERT INTO t3 VALUES (2); +COMMIT; +# +# Multi-table single statement +# +INSERT INTO t2 VALUES (10); +UPDATE t1, t2 SET t1.b= 'multi', t2.a= 20 +WHERE t1.a= 1 AND t2.a= 10; +# +# Verify slave replicated everything correctly +# +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 multi +2 txn row +SELECT * FROM t2 ORDER BY a; +a +2 +20 +SELECT * FROM t3 ORDER BY a; +a +1 +2 +# +# Cleanup +# +connection master; +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2, t3; +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_sequential_table_id_edge.result b/mysql-test/suite/rpl/r/rpl_sequential_table_id_edge.result new file mode 100644 index 0000000000000..645ab73ca5a9a --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_sequential_table_id_edge.result @@ -0,0 +1,117 @@ +include/master-slave.inc +[connection master] +connection master; +SET @@session.binlog_sequential_table_ids= ON; +# +# Test 1: Same table in multiple statements within one transaction +# +CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; +BEGIN; +INSERT INTO t1 VALUES (1, 10); +INSERT INTO t1 VALUES (2, 20); +UPDATE t1 SET b= 30 WHERE a= 1; +DELETE FROM t1 WHERE a= 2; +COMMIT; +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 30 +# +# Test 2: Transaction touching many tables +# +connection master; +BEGIN; +COMMIT; +connection slave; +connection slave; +SELECT * FROM many_1; +a +1 +SELECT * FROM many_25; +a +25 +SELECT * FROM many_50; +a +50 +# +# Test 3: Temporary tables mixed with regular tables +# +connection master; +CREATE TEMPORARY TABLE tmp1 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE reg1 (a INT PRIMARY KEY) ENGINE=InnoDB; +BEGIN; +INSERT INTO tmp1 VALUES (1); +INSERT INTO reg1 VALUES (1); +INSERT INTO t1 VALUES (3, 300); +COMMIT; +connection slave; +connection slave; +SELECT * FROM reg1 ORDER BY a; +a +1 +SELECT * FROM t1 ORDER BY a; +a b +1 30 +3 300 +# +# Test 4: DELETE with multi-table syntax +# +connection master; +CREATE TABLE t2 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (100, 1), (200, 2); +INSERT INTO t1 VALUES (100, 1); +DELETE t1, t2 FROM t1 INNER JOIN t2 ON t1.a= t2.a; +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 30 +3 300 +SELECT * FROM t2 ORDER BY a; +a b +200 2 +# +# Test 5: REPLACE and INSERT ... ON DUPLICATE KEY UPDATE +# +connection master; +INSERT INTO t1 VALUES (5, 50); +REPLACE INTO t1 VALUES (5, 55); +INSERT INTO t1 VALUES (6, 60) ON DUPLICATE KEY UPDATE b= 66; +INSERT INTO t1 VALUES (6, 60) ON DUPLICATE KEY UPDATE b= 66; +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 30 +3 300 +5 55 +6 66 +# +# Test 6: Multiple single-statement transactions (autocommit) +# +connection master; +SET autocommit= 1; +INSERT INTO t1 VALUES (7, 70); +INSERT INTO t1 VALUES (8, 80); +INSERT INTO t1 VALUES (9, 90); +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 30 +3 300 +5 55 +6 66 +7 70 +8 80 +9 90 +# +# Cleanup +# +connection master; +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2, reg1; +DROP TEMPORARY TABLE IF EXISTS tmp1; +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_sequential_table_id_toggle.result b/mysql-test/suite/rpl/r/rpl_sequential_table_id_toggle.result new file mode 100644 index 0000000000000..9e5800e0388b9 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_sequential_table_id_toggle.result @@ -0,0 +1,54 @@ +include/master-slave.inc +[connection master] +connection master; +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +# +# Insert with sequential IDs ON +# +SET @@session.binlog_sequential_table_ids= ON; +INSERT INTO t1 VALUES (1, 'sequential'); +INSERT INTO t2 VALUES (1); +# +# Insert with sequential IDs OFF (traditional) +# +SET @@session.binlog_sequential_table_ids= OFF; +INSERT INTO t1 VALUES (2, 'traditional'); +INSERT INTO t2 VALUES (2); +# +# Insert with sequential IDs ON again +# +SET @@session.binlog_sequential_table_ids= ON; +INSERT INTO t1 VALUES (3, 'sequential again'); +# +# Multi-table update with toggle mid-transaction +# +BEGIN; +INSERT INTO t1 VALUES (4, 'in-txn-seq'); +SET @@session.binlog_sequential_table_ids= OFF; +INSERT INTO t2 VALUES (4); +COMMIT; +# +# Verify slave receives all data correctly +# +connection slave; +connection slave; +SELECT * FROM t1 ORDER BY a; +a b +1 sequential +2 traditional +3 sequential again +4 in-txn-seq +SELECT * FROM t2 ORDER BY a; +a +1 +2 +4 +# +# Cleanup +# +connection master; +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2; +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_alter_online_debug.test b/mysql-test/suite/rpl/t/rpl_alter_online_debug.test index b257ce29ef314..940fde45b4e04 100644 --- a/mysql-test/suite/rpl/t/rpl_alter_online_debug.test +++ b/mysql-test/suite/rpl/t/rpl_alter_online_debug.test @@ -79,11 +79,13 @@ set debug_sync= 'now signal go'; --connection master --reap +SHOW BINLOG EVENTS; --source include/save_master_gtid.inc --connection slave source include/start_slave.inc; --source include/sync_with_master_gtid.inc +SHOW BINLOG EVENTS; select * from t; # Cleanup diff --git a/mysql-test/suite/rpl/t/rpl_sequential_table_id.test b/mysql-test/suite/rpl/t/rpl_sequential_table_id.test new file mode 100644 index 0000000000000..abc3140d46d1f --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_sequential_table_id.test @@ -0,0 +1,121 @@ +# Test sequential table IDs in row-based binlog events (binlog_sequential_table_ids) +# +# Verify that when binlog_sequential_table_ids=ON, the master assigns +# 1-based sequential table IDs in Table_map events, and that the slave +# replicates data correctly using array-based table lookup. + +--source include/have_innodb.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection master + +SET @saved_sequential= @@session.binlog_sequential_table_ids; +SET @@session.binlog_sequential_table_ids= ON; + +--echo # +--echo # Test 1: Single table INSERT — table_id should be 1 +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; + +--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1) + +INSERT INTO t1 VALUES (1, 'hello'); + +--let $event_info= query_get_value(SHOW BINLOG EVENTS IN '$binlog_file' FROM $binlog_start, Info, 3) +--echo Table_map event: $event_info + +--sync_slave_with_master +--connection slave +SELECT * FROM t1; + +--echo # +--echo # Test 2: Multi-table transaction (separate statements) — each resets to 1 +--echo # +--connection master + +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=InnoDB; + +--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1) + +BEGIN; +INSERT INTO t1 VALUES (2, 'world'); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); +COMMIT; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2; +SELECT * FROM t3; + +--echo # +--echo # Test 3: Multi-table single statement (UPDATE t1, t2) — IDs 1, 2 +--echo # +--connection master + +INSERT INTO t2 VALUES (10); + +--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1) + +UPDATE t1, t2 SET t1.b= 'updated', t2.a= 20 + WHERE t1.a= 1 AND t2.a= 10; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 WHERE a= 1; +SELECT * FROM t2 WHERE a= 20; + +--echo # +--echo # Test 4: Cross-database multi-table statement +--echo # +--connection master + +CREATE DATABASE IF NOT EXISTS db2; +CREATE TABLE db2.t4 (x INT PRIMARY KEY, y VARCHAR(50)) ENGINE=InnoDB; +INSERT INTO db2.t4 VALUES (1, 'original'); + +--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1) + +UPDATE t1, db2.t4 SET t1.b= 'cross-db', db2.t4.y= 'cross-db' + WHERE t1.a= 1 AND db2.t4.x= 1; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 WHERE a= 1; +SELECT * FROM db2.t4 WHERE x= 1; + +--echo # +--echo # Test 5: Multiple statements in transaction, verify slave correctness +--echo # +--connection master + +BEGIN; +INSERT INTO t1 VALUES (3, 'foo'); +UPDATE t2 SET a= 2 WHERE a= 20; +DELETE FROM t3 WHERE a= 1; +COMMIT; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2; +SELECT * FROM t3; + +--echo # +--echo # Cleanup +--echo # +--connection master +SET @@session.binlog_sequential_table_ids= @saved_sequential; +DROP TABLE t1, t2, t3; +DROP TABLE db2.t4; +DROP DATABASE db2; + +--sync_slave_with_master +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_sequential_table_id_compat.test b/mysql-test/suite/rpl/t/rpl_sequential_table_id_compat.test new file mode 100644 index 0000000000000..8a8c8a82bf3bc --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_sequential_table_id_compat.test @@ -0,0 +1,60 @@ +# Test backward compatibility: sequential table IDs work via hash fallback +# +# When the master uses sequential table IDs but the slave doesn't have +# the array optimization (simulated by the hash-based path), the slave +# should still replicate correctly because sequential IDs (1, 2, 3, ...) +# are valid hash keys. + +--source include/have_innodb.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection master + +SET @@session.binlog_sequential_table_ids= ON; + +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=InnoDB; + +--echo # +--echo # Insert data with sequential table IDs +--echo # +INSERT INTO t1 VALUES (1, 'compat test'); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); + +--echo # +--echo # Multi-table transaction +--echo # +BEGIN; +INSERT INTO t1 VALUES (2, 'txn row'); +UPDATE t2 SET a= 2 WHERE a= 1; +INSERT INTO t3 VALUES (2); +COMMIT; + +--echo # +--echo # Multi-table single statement +--echo # +INSERT INTO t2 VALUES (10); +UPDATE t1, t2 SET t1.b= 'multi', t2.a= 20 + WHERE t1.a= 1 AND t2.a= 10; + +--echo # +--echo # Verify slave replicated everything correctly +--echo # +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; + +--echo # +--echo # Cleanup +--echo # +--connection master +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2, t3; + +--sync_slave_with_master +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_sequential_table_id_edge.test b/mysql-test/suite/rpl/t/rpl_sequential_table_id_edge.test new file mode 100644 index 0000000000000..11bce97e4c126 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_sequential_table_id_edge.test @@ -0,0 +1,136 @@ +# Edge-case tests for binlog_sequential_table_ids +# +# Tests various edge cases: many tables, same table in multiple +# statements, temporary tables, and MIXED binlog format. + +--source include/have_innodb.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection master + +SET @@session.binlog_sequential_table_ids= ON; + +--echo # +--echo # Test 1: Same table in multiple statements within one transaction +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; + +BEGIN; +INSERT INTO t1 VALUES (1, 10); +INSERT INTO t1 VALUES (2, 20); +UPDATE t1 SET b= 30 WHERE a= 1; +DELETE FROM t1 WHERE a= 2; +COMMIT; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; + +--echo # +--echo # Test 2: Transaction touching many tables +--echo # +--connection master +--disable_query_log +let $i= 1; +while ($i <= 50) +{ + eval CREATE TABLE many_$i (a INT PRIMARY KEY) ENGINE=InnoDB; + inc $i; +} +--enable_query_log + +BEGIN; +--disable_query_log +let $i= 1; +while ($i <= 50) +{ + eval INSERT INTO many_$i VALUES ($i); + inc $i; +} +--enable_query_log +COMMIT; + +--sync_slave_with_master +--connection slave +SELECT * FROM many_1; +SELECT * FROM many_25; +SELECT * FROM many_50; + +--echo # +--echo # Test 3: Temporary tables mixed with regular tables +--echo # +--connection master +CREATE TEMPORARY TABLE tmp1 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE reg1 (a INT PRIMARY KEY) ENGINE=InnoDB; + +BEGIN; +INSERT INTO tmp1 VALUES (1); +INSERT INTO reg1 VALUES (1); +INSERT INTO t1 VALUES (3, 300); +COMMIT; + +--sync_slave_with_master +--connection slave +SELECT * FROM reg1 ORDER BY a; +SELECT * FROM t1 ORDER BY a; + +--echo # +--echo # Test 4: DELETE with multi-table syntax +--echo # +--connection master +CREATE TABLE t2 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (100, 1), (200, 2); +INSERT INTO t1 VALUES (100, 1); + +DELETE t1, t2 FROM t1 INNER JOIN t2 ON t1.a= t2.a; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo # +--echo # Test 5: REPLACE and INSERT ... ON DUPLICATE KEY UPDATE +--echo # +--connection master +INSERT INTO t1 VALUES (5, 50); +REPLACE INTO t1 VALUES (5, 55); +INSERT INTO t1 VALUES (6, 60) ON DUPLICATE KEY UPDATE b= 66; +INSERT INTO t1 VALUES (6, 60) ON DUPLICATE KEY UPDATE b= 66; + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; + +--echo # +--echo # Test 6: Multiple single-statement transactions (autocommit) +--echo # +--connection master +SET autocommit= 1; +INSERT INTO t1 VALUES (7, 70); +INSERT INTO t1 VALUES (8, 80); +INSERT INTO t1 VALUES (9, 90); + +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; + +--echo # +--echo # Cleanup +--echo # +--connection master +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2, reg1; +DROP TEMPORARY TABLE IF EXISTS tmp1; +--disable_query_log +let $i= 1; +while ($i <= 50) +{ + eval DROP TABLE many_$i; + inc $i; +} +--enable_query_log + +--sync_slave_with_master +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_sequential_table_id_toggle.test b/mysql-test/suite/rpl/t/rpl_sequential_table_id_toggle.test new file mode 100644 index 0000000000000..4a0ba1ca87e5f --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_sequential_table_id_toggle.test @@ -0,0 +1,60 @@ +# Test toggling binlog_sequential_table_ids ON/OFF mid-session +# +# Verify that sequential and old-style table IDs coexist in the same +# binlog and the slave handles the mixed binlog correctly. + +--source include/have_innodb.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection master + +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(100)) ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; + +--echo # +--echo # Insert with sequential IDs ON +--echo # +SET @@session.binlog_sequential_table_ids= ON; +INSERT INTO t1 VALUES (1, 'sequential'); +INSERT INTO t2 VALUES (1); + +--echo # +--echo # Insert with sequential IDs OFF (traditional) +--echo # +SET @@session.binlog_sequential_table_ids= OFF; +INSERT INTO t1 VALUES (2, 'traditional'); +INSERT INTO t2 VALUES (2); + +--echo # +--echo # Insert with sequential IDs ON again +--echo # +SET @@session.binlog_sequential_table_ids= ON; +INSERT INTO t1 VALUES (3, 'sequential again'); + +--echo # +--echo # Multi-table update with toggle mid-transaction +--echo # +BEGIN; +INSERT INTO t1 VALUES (4, 'in-txn-seq'); +SET @@session.binlog_sequential_table_ids= OFF; +INSERT INTO t2 VALUES (4); +COMMIT; + +--echo # +--echo # Verify slave receives all data correctly +--echo # +--sync_slave_with_master +--connection slave +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo # +--echo # Cleanup +--echo # +--connection master +SET @@session.binlog_sequential_table_ids= OFF; +DROP TABLE t1, t2; + +--sync_slave_with_master +--source include/rpl_end.inc diff --git a/sql/log.cc b/sql/log.cc index 8e2e37a83fcd0..2df378ff58d71 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -7917,6 +7917,18 @@ bool THD::binlog_write_table_maps() variables.option_bits & OPTION_GTID_BEGIN)) DBUG_RETURN(1); + /* + When binlog_sequential_table_ids is ON, assign 1-based sequential IDs + to each table's Table_map event within this statement group. This + replaces the default TABLE_SHARE::table_map_id (which can be large + and sparse) with compact IDs (1, 2, 3, ...) enabling O(1) array + lookup on the slave instead of hash lookup. The counter resets to + 1 at each call to binlog_write_table_maps(), i.e. per statement. + */ + bool use_sequential= variables.binlog_sequential_table_ids; + if (use_sequential) + binlog_sequential_table_id_counter= 1; + for (MYSQL_LOCK **cur_lock= locks ; cur_lock < locks_end ; cur_lock++) { TABLE **const end_ptr= (*cur_lock)->table + (*cur_lock)->table_count; @@ -7927,6 +7939,16 @@ bool THD::binlog_write_table_maps() TABLE *table= *table_ptr; if (table->current_lock != F_WRLCK || ! table->file->row_logging) continue; + /* + Store the chosen ID in binlog_sequential_id so that + write_table_map() and prepare_pending_rows_event() can + use it uniformly without needing to branch on the mode. + */ + if (use_sequential) + table->binlog_sequential_id= + (ulonglong) binlog_sequential_table_id_counter++; + else + table->binlog_sequential_id= table->s->table_map_id; if (mysql_bin_log.write_table_map(this, table)) DBUG_RETURN(1); if (table->restore_row_logging) @@ -7966,18 +7988,22 @@ bool MYSQL_BIN_LOG::write_table_map(THD *thd, TABLE *table) DBUG_ENTER("THD::binlog_write_table_map"); DBUG_PRINT("enter", ("table: %p (%s: #%llu)", table, table->s->table_name.str, - table->s->table_map_id)); + table->binlog_sequential_id)); - /* Pre-conditions */ - DBUG_ASSERT((table->s->table_map_id & MAX_TABLE_MAP_ID) != UINT32_MAX && - (table->s->table_map_id & MAX_TABLE_MAP_ID) != 0); + /* + Pre-condition: binlog_sequential_id was set by + binlog_write_table_maps() — either to a 1-based sequential + counter or to the original table_map_id. + */ + DBUG_ASSERT(table->binlog_sequential_id != UINT32_MAX); /* Ensure that all events in a GTID group are in the same cache */ if (thd->variables.option_bits & OPTION_GTID_BEGIN) is_transactional= 1; + /* Use binlog_sequential_id as the table_id in the Table_map event */ Table_map_log_event - the_event(thd, table, table->s->table_map_id, is_transactional); + the_event(thd, table, table->binlog_sequential_id, is_transactional); binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr(); binlog_cache_data *cache_data= (cache_mngr-> @@ -8251,8 +8277,12 @@ Event_log::prepare_pending_rows_event(THD *thd, TABLE* table, Rows_event_factory event_factory) { DBUG_ENTER("MYSQL_BIN_LOG::prepare_pending_rows_event"); - /* Pre-conditions */ - DBUG_ASSERT(table->s->table_map_id != ~0UL); + /* + The table_id used in Rows events must match the one written in + the preceding Table_map event (binlog_sequential_id), so that + the slave can correlate them. + */ + DBUG_ASSERT(table->binlog_sequential_id != UINT32_MAX); /* There is no good place to set up the transactional data, so we @@ -8275,14 +8305,14 @@ Event_log::prepare_pending_rows_event(THD *thd, TABLE* table, */ if (!pending || pending->server_id != serv_id || - pending->get_table_id() != table->s->table_map_id || + pending->get_table_id() != table->binlog_sequential_id || pending->get_general_type_code() != event_factory.type_code || pending->get_data_size() + needed > opt_binlog_rows_event_max_size || pending->read_write_bitmaps_cmp(table) == FALSE) { /* Create a new RowsEventT... */ Rows_log_event* const - ev= event_factory.create(thd, table, table->s->table_map_id, + ev= event_factory.create(thd, table, table->binlog_sequential_id, is_transactional); if (unlikely(!ev)) DBUG_RETURN(NULL); @@ -8379,6 +8409,14 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, binlog_cache_data *cache_data, Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone, cache_type, LOG_EVENT_SUPPRESS_USE_F, is_transactional, commit_id, has_xid, is_ro_1pc); + + /* + Signal to the slave that this GTID group uses sequential table IDs, + so the slave can use O(1) array-based table lookup instead of hash. + */ + if (thd->variables.binlog_sequential_table_ids) + gtid_event.flags_extra|= Gtid_log_event::FL_EXTRA_SEQUENTIAL_TABLE_IDS; + /* Check that any binlogging during DDL recovery preserves the FL_DLL flag on the GTID event. diff --git a/sql/log_event.h b/sql/log_event.h index 629ffc23476eb..09ef3bafeec7b 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -3521,6 +3521,8 @@ class Gtid_log_event: public Log_event static constexpr uchar FL_COMMIT_ALTER_E1= 4; static constexpr uchar FL_ROLLBACK_ALTER_E1= 8; static constexpr uchar FL_EXTRA_THREAD_ID= 16; // thread_id like in BEGIN Query + /* Table_map events in this group use compact 1-based sequential IDs */ + static constexpr uchar FL_EXTRA_SEQUENTIAL_TABLE_IDS= 32; #ifdef MYSQL_SERVER static constexpr uint32_t max_size= diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index ae97285b1797b..f28ec66d9824f 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -3294,6 +3294,12 @@ Gtid_log_event::do_apply_event(rpl_group_info *rgi) rgi->gtid_ev_flags_extra= flags_extra; rgi->gtid_ev_sa_seq_no= sa_seq_no; + /* + If the master wrote sequential table IDs for this GTID group, + enable array-based O(1) table lookup for the upcoming Rows events. + */ + rgi->m_use_sequential_table_ids= + (flags_extra & FL_EXTRA_SEQUENTIAL_TABLE_IDS) != 0; thd->reset_for_next_command(); if (opt_gtid_strict_mode && opt_bin_log && opt_log_slave_updates) @@ -5276,6 +5282,41 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) if (ptr->parent_l) continue; rgi->m_table_map.set_table(ptr->table_id, ptr->table); + /* + When sequential table IDs are enabled, also populate a flat + array for O(1) lookup in Rows_log_event::do_apply_event(). + The hash map is still populated for fallback and other code paths. + */ + if (rgi->m_use_sequential_table_ids && + ptr->table_id > 0) + { + /* Convert 1-based table_id to 0-based array index */ + uint idx= (uint)(ptr->table_id - 1); + if (idx >= rgi->m_table_array_size) + { + uint new_size= idx + 1; + /* + MY_ALLOW_ZERO_PTR is required here because m_table_array + starts as NULL. Without it, my_realloc() dereferences the + NULL pointer via USER_TO_HEADER(old_point) before checking, + causing a crash on the first replication event that uses + sequential table IDs. + MY_ZEROFILL ensures any new slots beyond the old size are + initialized to NULL. + */ + TABLE **new_arr= (TABLE**) my_realloc( + PSI_INSTRUMENT_ME, rgi->m_table_array, + new_size * sizeof(TABLE*), + MYF(MY_WME | MY_ALLOW_ZERO_PTR | MY_ZEROFILL)); + if (new_arr) + { + rgi->m_table_array= new_arr; + rgi->m_table_array_size= new_size; + } + } + if (idx < rgi->m_table_array_size) + rgi->m_table_array[idx]= ptr->table; + } /* Following is passing flag about triggers on the server. The problem was to pass it between table map event and row event. I do it via extended @@ -5298,7 +5339,22 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock); } - table= m_table= rgi->m_table_map.get_table(m_table_id); + /* + When sequential table IDs are active, use O(1) direct array + indexing instead of hash lookup. Fall back to the hash map for + non-sequential events or if the ID is out of the array's range + (defensive — should not happen with well-formed binlog). + */ + if (rgi->m_use_sequential_table_ids && + m_table_id > 0 && + m_table_id <= rgi->m_table_array_size) + { + table= m_table= rgi->m_table_array[m_table_id - 1]; + } + else + { + table= m_table= rgi->m_table_map.get_table(m_table_id); + } DBUG_PRINT("debug", ("m_table:%p, m_table_id: %llu%s", m_table, m_table_id, diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index d151ee3ab3a26..41a592d7ddefc 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -2079,6 +2079,7 @@ rpl_group_info::reinit(Relay_log_info *rli) this->rli= rli; tables_to_lock= NULL; tables_to_lock_count= 0; + m_use_sequential_table_ids= false; trans_retries= 0; last_event_start_time= 0; gtid_sub_id= 0; @@ -2108,7 +2109,9 @@ rpl_group_info::rpl_group_info(Relay_log_info *rli) gtid_ev_flags2(0), gtid_ev_flags_extra(0), gtid_ev_sa_seq_no(0), reserved_start_alter_thread(0), finish_event_group_called(0), rpt(NULL), start_alter_ev(NULL), direct_commit_alter(false), sa_info(NULL), - is_new_trans(false), assembler(NULL) + is_new_trans(false), assembler(NULL), + m_table_array(NULL), m_table_array_size(0), + m_use_sequential_table_ids(false) { reinit(rli); bzero(¤t_gtid, sizeof(current_gtid)); @@ -2123,6 +2126,7 @@ rpl_group_info::~rpl_group_info() free_annotate_event(); delete deferred_events; + my_free(m_table_array); mysql_mutex_destroy(&sleep_lock); mysql_cond_destroy(&sleep_cond); } @@ -2335,6 +2339,12 @@ void rpl_group_info::clear_tables_to_lock() my_free(to_free); } DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0); + /* + Reset so the next GTID group re-evaluates from its own + Gtid_log_event flags. The m_table_array itself is kept + allocated for reuse to avoid repeated malloc/free. + */ + m_use_sequential_table_ids= false; DBUG_VOID_RETURN; } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index e0e3d107c157d..f922844829b32 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -962,6 +962,14 @@ struct rpl_group_info logic remains the same. */ Rows_log_event_assembler *assembler; + /* + Flat array for O(1) table lookup when sequential table IDs are in use. + m_table_array[table_id - 1] maps the 1-based sequential ID to a TABLE*. + Dynamically resized via my_realloc as needed; freed in destructor. + */ + TABLE **m_table_array; + uint m_table_array_size; + bool m_use_sequential_table_ids; rpl_group_info(Relay_log_info *rli_); ~rpl_group_info(); diff --git a/sql/sql_class.h b/sql/sql_class.h index e8829839617f7..12f6c2d284591 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -965,6 +965,7 @@ typedef struct system_variables vers_asof_timestamp_t vers_asof_timestamp; my_bool binlog_alter_two_phase; + my_bool binlog_sequential_table_ids; Charset_collation_map_st character_set_collations; Sql_path path; @@ -3687,6 +3688,8 @@ class THD: public THD_count, /* this must be first */ /* 1 if binlog table maps has been written */ bool binlog_table_maps; + /* Per-statement counter for sequential table IDs (starts at 1) */ + uint binlog_sequential_table_id_counter; void issue_unsafe_warnings(); void reset_unsafe_warnings() diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 53b9c7b864ed7..83031780e371b 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2649,6 +2649,14 @@ static Sys_var_mybool Sys_binlog_alter_two_phase( SESSION_VAR(binlog_alter_two_phase), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); +static Sys_var_mybool Sys_binlog_sequential_table_ids( + "binlog_sequential_table_ids", + "When set, use compact 1-based sequential table IDs in row-based " + "binlog events instead of sparse global table_map_id values. " + "This enables O(1) array-based table lookup on the slave", + SESSION_VAR(binlog_sequential_table_ids), CMD_LINE(OPT_ARG), + DEFAULT(FALSE)); + static bool check_gtid_ignore_duplicates(sys_var *self, THD *thd, set_var *var) { diff --git a/sql/table.h b/sql/table.h index 065146e3e6fd1..46bb8c4288233 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1640,6 +1640,8 @@ struct TABLE #endif /* Temporary value used by binlog_write_table_maps(). Does not need init */ bool restore_row_logging; + /* Table ID used in Table_map events: sequential or table_map_id */ + ulonglong binlog_sequential_id; bool stats_is_read; /* Persistent statistics is read for the table */ bool histograms_are_read; #ifdef WITH_PARTITION_STORAGE_ENGINE