likes
comments
collection
share

MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的

作者站长头像
站长
· 阅读数 34

MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的

我们知道,MySQL 中的 SELECT 语句是可以当成计算器使用的,例如,

mysql> SELECT 3*7;
+-----+
| 3*7 |
+-----+
|  21 |
+-----+
1 row in set (0.00 sec)

mysql> SELECT sin(pi()/2);
+-------------+
| sin(pi()/2) |
+-------------+
|           1 |
+-------------+
1 row in set (0.00 sec)

本文就来梳理一下与“SELECT <算数表达式>”这种 SQL 语句相关的 MySQL 源代码。

胡译胡说 软件工程师、技术图书译者。译有《图解云计算架构》《图解量子计算机》《图解TCP/IP(第6版)》《计算机是怎样跑起来的》《自制搜索引擎》等。


下面,我们借助 gdb 对照着源代码来探索 MySQL 计算算数表达式的流程

使用 gdb 附加到 MySQL 的服务器进程 mysqld 后,执行如下命令,

$ sudo gdb --tui -q -p $(pidof mysqld)

(gdb) set print object on
(gdb) b THD::send_result_set_row

在 C++ 的代码中,有大量用抽象类/父类的指针指向派生类/子类的对象这种写法,这会导致 gdb 中的 ptype <ptr> 命令无法显示 <ptr> 指向对象的实际类型(派生类/具体类)。执行 set print object on 命令后就可在 ptype 命令的输出中看到对象的实际类型了。

更多内容可参考这个页面 stackoverflow.com/questions/8…

再进入 MySQL 客户端的终端窗口,并提交 SQL 语句 SELECT 3*7;

回到 gdb 的终端窗口中,按下 c 键后,程序应该会暂停在 THD::send_result_set_row() 这个函数。接下来就可以通过 ns 命令开始调试了。

SELECT 3*7; 相关的源代码

// mysql-5.7.44/sql/sql_class.cc +4737
bool THD::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("send_result_set_row");

  for (Item *item= it++; item; item= it++)  // ②
  {
    if (item->send(m_protocol, &str_buffer) || is_error())
      DBUG_RETURN(true);
    ...

这里通过迭代器 List_iterator_fast<Item> it ①遍历的是 Item 类的实例②。该类的实例对应着 SELECT <item1>, <item2>, ..., <itemN> 子句中的 <item_i>

对于 SELECT 3*7; 这条语句,3*7 就是一个 Item,类型为 Item 的子类 Item_func_mul

(gdb) ptype item
type = /* real type = Item_func_mul * */
class Item : public Parse_tree_node {
  private:

继续调试,跟踪到 Item::send() 这个方法中,

/**
  This is only called from items that is not of type item_field.
*/
bool Item::send(Protocol *protocol, String *buffer)
{
  bool result= false;
  enum_field_types f_type;

  switch ((f_type=field_type())) {  // ①
  default:
  case MYSQL_TYPE_NULL:
  ...
  case MYSQL_TYPE_LONGLONG:
  {
    longlong nr;
    nr= val_int();           // ②
    if (!null_value)
      result= protocol->store_longlong(nr, unsigned_flag);
    break;
  }
  case MYSQL_TYPE_FLOAT:
  ...

这里会根据 item 的 field_type() (该函数里面又调用了 result_type())①采取不同的处理。由于 3*7 的结果类型对应着 MYSQL_TYPE_LONGLONG,所以接下来会执行 val_int() 来计算②。

MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的

如图所示,这里广泛使用了模版方法设计模式

  • item 的结果到底是什么类型呢?这要由子类(Item_func_numhybrid)中重写版本的 result_type() 决定
  • 如何计算 item 的值呢?这也要由子类(Item_func_numhybrid)中重写过的 val_int() 计算
  • 甚至在 Item_func_numhybrid::val_int() 的实现中,又是预留了 int_op() 这样的接口(virtual = 0 纯虚函数),具体怎么计算还是交由子类(Item_func_mul::int_op())决定,即所谓的延迟到子类中实现

SELECT sin(pi()/2); 的 UML 对象图和类图

MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的

虽然表达式 sin(pi()/2) 是内含一个除法算式的函数调用表达式,但依然仅对应一个 Item 类(具体类型是Item_func_sin)的对象

  • Item_float 对象代表 pi() 并作为 Item_func_div 的一个输入
  • Item_int 对象代表 2 并作为 Item_func_div 的另一个输入
  • Item_func_div 对象计算 pi()/2,并将结果提供给 Item_func_sin 对象
  • Item_func_sin 对象计算 sin⁡(pi()/2)

MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的


好了,MySQL 计算算数表达式的流程就先梳理到这里。

下回再一起利用 gdb 探索其他有意思的部分吧!

转载自:https://juejin.cn/post/7374723564400279578
评论
请登录