MySQL字符集不一致致使性能降低25%,你敢信?

故事是這樣的:mysql

我在對MySQL進行性能測試時,發現CPU使用率接近100%,其中80%us, 16%sys,3%wa,iostat發現磁盤iops2000如下,avgqu-sz不超過3,%util最高70%,看來瓶頸不在磁盤IO上面,而在CPU上。sys部分使用率有點高。ios

因而我果斷使用perf top查看,赫然排在前面的2個,是my_ismbchar_utf8mb4和my_charpos_mb。git

my_ismbchar_utf8mb4顧名思義,很明顯是與字符集相關的;my_charpos_mb暫時不清楚。github

 

 

經驗告訴我,這很不正常!一般來講,消耗CPU最多的應該是數據頁相關的操做纔對啊。sql

我快速打開MySQL internal文檔搜索,沒找到有價值的信息。session

 

哦,你想要知道這個故事的前情提要?抱歉,我剛剛只說了壓測,按照國際慣例,我這就貼出環境和版本信息:socket

硬件:8核16GB,200GB SSD,騰訊雲虛擬機
操做系統版本:CentOS release 6.9 (Final)
MySQL版本:5.7.28-log MySQL Community Server (GPL),二進制方式安裝
MySQL參數:innodb_buffer_pool_size = 10752M
          innodb_flush_log_at_trx_commit = 1
          sync_binlog = 1
          character-set-server = utf8mb4
sysbench版本:1.0.19
sysbench參數:sysbench /usr/share/sysbench/oltp_read_write.lua   --tables=3 --table-size=1000000  --mysql-password=*** --mysql-user=root --mysql-socket=/usr/local/mysql5.7.28/mysql.sock --threads=128 --time=1800 run 

server的字符集是utf8mb4,接下來檢查一下db和表的字符集吧:svg

 

 

 

 

 

 嗯嗯,看起來一切都是那麼的正常……函數

server, DB, table的字符集都一致,如今只剩下sysbench的嫌疑最大!工具

但是,要怎麼檢查sysbench已經鏈接到MySQL的那些會話的字符集設置呢?

個人sysbench命令沒有顯式地指定字符集;show processlist沒有character_set_client信息,information_schema庫和mysql庫裏面也沒有與character_set_client信息。

sysbench --help 也沒有字符集相關的選項和參數;https://github.com/akopytov/sysbench/blob/master/src/drivers/mysql/drv_mysql.c  sysbench源碼中也沒有字符集相關的設置。

看來,sysbench鏈接MySQL的字符集設置,應該默認是latin1,應該是這裏的字符集設置不一致致使的。

 

BUT,對於技術問題,我不能光靠猜想啊!我必定要刨根問底,查它個水落石出……

 

 

源碼:

吃CPU最多的是my_ismbchar_utf8mb4函數對吧?那就先到源碼中搜它:

在strings/ctype-utf8.c 中定義的:

static uint
my_ismbchar_utf8mb4(const CHARSET_INFO *cs, const char *b, const char *e)
{
  int res= my_valid_mbcharlen_utf8mb4(cs, (const uchar*)b, (const uchar*)e);
  return (res > 1) ? res : 0;
}

它自己沒有複雜的邏輯,只是調用了my_valid_mbcharlen_utf8mb4,而後對返回值res 進行判斷,若是>1,就返回res,不然返回0。

行,那我再看看my_valid_mbcharlen_utf8mb4吧,

static int
my_valid_mbcharlen_utf8mb4(const CHARSET_INFO *cs __attribute__((unused)),
                           const uchar *s, const uchar *e)
{
  uchar c;

  if (s >= e)
    return MY_CS_TOOSMALL;

  c= s[0];
  if (c < 0xf0)
    return my_valid_mbcharlen_utf8mb3(s, e);

  if (c < 0xf5)
  {
    if (s + 4 > e) /* We need 4 characters */
      return MY_CS_TOOSMALL4;

    /*
省略若干行……
    */

    if (!(IS_CONTINUATION_BYTE(s[1]) &&
          IS_CONTINUATION_BYTE(s[2]) &&
          IS_CONTINUATION_BYTE(s[3]) &&
          (c >= 0xf1 || s[1] >= 0x90) &&
          (c <= 0xf3 || s[1] <= 0x8F)))
      return MY_CS_ILSEQ;

    return 4;
  }

  return MY_CS_ILSEQ;
}

