天天看點

MySQL:FTWRL一個奇怪的堵塞現象和堵塞總結

本案例由徐晨亮提供,并且一起探讨。

本文中

FTWRL = “flush table with read lock”

關于常用操作加MDL LOCK鎖類型參考文章:

http://blog.itpub.net/7728585/viewspace-2143093/ 歡迎關注我的《深入了解MySQL主從原理 32講 》,如下:

一、兩個不同的現象

首先建立一張有幾條資料的表就可以了,我這裡是baguait1表了。

  • 案例1

|SESSION1|SESSION2|SESSION3|

|-|-|-|

|步驟1:select sleep(1000) from baguait1 for update;|||

||步驟2:flush table with read lock;堵塞||

|||步驟3:kill session2|

|||步驟4:select * from baguait1 limit 1;成功 |

步驟2 “flush table with read lock;”操作等待狀态為“Waiting for global read lock”,如下:

mysql> select Id,State,Info   from information_schema.processlist  where command<>'sleep';
+----+------------------------------+------------------------------------------------------------------------------------+
| Id | State                        | Info                                                                               |
+----+------------------------------+------------------------------------------------------------------------------------+
|  1 | Waiting on empty queue       | NULL                                                                               |
| 18 | Waiting for global read lock | flush table with read lock                                                         |
|  3 | User sleep                   | select sleep(1000) from baguait1 for update                                        |
|  6 | executing                    | select Id,State,Info   from information_schema.processlist  where command<>'sleep' |
+----+------------------------------+------------------------------------------------------------------------------------+           
  • 案例2

這裡比較奇怪了,實際上我很久以前就遇到過和測試過但是沒有仔細研究過,這次剛好詳細看看。

|步驟1:select sleep(1000) from baguait1 |||

|||步驟4:select * from baguait1 limit 1;堵塞 |

步驟2 “flush table with read lock;”操作等待狀态為 “Waiting for table flush”,狀态如下:

mysql> select Id,State,Info   from information_schema.processlist  where command<>'sleep';
+----+-------------------------+------------------------------------------------------------------------------------+
| Id | State                   | Info                                                                               |
+----+-------------------------+------------------------------------------------------------------------------------+
|  1 | Waiting on empty queue  | NULL                                                                               |
| 26 | User sleep              | select sleep(1000) from baguait1                                                   |
| 23 | Waiting for table flush | flush table with read lock                                                         |
|  6 | executing               | select Id,State,Info   from information_schema.processlist  where command<>'sleep' |
+----+-------------------------+------------------------------------------------------------------------------------+           

步驟4 “select * from testmts.baguait1 limit 1”操作等待狀态為 “Waiting for table flush”,這個現象看起來非常奇怪沒有任何特殊的其他操作,select居然堵塞了。

mysql> select Id,State,Info   from information_schema.processlist  where command<>'sleep';
+----+-------------------------+------------------------------------------------------------------------------------+
| Id | State                   | Info                                                                               |
+----+-------------------------+------------------------------------------------------------------------------------+
|  1 | Waiting on empty queue  | NULL                                                                               |
| 26 | User sleep              | select sleep(1000) from baguait1                                                   |
| 27 | executing               | select Id,State,Info   from information_schema.processlist  where command<>'sleep' |
|  6 | Waiting for table flush | select * from testmts.baguait1 limit 1                                             |
+----+-------------------------+------------------------------------------------------------------------------------+           

如果仔細對比兩個案例實際上差別僅僅在于 步驟1中的select 語句是否加了for update,案例2中我們發現即便我們将“flush table with read lock;”會話KILL掉也會堵塞随後的關于本表上全部操作(包括select),這個等待實際上會持續到步驟1的sleep操作完成過後。

對于線上資料庫的話,如果在長時間的select大表期間執行“flush table with read lock;”就會出現這種情況,這種情況會造成全部關于本表的操作等待,即便你發現後殺掉了FTWRL會話也無濟于事,等待會持續到select操作完成後,除非你KILL掉長時間的select操作。

為什麼會出現這種情況呢?我們接下來慢慢分析。

二、sleep 函數生效點

關于本案例中我使用sleep函數來代替select 大表操作做為測試,在這裡這個代替是成立的。為什麼成立呢我們來看一下sleep函數的生效點如下:

