MySQL主从复制通过Binlog进行数据传输,主库写入数据,生成Binlog,通过dump线程将Binlog发送给从库,从库IO线程接收Binlog并写入自己的relay log,SQL线程应用relay log里的事务。本文结合MySQL源码,分析SQL线程的主要处理过程。
MySQL源码版本:5.7.19
1. SQL线程启动
MySQL从库在执行start slave命令时,在MySQL内部依次调用下面的函数来启动IO线程和SQL线程。
- mysql_execute_command(),sql/sql_parse.cc
- start_slave_cmd(),sql/rpl_slave.cc
- start_slave(),sql/rpl_slave.cc
- start_slave_threads(),sql/rpl_slave.cc
- start_slave_thread(),sql/rpl_slave.cc
其中SQL线程在 start_slave_thread() 函数中被创建。
start_slave_threads 函数在另外一个地方也会被调用,init_slave()函数,这个函数是在MySQL启动时调用,如果没有指定skip-slave-start,主从复制随着MySQL启动而自动启动。
2. SQL线程函数
MySQL主从复制SQL线程的线程函数位于sql/rpl_slave.cc文件中,定义如下:
extern "C" void *handle_slave_sql(void *arg)
(1)创建工作线程
在MySQL 5.7 版本,开启逻辑并行复制,SQL线程会创建多个工作线程并发进行relay log日志的应用,源码中创建工作线程的调用栈如下:
- handle_slave_sql()
- slave_start_workers()
- slave_start_single_worker()
- mysql_thread_create()
(2)进入循环
SQL线程创建完工作线程之后,会进入while循环,直到停止复制或者SQL线程被kill。
在循环中的主要函数调用关系如下:
- handle_slave_sql(),SQL线程主函数
- exec_relay_log_event()
- apply_event_and_update_pos()
- ev->apply_event(rli);
- ev->do_apply_event()
exec_relay_log_event 函数读取relay log中的event。
apply_event_and_update_pos函数去应用relay log event。在这个函数里,会将thd的server_id设置成event的server_id,保证event被应用后,生成自己的binlog时,server_id仍然是原始值。
apply_event_and_update_pos函数中会调用函数sql_delay_event(),用于处理延迟复制,比如使用了CHANGE MASTER TO MASTER_DELAY = X 这样的语法。
ev->apply_event()函数是event自己成员函数,这个函数里面会去判断event是否可以并行应用,如果不可以,就在当前线程(SQL线程)去处理这个event,如果可以并行应用,则会返回上一层函数apply_event_and_update_pos,将event丢进入一个队列,后续worker线程会去这个队列中取出event处理。
3. 工作线程主要逻辑
工作线程的线程函数为:
extern "C" void *handle_slave_worker(void *arg)
主要调用关系如下:
- handle_slave_worker(),工作线程主函数
- slave_worker_exec_job_group()
- slave_worker_exec_event()
- ev->do_apply_event_worker(this);
- ev->do_apply_event()
- mysql_parse()
工作线程内部主要是一个while循环,调用slave_worker_exec_job_group()函数执行分配给自己的任务。
在slave_worker_exec_job_group函数中,拿到event后,调用下面这个函数应用event:
error= worker->slave_worker_exec_event(ev);
在slave_worker_exec_event函数中又会调用event自己的成员函数来应用event:
ret= ev->do_apply_event_worker(this);
在do_apply_event_worker函数中,调用不同类型event的do_apply_event()成员函数。比如Query_log_event这种类型的event。
Query_log_event::do_apply_event()
do_apply_event函数中最终调用mysql_parse函数执行SQL语句。
4. 总结
本文简单分析了MySQL主从复制SQL线程的主要处理逻辑,MySQL主从复制是逻辑复制,从上面过程来看,SQL线程(包括工作线程)从relay log中拿到event,然后像执行一个原始SQL一样在从库上重新执行一次,相对于物理复制,这种方式效率并不高,耗费资源,并且容易产生复制延迟。