MDEV-36143 Row event replication with Aria does not honour BLOCK_COMMIT

This commit fixes a bug where Aria tables are used in
(master->slave1->slave2) and a backup is taken on slave2. In this case
it is possible that the replication position in the backup, stored in
mysql.gtid_slave_pos, will be wrong. This will lead to replication
errors if one is trying to use the backup as a new slave.

Analyze:
Replicated row events are committed with trans_commit_stmt() and
thd->transaction->all.ha_list != 0.
This means that backup_commit_lock is not taken for Aria tables,
which means the rows are committed and binary logged on the slave
under BLOCK_COMMIT which should not happen.

This issue does not occur on the master as thd->transaction->all.ha_list
is == 0 under AUTO_COMMIT, which sets 'is_real_trans' and 'rw_trans'
which in turn causes backup_commit_lock to be taken.

Fixed by checking in ha_check_and_coalesce_trx_read_only() if all handlers
supports rollback and if not, then wait for BLOCK_COMMIT also for
statement commit.
This commit is contained in:
Monty 2025-05-08 15:08:02 +03:00
commit 22024da64e
5 changed files with 50 additions and 8 deletions

View file

@ -266,6 +266,7 @@ col1
SET AUTOCOMMIT = 0;
UPDATE t_permanent_innodb SET col1 = 9;
UPDATE t_permanent_aria SET col1 = 9;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
UPDATE t_permanent_myisam SET col1 = 9;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
UPDATE t_permanent_aria2 SET col1 = 9;

View file

@ -328,6 +328,7 @@ select * from t_permanent_aria2;
SET AUTOCOMMIT = 0;
UPDATE t_permanent_innodb SET col1 = 9;
--error ER_LOCK_WAIT_TIMEOUT
UPDATE t_permanent_aria SET col1 = 9;
--error ER_LOCK_WAIT_TIMEOUT
UPDATE t_permanent_myisam SET col1 = 9;

View file

@ -1586,6 +1586,12 @@ uint ha_count_rw_2pc(THD *thd, bool all)
/**
Check if we can skip the two-phase commit.
@param thd Thread handler
@param ha_list List of all engines participating on the commit
@param all True if this is final commit (not statement commit)
@param no_rollback Set to 1 if one of the engines doing writes does
not support rollback
A helper function to evaluate if two-phase commit is mandatory.
As a side effect, propagates the read-only/read-write flags
of the statement transaction to its enclosing normal transaction.
@ -1604,16 +1610,21 @@ uint ha_count_rw_2pc(THD *thd, bool all)
uint
ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
bool all)
bool all, bool *no_rollback)
{
/* The number of storage engines that have actual changes. */
unsigned rw_ha_count= 0;
Ha_trx_info *ha_info;
*no_rollback= false;
for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
{
if (ha_info->is_trx_read_write())
{
++rw_ha_count;
if (ha_info->is_trx_no_rollback())
*no_rollback= true;
}
if (! all)
{
@ -1636,7 +1647,18 @@ ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
information up, and the need for two-phase commit has been
already established. Break the loop prematurely.
*/
break;
if (*no_rollback == 0)
{
while ((ha_info= ha_info->next()))
{
if (ha_info->is_trx_read_write() && ha_info->is_trx_no_rollback())
{
*no_rollback= 1;
break;
}
}
break;
}
}
}
return rw_ha_count;
@ -1771,7 +1793,9 @@ int ha_commit_trans(THD *thd, bool all)
if (is_real_trans) /* not a statement commit */
thd->stmt_map.close_transient_cursors();
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
bool no_rollback;
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all,
&no_rollback);
/* rw_trans is TRUE when we in a transaction changing data */
bool rw_trans= is_real_trans && rw_ha_count > 0;
MDL_request mdl_backup;
@ -1784,7 +1808,7 @@ int ha_commit_trans(THD *thd, bool all)
calling ha_commit_trans() from spader_commit().
*/
if (rw_trans && !thd->backup_commit_lock)
if ((rw_trans || no_rollback) && !thd->backup_commit_lock)
{
/*
Acquire a metadata lock which will ensure that COMMIT is blocked
@ -2114,7 +2138,9 @@ int ha_commit_one_phase(THD *thd, bool all)
static bool is_ro_1pc_trans(THD *thd, Ha_trx_info *ha_info, bool all,
bool is_real_trans)
{
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
bool no_rollback;
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all,
&no_rollback);
bool rw_trans= is_real_trans &&
(rw_ha_count > (thd->is_current_stmt_binlog_disabled()?0U:1U));
@ -5145,6 +5171,9 @@ void handler::mark_trx_read_write_internal()
*/
if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)
ha_info->set_trx_read_write();
/* Mark if we are using a table that cannot do rollback */
if (ht->flags & HTON_NO_ROLLBACK)
ha_info->set_trx_no_rollback();
}
}

View file

@ -2014,6 +2014,16 @@ public:
DBUG_ASSERT(is_started());
return m_flags & (int) TRX_READ_WRITE;
}
void set_trx_no_rollback()
{
DBUG_ASSERT(is_started());
m_flags|= (int) TRX_NO_ROLLBACK;
}
bool is_trx_no_rollback() const
{
DBUG_ASSERT(is_started());
return m_flags & (int) TRX_NO_ROLLBACK;
}
bool is_started() const { return m_ht != NULL; }
/** Mark this transaction read-write if the argument is read-write. */
void coalesce_trx_with(const Ha_trx_info *stmt_trx)
@ -2038,7 +2048,7 @@ public:
return m_ht;
}
private:
enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 };
enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1, TRX_NO_ROLLBACK= 2 };
/** Auxiliary, used for ha_list management */
Ha_trx_info *m_next;
/**
@ -5531,7 +5541,7 @@ uint ha_count_rw_all(THD *thd, Ha_trx_info **ptr_ha_info);
bool non_existing_table_error(int error);
uint ha_count_rw_2pc(THD *thd, bool all);
uint ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
bool all);
bool all, bool *no_rollback);
int get_select_field_pos(Alter_info *alter_info, int select_field_count,
bool versioned);

View file

@ -1446,7 +1446,8 @@ bool wsrep_check_mode_after_open_table (THD *thd,
}
// Check are we inside a transaction
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, thd->transaction->all.ha_list, true);
bool not_used;
uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, thd->transaction->all.ha_list, true, &not_used);
bool changes= wsrep_has_changes(thd);
// Roll back current stmt if exists