MySQL 是如何处理“SELECT <算数表达式>”这类 SQL 语句的
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() 这个函数。接下来就可以通过 n 和 s 命令开始调试了。
与 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() 来计算②。

如图所示,这里广泛使用了模版方法设计模式。
- 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 对象图和类图

虽然表达式 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 计算算数表达式的流程就先梳理到这里。
下回再一起利用 gdb 探索其他有意思的部分吧!
转载自:https://juejin.cn/post/7374723564400279578