这几天研究了下binlog中row模式如何工作的,目的是寻求一种方法来模拟mysql的处理方式,解析出row模式时存储在binlog的行数据
当在master上执行一条SQL语句,在ROW模式下,总共做四次解析
1.Begin:Query_log_event::Query_log_event
2.Table Map:Table_map_log_event::Table_map_log_event 与表模式相关的信息,用于配合对行数据的解析
3.
Write_rows_log_event::Write_rows_log_event
Update_rows_log_event::Update_rows_log_event
Delete_rows_log_event::Delete_rows_log_event
4.commit:XID_EVENT
1.入口函数:我们可以把断点设在
0x00000000006d6dc4 in exec_relay_log_event (thd=0xe94d910, rli=0xe93a960) at slave.cc:2242
在handle_slave_sql函数中,有一个while循环会不断的调用exec_relay_log_event函数
在exec_relay_log_event中,主要会调用以下几个函数:
Log_event * ev = next_event(rli); 从cache或relay log中读取一条记录
exec_res= apply_event_and_update_pos(ev, thd, rli); //执行binlog事件并修改当前读的位置
2. next_event
next_event首先会调用:
Log_event* Log_event::read_log_event(IO_CACHE* file,
pthread_mutex_t* log_lock,
const Format_description_log_event
*description_event)
来读取记录,首先会读取头部信息,并检查头部信息是否合法,然后调用res= read_log_event(buf, data_len, &error, description_event)
原型:
Log_event* Log_event::read_log_event(const char* buf, uint event_len,
const char **error,
const Format_description_log_event *description_event)
在这里会根据事件的类型来调用相应的构造函数,这里我们只关心ROW模式的事件处理:
case WRITE_ROWS_EVENT:
ev = new Write_rows_log_event(buf, event_len, description_event);
break;
case UPDATE_ROWS_EVENT:
ev = new Update_rows_log_event(buf, event_len, description_event);
break;
case DELETE_ROWS_EVENT:
ev = new Delete_rows_log_event(buf, event_len, description_event);
break;
OK,形成一个事件暂时不是我所关心的,因为在最终的ev中,并没有把我想要的数据从relay log中解析出来,也就是说没有对读取的ROW数据进行分析提取。
不同的事件类型对应不同的处理函数,以delete、update和insert为例:
(gdb) b Update_rows_log_event::do_exec_rowBreakpoint 24 at 0x65ec84: file log_event.cc, line 9290.
(gdb) b Delete_rows_log_event::do_exec_rowBreakpoint 25 at 0x65ef7c: file log_event.cc, line 9166.
(gdb) b Write_rows_log_event::do_exec_rowBreakpoint 26 at 0x65f98a: file log_event.cc, line 8703.
这里我只关心update操作,因此继续跟进:
首先调用 int error= find_row(rli); 在表中找到相应的行(binlog row模式会记录旧版本),若不存在则返回error(在 mysqld的输出信息中可以看到sql线程报错error)
然后,解析数据,并将新的行数据存储到record[0]中
最后调用存储引擎接口函数handler::ha_update_row
其实,通过分析,不难发现,对binlog最终处理方式,都会在存储引擎之上,调用相应的接口,例如在statement模式下,就会模拟一个新的SQL请求提交给mysql_parse函数
现在还没有达到我们的目的,在最初分析的时候。将大量精力放在寻找mysql何时才会将其记录在ev中的record[0](新版本数据)和record[1]进行解析,但一直执行到ha_innobase::update_row都没有对其中的数据做任何处理
无奈之下,继续观察结构体数据,结果在执行ha_update_row之前,观察到m_table->field被赋值。其类型为FIELD**,显然,这是个结构体数组,尝试着打印其中的信息,发现数组中每个元素分别对应表的各个字段,并且存储的是新版本数据
现在的问题是:找到在什么地方、如何解析行数据的?
gdb的过程中,发现一个诡异的现象:
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) n
3569 if (m_curr_row_end > m_rows_end)
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) n
9323 if ((error= unpack_current_row(rli, abort_on_warnings)))
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) n
9335 DBUG_PRINT("info",("Updating row in table"));
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) n
9336 DBUG_DUMP("old record", m_table->record[1], m_table->s->reclength);
(gdb) p m_table->field[2]->ptr
Cannot access memory at address 0x0
(gdb) n
9337 DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength);
(gdb) p m_table->field[2]->ptr
$258 = (uchar *) 0x2aaab800628a "\rhello,world!!", ' ' <repeats 87 times>
(gdb)
问题总得要解决,反复gdb跟踪,发现m_table与unpack_row 函数中的table参数内存地址完全一致,尽管不知道何时赋值给m_table的……
那么,解决问题的关键就是unpack_row函数了。大致浏览了下代码,也跟预想中的很符合,下一步将深入进去看看解析的具体流程,验证猜想!