简介
首先说明的是这并不是多线程复制中的问题,而是一个现象,这里来解释说明这个现象。
开启多线程回放的同学如果经常在slave示例上执行show processlist
命令的话,应该对这两个信息不陌生。
出处
开启多线程回放后,回放控制线程会根据既定的规则,进行并发回放。因此,后续事务如果不可以跟正在回放的事务并发的话,就必须要进行等待。如果开启了slave_preserve_commit_order
,进行并发回放的多个事务之间,也要按照和主库上提交的顺序一样,进行提交。以上所述这也是这两个信息出现的原因。其中,Waiting for dependent transaction to commit
,是当前事务无法和正在回放的事务并发回放出现的等待;Waiting for preceding transaction to commit
,是当前并发回放的事务在进入commit时的flush队列前,必须等到先前事务已经进入flush队列而引起的等待。
代码位置
bool Mts_submode_logical_clock::
wait_for_last_committed_trx(Relay_log_info* rli,
longlong last_committed_arg,
longlong lwm_estimate_arg)
{
if ((!rli->info_thd->killed && !is_error) &&
!clock_leq(last_committed_arg, get_lwm_timestamp(rli, true)))
{
PSI_stage_info old_stage;
struct timespec ts[2];
set_timespec_nsec(&ts[0], 0);
DBUG_ASSERT(rli->gaq->len >= 2); // there's someone to wait
thd->ENTER_COND(&rli->logical_clock_cond, &rli->mts_gaq_LOCK,
&stage_worker_waiting_for_commit_parent, &old_stage); //这里就是看到的Waiting for dependent transaction to commit
do
{
mysql_cond_wait(&rli->logical_clock_cond, &rli->mts_gaq_LOCK);
}
while ((!rli->info_thd->killed && !is_error) &&
!clock_leq(last_committed_arg, estimate_lwm_timestamp()));
my_atomic_store64(&min_waited_timestamp, SEQ_UNINIT); // reset waiting flag
mysql_mutex_unlock(&rli->mts_gaq_LOCK);
thd->EXIT_COND(&old_stage);
set_timespec_nsec(&ts[1], 0);
my_atomic_add64(&rli->mts_total_wait_overlap, diff_timespec(&ts[1], &ts[0]));
}
}
而Waiting for preceding transaction to commit
在如下函数中
/**
Waits until it becomes the queue head.
@retval false All previous threads succeeded so this thread can go
ahead and commit.
*/
bool Commit_order_manager::wait_for_its_turn(Slave_worker *worker,
bool all)
{
DBUG_ENTER("Commit_order_manager::wait_for_its_turn");
/*
When prior transaction fail, current trx should stop and wait for signal
to rollback itself
*/
if ((all || ending_single_stmt_trans(worker->info_thd, all) || m_rollback_trx) &&
m_workers[worker->id].status == OCS_WAIT)
{
PSI_stage_info old_stage;
mysql_cond_t *cond= &m_workers[worker->id].cond;
THD *thd= worker->info_thd;
DBUG_PRINT("info", ("Worker %lu is waiting for commit signal", worker->id));
mysql_mutex_lock(&m_mutex);
thd->ENTER_COND(cond, &m_mutex,
&stage_worker_waiting_for_its_turn_to_commit,
&old_stage); //这里就是看到的Waiting for preceding transaction to commit
while (queue_front() != worker->id)
{
if (unlikely(worker->found_order_commit_deadlock()))
{
mysql_mutex_unlock(&m_mutex);
thd->EXIT_COND(&old_stage);
DBUG_RETURN(true);
}
mysql_cond_wait(cond, &m_mutex);
}
mysql_mutex_unlock(&m_mutex);
thd->EXIT_COND(&old_stage);
m_workers[worker->id].status= OCS_SIGNAL;
if (m_rollback_trx)
{
unregister_trx(worker);
DBUG_PRINT("info", ("thd has seen an error signal from old thread"));
thd->get_stmt_da()->set_overwrite_status(true);
my_error(ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR, MYF(0));
}
}
DBUG_RETURN(m_rollback_trx);
}
图解
把这两种等待以图的方式展现出来,就是如下的样子
假设有三个事务如下,t1/t2可以并发回放,t3则必须等待t2回放完成之后才可以进行回放,其等待关系如下