详解MySQL Server端如何发送结果集给客户端

来源:互联网 发布:端口镜像 流量分析 编辑:程序博客网 时间:2024/06/10 23:45

MySQL Server和Client之间的交互有一套定义得很明确的协议,称为MySQL Client/Server Protocol。 写数据库的人,只需要遵循这套协议来写程序,就能让自己的数据库被各种MySQL客户端连接,如mysql命令行,php mysql,JDBC等等。这是一个非常诱人的设计选择(Design Choice)!如果自己实现一套协议,写完数据库后,还需要给各种语言写客户端库,写各种客户端软件,完全就是噩梦。

MySQL源码中,哪里负责实现这个协议呢?这里:

sql/protocol.cc

对于一个select语句,他会有很多行结果,每一行结果都是调用

bool Protocol::send_result_set_row(List<Item> *row_items)

来发送,它的详细实现如下:

/**  Send one result set row.  @param row_items a collection of column values for that row  @return Error status.    @retval TRUE  Error.    @retval FALSE Success.*/bool Protocol::send_result_set_row(List<Item> *row_items){  char buffer[MAX_FIELD_WIDTH];  String str_buffer(buffer, sizeof (buffer), &my_charset_bin);  List_iterator_fast<Item> it(*row_items);  DBUG_ENTER("Protocol::send_result_set_row");  for (Item *item= it++; item; item= it++)  {    if (item->send(this, &str_buffer))    {      // If we're out of memory, reclaim some, to help us recover.      this->free();      DBUG_RETURN(TRUE);    }    /* Item::send() may generate an error. If so, abort the loop. */    if (thd->is_error())      DBUG_RETURN(TRUE);    /*      Reset str_buffer to its original state, as it may have been altered in      Item::send().    */    str_buffer.set(buffer, sizeof(buffer), &my_charset_bin);  }  DBUG_RETURN(FALSE);}

一行数据中有很多列,每一列都是一个Item对象,它有一个send方法,负责将Item中的数据按照MySQL Client/Server协议“序列化”到发送缓冲区内:

item->send(this, &str_buffer)

Item是一个基类,它下面有很多子类,子类下面还有子类。如下图1,显示了第一层子类 (图片由Doxgen自动生成), 图2、3是部分细节展开。
Item类层次

Item_ident子类层次

常量类结构

如有必要,任何子类都可以去实现send方法。MySQL中,如下一个类实现了send,其中Item::send是兜底方案。

virtual bool Item::send(Protocol *protocol, String *str);inline bool Item_sp_variable::send(Protocol *protocol, String *str);bool Item_name_const::send(Protocol *protocol, String *str)bool Item_field::send(Protocol *protocol, String *str_arg);bool Item_null::send(Protocol *protocol, String *str);bool Item_ref::send(Protocol *prot, String *tmp);bool Item_func_set_user_var::send(Protocol *protocol, String *str_arg);

Field和Item之间如何建立联系的呢?Item_field类!

Item_field(Field *field); // 会将field保存到Item_field::result_field

它将Field封装为一个Item,然后通过下面的代码实现结果的桥接:

bool Item_field::send(Protocol *protocol, String *buffer){  return protocol->store(result_field);}

进而调用

bool Protocol_text::store(Field *field){  if (field->is_null())    return store_null();  char buff[MAX_FIELD_WIDTH];  String str(buff,sizeof(buff), &my_charset_bin);  const CHARSET_INFO *tocs= this->thd->variables.character_set_results;  field->val_str(&str);  /// bridge point  return store_string_aux(str.ptr(), str.length(), str.charset(), tocs);}

例如,Field是一个Field_medium,则调用下面的代码:

String *Field_medium::val_str(String *val_buffer,            String *val_ptr __attribute__((unused))){  ASSERT_COLUMN_MARKED_FOR_READ;  const CHARSET_INFO *cs= &my_charset_numeric;  uint length;  uint mlength=max(field_length+1,10*cs->mbmaxlen);  val_buffer->alloc(mlength);  char *to=(char*) val_buffer->ptr();  long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr);  length=(uint) cs->cset->long10_to_str(cs,to,mlength,-10,j);  val_buffer->length(length);  if (zerofill)    prepend_zeros(val_buffer); /* 他们这个补0做得比较挫,val_buffer是个临时缓冲区,并且还会有memmove操作在里面 */  val_buffer->set_charset(cs);  return val_buffer;}

这里会将数值转化成字符串,如果有zerofill标记,还会根据需要在字符串前面补0。

[TBC/未完]

1 0