searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

MySQL是如何得到一个客户端命令的?

2024-09-05 09:26:34
1
0

MySQL是如何得到一个客户端命令的?

因为在线程池中看到AliSQL的优化,其中说到改进到在线程池中做到:
接收到一个请求之后,先读取网络包,在根据包的进行操作类型的识别,然后得到SQL语句,然后可以根据SQL的类型与上下文将其分类:

  • 查询操作,会话处于自动提交模式,SQL类型为查询语句。
  • 更新操作,会话处于自动提交模式,SQL类型为DML语句。
  • 事务操作,会话处于事务模式(start transaction或autocommit=0)下的任何语句。
  • 管理操作,以上之外的操作,比如“show”、“set”等操作。

所以在解析器执行之前对SQL进行分类,是到底是怎么做到的呢? 通过源码来做一个分析

Server等待客户端的请求

在mysql原生的连接管理方式中每线程每连接,每个连接会阻塞在get_command这个函数里,等待客户端的请求
在这之前会设置阻塞的时间(net_wait_timeout默认8小时),超时后会退出

> do_command
  > my_net_set_read_timeout(net, thd->variables.net_wait_timeout)
  > thd->get_protocol()->get_command()

而在percona线程池中,其通过监听线程(listener)来监听(epoll方式)事件,如果客户端有数据可以读,那么监听线程会把这个连接分配给一个工作线程去处理(也可能自己处理)
这里可以注意到,工作线程处理时,在正常情况下,到了get_command()的时候一定是有数据的,不用等待

一步步读取网络包

> Protocol_classic::get_command()
  > read_packet()
    > my_net_read()
      > net_read_compressed_packet()/net_read_uncompressed_packet() // 压缩/未压缩
        > net_read_packet()           // 这里可能会有多个包,超过最大长度
          > net_read_packet_header()  // 读取包头 包括协议头,大小和数量
            > net_read_raw_loop()     // 读取包头
          > net_read_raw_loop()       // 读取包的内容
            > vio_read()              // 循环读取
              > mysql_socket_recv()
                > recv()              // 接收socket缓存区的数据
  > parse_packet()                    // 解析包

解析数据包

parse_packet()函数把网络包解析并保存

格式:

Type Name Description
int<1> 执行命令 执行的操作,比如切换数据库,查询表等操作
string 参数 命令相关的内容

第一个参数是执行的命令类型(enum_server_command),如COM_QUERY,COM_INIT_DB等
第二个参数是命令相关的内容,如果COM_INIT_DB保存切换的数据库名,COM_QUERY保存了执行的SQL及参数等

如何区分不同的SQL

在线程池的处理阶段,我们可以通过从网络包中解析到客户端执行命令的类型,但是有一个问题:
由于事务在do_command阶段还需要解析,意味着每个请求的网络包会被解析两次,第一次需要通过MSG_PEEK的方式读取,即不会清空缓冲区,这样第二次仍然可以读取到网络包

通过解析数据包,可以很容易的区别不同的server_command,直接通过返回的网络包的第一个字节即可判断

如何区分COM_QUERY命令中执行的SQL,没有通过解析器解析SQL,只能通过字符串匹配的方式来获取信息,这样获取到的信息是不足的

再看AliSQL的区分的:

  • 查询操作,会话处于自动提交模式,SQL类型为查询语句。
  • 更新操作,会话处于自动提交模式,SQL类型为DML语句。
  • 事务操作,会话处于事务模式(start transaction或autocommit=0)下的任何语句。
  • 管理操作,以上之外的操作,比如“show”、“set”等操作。

只能通过简单的字符串匹配select,来确定是否是查询语句
通过匹配insert,update,delete来确定是否是DML
可以通过thd_is_transaction_active()来判断是否开启事务

0条评论
0 / 1000
qinyl
6文章数
0粉丝数
qinyl
6 文章 | 0 粉丝
qinyl
6文章数
0粉丝数
qinyl
6 文章 | 0 粉丝
原创