T@3: | | | | | | | | >evaluate_join_record
T@3: | | | | | | | | | enter: join: 0x7ffee0007350 join_tab index: 0 table: tii cond: 0x0
T@3: | | | | | | | | | counts: evaluate_join_record join->examined_rows++: 1
T@3: | | | | | | | | | >end_send
T@3: | | | | | | | | | | >Query_result_send::send_data
T@3: | | | | | | | | | | | >send_result_set_row
T@3: | | | | | | | | | | | | >THD::enter_cond
T@3: | | | | | | | | | | | | | THD::enter_stage: 'User sleep' /mysqldata/percona-server-locks-detail-5.7.22/sql/item_func.cc:6057
T@3: | | | | | | | | | | | | | >PROFILING::status_change
T@3: | | | | | | | | | | | | | <PROFILING::status_change 384
T@3: | | | | | | | | | | | | <THD::enter_cond 3405           

這裡看出sleep的生效點實際上每次Innodb層傳回一行資料經過where條件判斷後,再觸發sleep函數,也就是每行經過where條件過濾的資料在發送給用戶端之前都會進行一次sleep操作。這個時候實際上該打開表的和該上MDL LOCK的都已經完成了,是以使用sleep函數來模拟大表select操作導緻的FTWRL堵塞是可以的。

三、FTWRL做了什麼工作

實際上這部分我們可以在函數mysql_execute_command尋找case SQLCOM_FLUSH 的部分,實際上主要調用函數為reload_acl_and_cache,其中核心部分為:

if (thd->global_read_lock.lock_global_read_lock(thd))//加 MDL GLOBAL 級别S鎖
    return 1;                               // Killed
      if (close_cached_tables(thd, tables, //關閉表操作釋放 share 和 cache
                              ((options & REFRESH_FAST) ?  FALSE : TRUE),
                              thd->variables.lock_wait_timeout)) //等待時間受lock_wait_timeout影響
      {
        /*
          NOTE: my_error() has been already called by reopen_tables() within
          close_cached_tables().
        */
        result= 1;
      }

      if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // MDL COMMIT 鎖
      {
        /* Don't leave things in a half-locked state */
        thd->global_read_lock.unlock_global_read_lock(thd);
        return 1;
      }           

更具體的關閉表的操作和釋放table緩存的部分包含在函數close_cached_tables中,我就不詳細寫了。但是我們需要明白table緩存實際上包含兩個部分:

  • table cache define:每一個表第一次打開的時候都會建立一個靜态的表定義結構記憶體,當多個會話同時通路同一個表的時候,從這裡拷貝成相應的instance供會話自己使用。由參數table_definition_cache定義大小,由狀态值Open_table_definitions檢視目前使用的個數。對應函數get_table_share。
  • table cache instance:同上所述,這是會話實際使用的表定義結構是一個instance。由參數table_open_cache定義大小,由狀态值Open_tables檢視目前使用的個數。對應函數open_table_from_share。

這裡我統稱為table緩存,好了下面是我總結的FTWRl的大概步驟:

第一步: 加MDL LOCK類型為GLOBAL 級别為S。如果出現等待狀态為‘Waiting for global read lock’。注意select語句不會上GLOBAL級别上鎖,但是DML/DDL/FOR UPDATE語句會上GLOBAL級别的IX鎖,IX鎖和S鎖不相容會出現這種等待。下面是這個相容矩陣:

| Type of active   |
  Request |   scoped lock    |
   type   | IS(*)  IX   S  X |
 ---------+------------------+
 IS       |  +      +   +  + |
 IX       |  +      +   -  - |
 S        |  +      -   +  - |
 X        |  +      -   -  - |           

第二步:推進全局表緩存版本。源碼中就是一個全局變量 refresh_version++。

第三步:釋放沒有使用的table 緩存。可自行參考函數close_cached_tables函數。

第四步:判斷是否有正在占用的table緩存,如果有則等待,等待占用者釋放。等待狀态為'Waiting for table flush'。這一步會去判斷table緩存的版本和全局表緩存版本是否比對,如果不比對則等待如下:

for (uint idx=0 ; idx < table_def_cache.records ; idx++) 
     {
       share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); //尋找整個 table cache shared hash結構
       if (share->has_old_version()) //如果版本 和 目前 的 refresh_version 版本不一緻
       {
         found= TRUE;
         break; //跳出第一層查找 是否有老版本 存在
       }
     }
