MySQL:sending data狀態包含了什麼

gaopengtttt發表於2018-09-28

MySQL:sending data狀態包含了什麼

一、問題由來

這是一個朋友問我的(@成都--麥澀可),原問題如下:

  • 資料庫傳送資料給客戶端這個時間算是sql的執行時間嘛?

要解決問題我們需要知道MySQL何時將資料傳輸給了客戶端,既然是要傳輸實際的資料給客戶端那麼肯定是select語句了,同時我們要明白一個正常select執行到底要經歷哪些階段。

二、一個簡單SELECT語句經歷的階段

  2392  T@10: | | | | | THD::enter_stage: 'checking permissions' /root/mysql5.7.14/percona-server-5.7.14-7/sql/auth/sql_authorization.cc:843
  2404  T@10: | | | | | | THD::enter_stage: 'Opening tables' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_base.cc:5719
  2512  T@10: | | | | | THD::enter_stage: 'init' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_select.cc:121
  2681  T@10: | | | | | | | THD::enter_stage: 'System lock' /root/mysql5.7.14/percona-server-5.7.14-7/sql/lock.cc:321
  2772  T@10: | | | | | | | THD::enter_stage: 'optimizing' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_optimizer.cc:151
  2865  T@10: | | | | | | | THD::enter_stage: 'statistics' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_optimizer.cc:386
  3329  T@10: | | | | | | | THD::enter_stage: 'preparing' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_optimizer.cc:494
  3466  T@10: | | | | | | THD::enter_stage: 'executing' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_executor.cc:119
  3474  T@10: | | | | | | THD::enter_stage: 'Sending data' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_executor.cc:195
  3668  T@10: | | | | | THD::enter_stage: 'end' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_select.cc:199
  3685  T@10: | | | | THD::enter_stage: 'query end' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_parse.cc:5174
  3754  T@10: | | | | THD::enter_stage: 'closing tables' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_parse.cc:5252
  3882  T@10: | | | THD::enter_stage: 'freeing items' /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_parse.cc:5855

實際上整個階段都算到了語句的實際消耗時間之中的,但是慢查詢記錄的是:

  • 實際執行時間 = (freeing items 末端的時間) 實際消耗時間 - (System lock末端的時間 )比如MDL LOCK等待時間 - (Innodb層鎖定的時間)行鎖等待消耗時間

關於慢查詢的詳細情況可以參考我的一篇文章,這裡不再重述。

 MySQL慢查詢記錄原理和內容解析

三、什麼是Sending data狀態

實際上這個狀態是select語句才會有的,如果是DML則不同但是都有等同的階段如下:

  • select:Sending data

  • insert語句:Update

  • delete/update:Updating

這個階段非常的巨大,它至少包含了:

  • Innodb 層資料的定位返回給MySQL 層

  • Innodb 層資料的查詢返回給MySQL 層

  • Innodb 層資料的修改(如果是MDL)

  • Innodb 層加鎖以及等待

  • 等待進入Innodb層(innodb_thread_concurrency引數)

  • MySQL 層傳送資料給客戶端

可以看到基本所有和Innodb層打交道的過程都包裹在這個狀態下面,當然我只是列舉了我想到的一些,其實可能還有很多很多,這裡我也把  MySQL 層傳送資料給客戶端 包含在 Sending data的答案給出了,下面我們進行分析。

四、如何證明Sending data狀態包含了網路資料傳輸的時間

之前你可以參考一下我的文章

 MySQL:Innodb Handler_read_*變數解釋

我們建立一個簡單的表如下,並且填充資料如下:

