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()來判斷是否開啟事務