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

gaopengtttt發表於2019-09-18

本案例由 徐晨亮提供,並且一起探討。

本文中
FTWRL = “flush table with read lock”

關於常用操作加MDL LOCK鎖型別參考文章:
http://blog.itpub.net/7728585/viewspace-2143093/

歡迎關注我的《深入理解MySQL主從原理 32講 》,如下:

image.png


一、兩個不同的現象

首先建立一張有幾條資料的表就可以了,我這裡是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

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

SESSION1 SESSION2 SESSION3
步驟1:select sleep(1000) from baguait1
步驟2:flush table with read lock;堵塞
步驟3:kill session2
步驟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操作不會受到任何影響

五、案例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)被什麼堵塞
  • 長時間的DDL\DML\FOR 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會堵塞DDL\DML\FOR 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

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

相關文章