這個函數對輸入的字符進行比對,判斷是utf8mb3仍是utf8mb4。utf8mb3?之前沒據說過啊!上知乎一搜,原來還有這麼一段有趣的歷史 ☜

不過,僅僅看這個函數的代碼,是不會相信它竟然會吃掉7%以上的CPU的。我也不信!

好吧,先作個perf record看看:

#第1步,查看mysqld進程的pid
ps -ef | grep mysqld 
#第2步,將mysqld進程相關的cpu
-clock事件及調用堆棧記錄起來,默認保存在perf.data文件中 perf record -e cpu-clock -g -p 14345
#第3步,用perf script工具對perf.data進行解析 perf script
-i perf.data &> perf.unfold
#第4步,下載一個集漂亮、強大於一身的工具: git clone https:
//github.com/brendangregg/FlameGraph.git

#第5步:將perf.unfold中的符號進行摺疊 ./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
#第6步,生成火焰圖 .
/FlameGraph/flamegraph.pl perf.folded > perf.svg

效果就是這樣的↓  能夠看出,my_ismbchar_utf8mb4佔比確實最高,達到了7.47%

 

 

 

去跟蹤調用堆棧,能夠發現是在sql\sql_lex.cc中的get_text()函數中,調用了宏use_mb和my_ismbchar來檢查字符集。

這2個宏一樣都是調用ismbchar() - detects whether the given string is a multi-byte sequence。   utf8mb4中的mb,全稱就是multi-byte

static char *get_text(Lex_input_stream *lip, int pre_skip, int post_skip)
{
  uchar c,sep;
  uint found_escape=0;
  const CHARSET_INFO *cs= lip->m_thd->charset();

  lip->tok_bitmap= 0;
  sep= lip->yyGetLast();                        // String should end with this
  while (! lip->eof())
  {
    c= lip->yyGet();
    lip->tok_bitmap|= c;
    {
      int l;
      if (use_mb(cs) &&
          (l = my_ismbchar(cs,
                           lip->get_ptr() -1,
                           lip->get_end_of_query()))) {
        lip->skip_binary(l-1);
        continue;
      }
    }
    if (c == '\\' &&
        !(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES))
    {                    // Escaped character
      found_escape=1;
      if (lip->eof())
    return 0;
      lip->yySkip();
    }
// 省略若干行……
  }
  return 0;                    // unexpected end of query
}

 

 

 解決方法:

上面說了一大通,可能有點雲裏霧裏,抱歉哈,我能力有限,不能把它解釋得更通俗一些。

簡而言之,就是證實了確實是字符集不一致,致使MySQL在語法解析的時候,對每個用戶輸入的字符(MySQL關鍵字除外),都要進行若干次字符集檢查,因此纔會發生my_ismbchar_utf8mb4吃掉不少CPU資源這樣一個故事 。

要解決就很簡單啦:保持character_set_server  &&  database characterset  &&  table characterset  &&  Client characterset一致!

我就是由於忽略了sysbench的字符集設置,因此才把本身給坑了。

既然sysbench沒有提供字符集相關的選項和參數,那我就把MySQL的字符集統一成latin1來測吧(也能夠去修改sysbench的mysql driver源碼,讓它支持設置字符集,可是我不擅長C……)

 

 

最後總結:

調整字符集以前,QPS最高只能壓到73797,統一字符集以後,QPS達到了98272。  73797/98272*100%=75.09%

 

再來看看TPS,調整字符集以前,TPS最高只能壓到3689,統一字符集以後,TPS達到了3689。  73797/4913*100%=75.08%

 

 

 多麼痛的領悟……

相關文章
相關標籤/搜索