...
if (found)//如果找到老版本,需要等待
   {
     /*
       The method below temporarily unlocks LOCK_open and frees
       share's memory.
     */
     if (share->wait_for_old_version(thd, &abstime,
                                   MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
     {
       mysql_mutex_unlock(&LOCK_open);
       result= TRUE;
       goto err_with_reopen;
     }
   }           

而等待的結束就是占用的table緩存的占用者釋放,這個釋放操作存在于函數close_thread_table中,如下:

if (table->s->has_old_version() || table->needs_reopen() ||
      table_def_shutdown_in_progress)
  {
    tc->remove_table(table);//關閉 table cache instance
    mysql_mutex_lock(&LOCK_open);
    intern_close_table(table);//去掉 table cache define
    mysql_mutex_unlock(&LOCK_open);
  }           

最終會調用函數MDL_wait::set_status将FTWRL喚醒,也就是說對于正在占用的table緩存釋放者不是FTWRL會話而是占用者自己。不管怎麼樣最終整個table緩存将會被清空,如果經過FTWRL後去檢視Open_table_definitions和Open_tables将會發現重新計數了。下面是喚醒函數的代碼,也很明顯:

bool MDL_wait::set_status(enum_wait_status status_arg) open_table
{
  bool was_occupied= TRUE;
  mysql_mutex_lock(&m_LOCK_wait_status);
  if (m_wait_status == EMPTY)
  {
    was_occupied= FALSE;
    m_wait_status= status_arg;
    mysql_cond_signal(&m_COND_wait_status);//喚醒
  }
  mysql_mutex_unlock(&m_LOCK_wait_status);//解鎖
  return was_occupied;
}           

第五步:加MDL LOCK類型COMMIT 級别為S。如果出現等待狀态為‘Waiting for commit lock’。如果有大事務的送出很可能出現這種等待。

四、案例1解析

步驟1 我們使用select for update語句,這個語句會加GLOBAL級别的IX鎖,持續到語句結束(注意實際上還會加對象級别的MDL_SHARED_WRITE(SW)鎖持續到事務結束,和FTWRL無關不做描述)

步驟2 我們使用FTWRL語句,根據上面的分析需要擷取GLOBAL級别的S鎖,不相容,是以出現了等待‘Waiting for global read lock’

步驟3 我們KILL掉了FTWRL會話,這種情況下會話退出,FTWRL就像沒有執行過一樣不會有任何影響,因為它在第一步就堵塞了。

步驟4 我們的select操作不會受到任何影響,因為在GLOBAL級别select不會加MDL LOCK,對象級别MDL LOCK select/select for update是相容的(即MDL_SHARED_READ(SR)和MDL_SHARED_WRITE(SW)相容),且FTWRL還沒有執行實際的操作。

五、案例2解析

步驟1 我們使用select 語句,這個語句不會在GLOBAL級别上任何的鎖(注意實際上還會加對象級别的MDL_SHARED_READ(SR)鎖持續到事務結束,和FTWRL無關不做描述)

步驟2 我們使用FTWRL語句,根據上面的分析我們發現FTWRL語句可以擷取了GLOBAL 級别的S鎖,因為單純的select 語句不會在GLOBAL級别上任何鎖。同時會将全局表緩存版本推進然後釋放掉沒有使用的table 緩存。但是在第三節的第四步中我們發現因為baguait1的表緩存正在被占用,是以出現了等待,等待狀态為'Waiting for table flush'。

步驟3 我們KILL掉了FTWRL會話,這種情況下雖然GLOBAL 級别的S鎖會釋放,但是全局表緩存版本已經推進了,同時沒有使用的table 緩存已經釋放掉了。

步驟4 再次執行一個baguait1表上的select 查詢操作,這個時候在打開表的時候會去判斷是否table緩存的版本和全局表緩存版本比對如果不比對進入等待,等待為‘Waiting for table flush’,下面是這個判斷:

if (share->has_old_version())
    {
      /*
        We already have an MDL lock. But we have encountered an old
        version of table in the table definition cache which is possible
        when someone changes the table version directly in the cache
        without acquiring a metadata lock (e.g. this can happen during
        "rolling" FLUSH TABLE(S)).
        Release our reference to share, wait until old version of
        share goes away and then try to get new version of table share.
      */
      release_table_share(share);
     ...
      wait_result= tdc_wait_for_old_version(thd, table_list->db,
                                            table_list->table_name,
                                            ot_ctx->get_timeout(),
                                            deadlock_weight);
           

整個等待操作和FTWRL一樣,會等待占用者釋放table緩存後才會醒來繼續。

是以後續本表的所有select/DML/DDL都會堵塞,代價極高,即便KILL掉FTWRL會話也無用。

六、FTWRL堵塞和被堵塞的簡單總結

(1)被什麼堵塞
  • 長時間的DDLDMLFOR UPDATE堵塞FTWRL,因為FTWRL需要擷取 GLOBAL的S鎖,而這些語句都會對GLOBAL持有IX(MDL_INTENTION_EXCLUSIVE)鎖,根據相容矩陣不相容。等待為:Waiting for global read lock 。本文的案例1就是這種情況。
  • 長時間的select堵塞FTWRL, 因為FTWRL會釋放所有空閑的table緩存,如果有占用者占用某些table緩存,則會等待占用者自己釋放這些table緩存。等待為:Waiting for table flush 。本文的案例2就是這種情況,會堵塞随後關于本表的任何語句,即便KILL FTWRL會話也不行,除非KILL掉長時間的select操作才行。實際上flush table也會存在這種堵塞情況。
  • 長時間的commit(如大事務送出)也會堵塞FTWRL,因為FTWRL需要擷取COMMIT的S鎖,而commit語句會對commit持有IX(MDL_INTENTION_EXCLUSIVE)鎖,根據相容矩陣不相容。
(2)堵塞什麼
  • FTWRL會堵塞DDLDMLFOR UPDATE操作,堵塞點為 GLOBAL級别 的S鎖,等待為:Waiting for global read lock 。
  • FTWRL會堵塞commit操作,堵塞點為COMMIT的S鎖,等待為Waiting for commit lock 。
  • FTWRL不會堵塞select操作,因為select不會在GLOBAL級别上鎖。

最後提醒一下很多備份工具都要執行FTWRL操作,一定要注意它的堵塞/被堵塞場景和特殊場景。

備注棧幀和斷點:

(1)使用的斷點

  • MDL_context::acquire_lock 擷取DML LOCK
  • open_table_from_share 擷取table cache instance
  • alloc_table_share 配置設定table define(share)
  • get_table_share 擷取table define(share)
  • close_cached_tables flush table關閉全部table cache instance 和table define
  • reload_acl_and_cache flush with read lock 進行MDL LOCK加鎖為GLOBAL TYPE:S ,同時調用close_cached_tables 同時擷取COMMIT級别 TYPE S
  • MDL_wait::set_status 喚醒操作
  • close_thread_table 占用者判斷釋放
  • my_hash_delete hash删除操作,從table cache instance 和table define中釋放table緩存都是需要調用這個删除操作的。

(2)FTWRL堵塞棧幀由于select堵塞棧幀:

(gdb) bt
#0  0x00007ffff7bd3a5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x000000000192027b in native_cond_timedwait (cond=0x7ffedc007c78, mutex=0x7ffedc007c30, abstime=0x7fffec5bbb90)
    at /mysqldata/percona-server-locks-detail-5.7.22/include/thr_cond.h:129
#2  0x00000000019205ea in safe_cond_timedwait (cond=0x7ffedc007c78, mp=0x7ffedc007c08, abstime=0x7fffec5bbb90, 
    file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", line=1899) at /mysqldata/percona-server-locks-detail-5.7.22/mysys/thr_cond.c:88
#3  0x00000000014b9f21 in my_cond_timedwait (cond=0x7ffedc007c78, mp=0x7ffedc007c08, abstime=0x7fffec5bbb90, 
    file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", line=1899) at /mysqldata/percona-server-locks-detail-5.7.22/include/thr_cond.h:180
#4  0x00000000014ba484 in inline_mysql_cond_timedwait (that=0x7ffedc007c78, mutex=0x7ffedc007c08, abstime=0x7fffec5bbb90, 
    src_file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", src_line=1899)
    at /mysqldata/percona-server-locks-detail-5.7.22/include/mysql/psi/mysql_thread.h:1229
#5  0x00000000014bb702 in MDL_wait::timed_wait (this=0x7ffedc007c08, owner=0x7ffedc007b70, abs_timeout=0x7fffec5bbb90, set_status_on_timeout=true, 
    wait_state_name=0x2d897b0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1899
#6  0x00000000016cdb30 in TABLE_SHARE::wait_for_old_version (this=0x7ffee0a4fc30, thd=0x7ffedc007b70, abstime=0x7fffec5bbb90, deadlock_weight=100)
    at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:4717
#7  0x000000000153829b in close_cached_tables (thd=0x7ffedc007b70, tables=0x0, wait_for_refresh=true, timeout=31536000)
    at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1291
#8  0x00000000016123ec in reload_acl_and_cache (thd=0x7ffedc007b70, options=16388, tables=0x0, write_to_binlog=0x7fffec5bc9dc)
    at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_reload.cc:224
#9  0x00000000015cee9c in mysql_execute_command (thd=0x7ffedc007b70, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:4433
#10 0x00000000015d2fde in mysql_parse (thd=0x7ffedc007b70, parser_state=0x7fffec5bd600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#11 0x00000000015c6b72 in dispatch_command (thd=0x7ffedc007b70, com_data=0x7fffec5bdd70, command=COM_QUERY)
    at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
           

(3)殺點FTWRL會話後其他select操作等待棧幀:

#0  MDL_wait::timed_wait (this=0x7ffee8008298, owner=0x7ffee8008200, abs_timeout=0x7fffec58a600, set_status_on_timeout=true, wait_state_name=0x2d897b0)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1888
#1  0x00000000016cdb30 in TABLE_SHARE::wait_for_old_version (this=0x7ffee0011620, thd=0x7ffee8008200, abstime=0x7fffec58a600, deadlock_weight=0)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:4717
#2  0x000000000153b6ba in tdc_wait_for_old_version (thd=0x7ffee8008200, db=0x7ffee80014a0 "testmts", table_name=0x7ffee80014a8 "tii", wait_timeout=31536000, 
   deadlock_weight=0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:2957
#3  0x000000000153ca97 in open_table (thd=0x7ffee8008200, table_list=0x7ffee8001708, ot_ctx=0x7fffec58aab0)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:3548
#4  0x000000000153f904 in open_and_process_table (thd=0x7ffee8008200, lex=0x7ffee800a830, tables=0x7ffee8001708, counter=0x7ffee800a8f0, flags=0, 
   prelocking_strategy=0x7fffec58abe0, has_prelocking_list=false, ot_ctx=0x7fffec58aab0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:5213
#5  0x0000000001540a58 in open_tables (thd=0x7ffee8008200, start=0x7fffec58aba0, counter=0x7ffee800a8f0, flags=0, prelocking_strategy=0x7fffec58abe0)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:5831
#6  0x0000000001541e93 in open_tables_for_query (thd=0x7ffee8008200, tables=0x7ffee8001708, flags=0)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:6606
#7  0x00000000015d1dca in execute_sqlcom_select (thd=0x7ffee8008200, all_tables=0x7ffee8001708) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5416
#8  0x00000000015ca380 in mysql_execute_command (thd=0x7ffee8008200, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:2939
#9  0x00000000015d2fde in mysql_parse (thd=0x7ffee8008200, parser_state=0x7fffec58c600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#10 0x00000000015c6b72 in dispatch_command (thd=0x7ffee8008200, com_data=0x7fffec58cd70, command=COM_QUERY)
   at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490           

(4)占用者釋放喚醒FTWRL棧幀:

Breakpoint 3, MDL_wait::set_status (this=0x7ffedc000c78, status_arg=MDL_wait::GRANTED) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1832
1832      bool was_occupied= TRUE;
(gdb) bt
#0  MDL_wait::set_status (this=0x7ffedc000c78, status_arg=MDL_wait::GRANTED) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1832
#1  0x00000000016c2483 in free_table_share (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:607
#2  0x0000000001536a22 in table_def_free_entry (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:524
#3  0x00000000018fd7aa in my_hash_delete (hash=0x2e4cfe0, record=0x7ffee0011620 "\002") at /mysqldata/percona-server-locks-detail-5.7.22/mysys/hash.c:625
#4  0x0000000001537673 in release_table_share (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:949
#5  0x00000000016cad10 in closefrm (table=0x7ffee000f280, free_share=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:3597
#6  0x0000000001537d0e in intern_close_table (table=0x7ffee000f280) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1109
#7  0x0000000001539054 in close_thread_table (thd=0x7ffee0000c00, table_ptr=0x7ffee0000c68) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1780
#8  0x00000000015385fe in close_open_tables (thd=0x7ffee0000c00) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1443
#9  0x0000000001538d4a in close_thread_tables (thd=0x7ffee0000c00) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1722
#10 0x00000000015d19bc in mysql_execute_command (thd=0x7ffee0000c00, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5307
#11 0x00000000015d2fde in mysql_parse (thd=0x7ffee0000c00, parser_state=0x7fffec5ee600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#12 0x00000000015c6b72 in dispatch_command (thd=0x7ffee0000c00, com_data=0x7fffec5eed70, command=COM_QUERY)
    at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
           

作者微信:gp_22389860