Create Table: CREATE TABLE `ty` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `a` int(11) DEFAULT NULL,  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idxa` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4
mysql> select * from ty;
+----+------+------+| id | a    | b    |+----+------+------+|  8 |    2 |    3 ||  9 |    5 |    4 || 10 |    6 |    7 || 11 |    6 |    8 |+----+------+------+4 rows in set (4.14 sec)

我們執行如下語句:

mysql> desc select * from ty where a =6 and b=8;
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | ty    | NULL       | ref  | idxa          | idxa | 5       | const |    2 |    33.33 | Using where |+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
mysql> select * from ty where a =6 and b=8;
+----+------+------+| id | a    | b    |+----+------+------+| 11 |    6 |    8 |+----+------+------+1 row in set (7.22 sec)

注意:這裡的語句執行時間很長是因為我打了GDB斷點所以看起來很久而已
我做了語句的trace,這個語句大概需要如下步驟:

  • 首先Innodb定位到索引  idxa 資料6所在位置這是初次定位呼叫函式ha_innobase::index_read,返回資料| 10 | 6 | 7 |給MySQL層,但是MySQL層過濾掉不符合條件 a =6 and b=8 不需要返回給客戶端。

   1547 T@12: | | | | | | | | >handler::ha_index_read_map   1548 T@12: | | | | | | | | | >index_read (這裡進行初次訪問索引定位)
   1552 T@12: | | | | | | | | | | >row_search_mvcc
   1553 T@12: | | | | | | | | | | | >btr_cur_search_to_nth_level   1554 T@12: | | | | | | | | | | | <btr_cur_search_to_nth_level 2009
...
   1593 T@12: | | | | | | | | | | <row_search_mvcc 6070
   1594 T@12: | | | | | | | | | <index_read 9179
   1595 T@12: | | | | | | | | <handler::ha_index_read_map 3190
   1596 T@12: | | | | | | | | >evaluate_join_record (這裡進入MySQL 層where條件判斷流程,不滿足不傳送)
...   1600 T@12: | | | | | | | | <evaluate_join_record 1701
  • 定位完成後再次訪問索引  idxa 的下一條資料,Innodb直接讀取就好了呼叫函式ha_innobase::index_next_same,返回資料| 11 | 6 | 8 |給Mysql層,因為滿足條件 a =6 and b=8  所以返回給客戶端

  1601 T@12: | | | | | | | | >handler::ha_index_next_same(這裡就是順序訪問索引的下一行資料了)   1602 T@12: | | | | | | | | | >general_fetch
   1603 T@12: | | | | | | | | | | >row_search_mvcc
   ....
   1607 T@12: | | | | | | | | | | <row_search_mvcc 6070
   1608 T@12: | | | | | | | | | <general_fetch 9487
   1609 T@12: | | | | | | | | <handler::ha_index_next_same 3414
   1610 T@12: | | | | | | | | >evaluate_join_record(這裡進入MySQL 層where條件判斷流程,滿足條件需要傳送)   1613 T@12: | | | | | | | | | >end_send
   1614 T@12: | | | | | | | | | | >Query_result_send::send_data(這裡就是傳送資料給客戶端了,也就是透過網路傳送資料給客戶端了)
   1615 T@12: | | | | | | | | | | | >send_result_set_row   1616 T@12: | | | | | | | | | | | <send_result_set_row 4967
   1620 T@12: | | | | | | | | | | <Query_result_send::send_data 2915
   1621 T@12: | | | | | | | | | | >Protocol_classic::end_row
   1622 T@12: | | | | | | | | | | <Protocol_classic::end_row 1198
   1625 T@12: | | | | | | | | | <end_send 2991
   1626 T@12: | | | | | | | | <evaluate_join_record 1701
  • 因為是非唯一索引因此需要再次訪問下一條資料來判斷已經讀取了所有a=6的資料,因此Innodb需要在讀取索引  idxa 的下一條資料呼叫函式ha_innobase::index_next_same。

   1627 T@12: | | | | | | | | >handler::ha_index_next_same(這裡就是順序訪問索引的下一行資料了)   1628 T@12: | | | | | | | | | >general_fetch
   1629 T@12: | | | | | | | | | | >row_search_mvcc
  ...
   1639 T@12: | | | | | | | | | | <row_search_mvcc 6070
   1640 T@12: | | | | | | | | | <general_fetch 9487
   1641 T@12: | | | | | | | | <handler::ha_index_next_same 3414

所以總結整個流程一共經歷了一次索引定位,兩次索引順序讀取,一共讀取了三條資料,但是返回給MySQL層的只有前面兩條資料,透過MySQL層的過濾只傳送給了客戶端一條滿足條件的資料。

五、總結

很顯然資料返回給客戶端的時間包含在了整個語句的實際消耗時間中,同時包含在了慢查詢的實際執行時間(Query_time指標)中,它是在sending data狀態下完成的。

作者微信:



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2215202/,如需轉載,請註明出處,否則將追究法律責任。

相關文章