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

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/未完]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值