MySQL是如何得到一个客户端命令的?

2024-09-05 09:26:34
1
0

MySQL是如何得到一个客户端命令的?

因为在线程池中看到AliSQL的优化,其中说到改进到在线程池中做到:
接收到一个请求之后,先读取网络包,在根据包的进行操作类型的识别,然后得到SQL语句,然后可以根据SQL的类型与上下文将其分类:

  • 查询操作,会话处于自动提交模式,SQL类型为查询语句。
  • 更新操作,会话处于自动提交模式,SQL类型为DML语句。
  • 事务操作,会话处于事务模式(start transaction或autocommit=0)下的任何语句。
  • 管理操作,以上之外的操作,比如“show”、“set”等操作。

所以在解析器执行之前对SQL进行分类,是到底是怎么做到的呢? 通过源码来做一个分析

Server等待客户端的请求

在mysql原生的连接管理方式中每线程每连接,每个连接会阻塞在get_command这个函数里,等待客户端的请求
在这之前会设置阻塞的时间(net_wait_timeout默认8小时),超时后会退出

> do_command
  > my_net_set_read_timeout(net, thd->variables.net_wait_timeout)
  > thd->get_protocol()->get_command()

而在percona线程池中,其通过监听线程(listener)来监听(epoll方式)事件,如果客户端有数据可以读,那么监听线程会把这个连接分配给一个工作线程去处理(也可能自己处理)
这里可以注意到,工作线程处理时,在正常情况下,到了get_command()的时候一定是有数据的,不用等待

一步步读取网络包

> Protocol_classic::get_command()
  > read_packet()
    > my_net_read()
      > net_read_compressed_packet()/net_read_uncompressed_packet() // 压缩/未压缩
        > net_read_packet()           // 这里可能会有多个包,超过最大长度
          > net_read_packet_header()  // 读取包头 包括协议头,大小和数量
            > net_read_raw_loop()     // 读取包头
          > net_read_raw_loop()       // 读取包的内容
            > vio_read()              // 循环读取
              > mysql_socket_recv()
                > recv()              // 接收socket缓存区的数据
  > parse_packet()                    // 解析包

解析数据包

parse_packet()函数把网络包解析并保存

格式:

Type Name Description
int<1> 执行命令 执行的操作,比如切换数据库,查询表等操作
string 参数 命令相关的内容

第一个参数是执行的命令类型(enum_server_command),如COM_QUERY,COM_INIT_DB等
第二个参数是命令相关的内容,如果COM_INIT_DB保存切换的数据库名,COM_QUERY保存了执行的SQL及参数等

如何区分不同的SQL

在线程池的处理阶段,我们可以通过从网络包中解析到客户端执行命令的类型,但是有一个问题:
由于事务在do_command阶段还需要解析,意味着每个请求的网络包会被解析两次,第一次需要通过MSG_PEEK的方式读取,即不会清空缓冲区,这样第二次仍然可以读取到网络包

通过解析数据包,可以很容易的区别不同的server_command,直接通过返回的网络包的第一个字节即可判断

如何区分COM_QUERY命令中执行的SQL,没有通过解析器解析SQL,只能通过字符串匹配的方式来获取信息,这样获取到的信息是不足的

再看AliSQL的区分的:

  • 查询操作,会话处于自动提交模式,SQL类型为查询语句。
  • 更新操作,会话处于自动提交模式,SQL类型为DML语句。
  • 事务操作,会话处于事务模式(start transaction或autocommit=0)下的任何语句。
  • 管理操作,以上之外的操作,比如“show”、“set”等操作。

只能通过简单的字符串匹配select,来确定是否是查询语句
通过匹配insert,update,delete来确定是否是DML
可以通过thd_is_transaction_active()来判断是否开启事务

文章来自个人专栏
文章
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0