Author: 沽月
MySQL 主备通过 binlog 实现数据同步的功能,主库将生成的 binlog 通过 binlog send 线程发送到备库,备库通过应用这些 binlog 来更新数据,实现主备数据一致,其应用 binlog 的读取操作与更新操作的堆栈分别如下。
读取操作:
#0 row_search_for_mysql
#1 0x0000000000c200c2 in ha_innobase::index_read
#2 0x0000000000c21c57 in ha_innobase::rnd_pos
#3 0x000000000090c5d3 in handler::rnd_pos_by_record
#4 0x0000000000a574c3 in Rows_log_event::find_row
#5 0x0000000000a589da in Delete_rows_log_event::do_exec_row
#6 0x0000000000a50dcc in Rows_log_event::do_apply_event
#7 0x00000000005d0bb8 in Log_event::apply_event
#8 0x00000000005b9782 in apply_event_and_update_pos
...
更新操作:
#0 row_update_for_mysql
#1 0x0000000000c1f466 in ha_innobase::delete_row
#2 0x000000000090b64a in handler::ha_delete_row
#3 0x0000000000a58a4b in Delete_rows_log_event::do_exec_row
#4 0x0000000000a50dcc in Rows_log_event::do_apply_event
#5 0x00000000005d0bb8 in Log_event::apply_event
#6 0x00000000005b9782 in apply_event_and_update_pos
...
综上,我们需要一种可以在 DML 操作之前将数据从磁盘加载到内存的功能,以实现数据库的快速操作。
我们需要找到一种将数据加载到内存的方法,但又不对数据进行修改,需要满足以下的条件:
因此,我们可以在mysqld启动时启动额外的线程对 relay log 进行特殊处理,以达到数据加载的目的。
RDS MySQL 利用 relay log 来解决上述两个问题,当系统启动后,可以在后台开启一个独立于SQL thread之外的线程将 relay log 相关的数据从磁盘加载到内存中,从而使备库在查找数据的时候直接利用buffer pool,而不需要从磁盘中进行加载,同理,使用这种方法也可以解决系统预热的问题。
当启动后,如果发现延迟且 buffer pool 命中率较低时,可以启用 relay fetch thread, 具体语法为:
启动 relay_fetch_thread: start slave relay_fetch_thread;
停止 relay_fetch_thread: stop slave relay_fetch_thread;
relay fetch thread 读取relay log, 并将要执行的数据从磁盘上加载到内存中,所以只能对包含数据部分的 log_event 进行操作,对 Query_log_event,Write_rows_log_event 是无法进行预读的,前者是因为Query_log_event 只是SQL语句,不包含具体的数据信息;后者则是event中没有的数据,所以不需要进行加载,另外为了防止 buffer pool 中读取的 page 被 evict 出去,我们需要对两种情况进行分别处理:
因此,relay fetch thread 与 sql thread 应该相差的距离不太远,我们的策略是 relay fetch thread 与 sql thread 应该在同一个 relay log 上,具体策略如下:
relay fetch thread 执行过程的伪码如下:
handle_slave_relay_fetch
{
init_thd_and_rli();
while (!relay_fetch_killed(eli))
{
ev= Log_event::read_log_event(&rli->relay_log_buf, 0, rli->relay_log.description_event_for_relay_fetch);
if (ev == NULL)
{
deal with situations like hot_log, relay log purged, eof of relay log etc.
}
else
{
switch(ev->get_type_code())
{
case QUERY_EVENT:
deal with begin, commit
break;
case XID_EVENT:
deal with xid(commit)
break;
case TABLE_MAP_EVENT:
init table info for rows log event
break;
case UPDATE_ROWS_EVENT:
case DELETE_ROWS_EVENT:
find_row();
break;
case FORMAT_DESCRIPTION_EVENT:
init description_event_for_relay_fetch for reading binlog event;
default:
break;
}
delete ev;
}
}
}