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