記一次字串末尾空白丟失的排查 → MySQL 是會玩的!

2023-06-25 12:02:12

開心一刻

  今天答應準時回家和老婆一起吃晚飯,但臨時有事加了會班,回家晚了點

  回到家,本以為老婆會很生氣,但老婆卻立即從廚房端出了熱著的飯菜

  老婆:還沒吃飯吧,去洗下,來吃飯吧

  我洗好,坐下吃飯,內心感動十分;老婆坐旁邊深情的看著我

  老婆:你知道誰最愛你嗎

  我毫不猶豫道:你

  老婆:誰最關心你?

  我:你

  老婆:我是誰呀?

  我:我老婆

  老婆:那你以後是不是得對我好點?

  這時電話響了,一看好哥們打過來的,我接了並開了擴音

  哥們:樓下洗浴八折,幹啥呢?

  我:那個......,在陪我前妻吃口飯

問題背景

  一天,小夥伴找到我,他說他碰到一個很奇怪的問題

  他說:明明表名的入參是 test  ,為什麼展示到介面的記錄包括 test 這條記錄?

  他補充道:會不會是 MyBatis-Plus 做了什麼騷操作,把 test  末尾的空格給拿掉了

  我:你直接把 SQL 語句到 MySQL 執行下試試

  結果如下:

  這看起來不夠直觀,我移動下游標

  然後我和小夥伴面面相覷

  我們來看下預設情況下,末尾空白的判斷情況

   MySQL 5.7.36 如下

  1 表示 TRUE ,也就是相等

   MySQL 8.0.27 如下

  0 表示 FALSE ,表示不相等

  這是什麼原因,我們繼續往下看

字元集與字元序

  比較肯定就需要比較規則, SQL 的比較規則就離不開字元序,字元序又與字元集相關,所以我們一個一個來捋

  字元集

  關於字元集,不是隻言片語可以說清楚的,但是大家也不用擔心,網上相關資料已經非常多,大家擦亮慧眼去查閱即可

  簡單點來說:字元集定義了字元和字元的編碼

  有人又問了:字元、字元的編碼又是什麼?

   Charset :字元集名

   Description :描述

   Default collation :預設字元序

   Maxlen :每個字元最多位元組數

  字元序

  定義了字元的比較規則;字元間的比較按何種規則進行

  一個字元集對應多個字元序,通過 SHOW COLLATION; 可以檢視全部的字元序;也可以帶條件查具體某個字元集的字元序

   Default 等於 Yes 表示是預設字元序

  每個字元集都有預設的字元序

  server的字元集與字元序

  當我們建立資料庫時,沒有指定字元集、字元序,那麼server字元集、server字元序就會作為該資料庫的字元集、字元序

  database的字元集與字元序

  指定資料庫級別的字元集、字元序

  同一個MySQL服務下的資料庫,可以分別指定不同的字元集、字元序

  建立、修改資料庫的時候,可以通過 CHARACTER SET 、  COLLATE  指定資料庫的字元集、字元序

  可以通過

  檢視資料庫的字元集和字元序

  table的字元集與字元序

  建立、修改表的時候,可以通過 CHARACTER SET 、 COLLATE 指定表的字元集、字元序

  可以通過

  檢視表的字元序

  column的字元集與字元序

  型別為 CHAR 、 VARCHAR 、 TEXT 的列,可以指定字元集、字元序

  可以通過

  檢視欄位的字元集和字元序

  多個維度指定字元集、字元序的話,粒度越細的優先順序越高( column > table > database > server )

  如果細粒度未指定字元集、字元序,那麼會繼承上一級的字元集,字元序則是上一級字元集的預設字元序

  通常情況下我們一般不會指定 table 、 column 粒度的字元集、字元序

  也就是說,通常情況下 column 的字元集會與 database 的字元集一致,而 column 的字元序則是 database 字元集的預設字元序

空白丟失

  上面講了那麼多,跟空白丟失有什麼關係?

  大家先莫急,繼續往下看

   MySQL5.7 The CHAR and VARCHAR Types中有這麼一段

  翻譯過來就是:

    1、型別是 CHAR 、 VARCHAR 、 TEXT 列的值,會根據列的字元序來比較和排序

    2、所有 MySQL 排序規則的型別都是 PAD SPACE 。這就意味著, CHAR 、 VARCHAR 、 TEXT 型別的值進行比較時,不用考慮任何末尾空格,LIKE 除外

    3、不受 SQL mode 影響,也就是說不管是嚴格模式,還是非嚴格模式,都不影響 2 所說的規則

  劃重點,記筆記:在 MySQL5.7 及以下( <=5.7 )版本中,排序規則都是 PAD SPACE ,末尾的空格會忽略不考慮

  那如何讓末尾空格參與比較了,有三種處理方式

  1、 BINARY ,類似 SELECT 'test' = BINARY 'test '; 

  2、 LIKE ,類似 SELECT 'test' LIKE 'test '; 

  3、 LENGTH 函數,類似

   MySQL8 做了調整,The CHAR and VARCHAR Types 有如下說明

  翻譯過來就是:

    1、型別是 CHAR 、 VARCHAR 、 TEXT 列的值,會根據列的字元序來比較和排序

    2、 MySQL 字元序的 pad 引數的可選值,除了 PAD SPACE ,還增加了 NO PAD 

    3、對於非二進位制字串( CHAR 、 VARCHAR 、 TEXT ),字元序 pad 引數決定如何去處理字串末尾的空格

       NO PAD 不會忽略末尾空格,會將其當做其他字元一樣對待

       PAD SPACE 會忽略末尾空格, LIKE 除外

       SQL mode 不參與字串末尾空格的處理

   MySQL8 server 維度的字元集是 utf8mb4 ,對應的預設字元序是: utf8mb4_0900_ai_ci 

   Pad_attribute 的值是 NO PAD ,也就是不會忽略字串末尾的空格

  所以在 MySQL8 中, SELECT 'test' = 'test '; 預設情況下得到的結果是 0

總結

  1、非二進位制字串( CHAR 、 VARCHAR 、 TEXT )比較時,末尾空格的處理跟列的字元序有直接關係

  2、 MySQL5.7 及之前的版本,排序規則的型別都是 PAD SPACE ,會忽略字串末尾的空格, LIKE 除外

  3、 MySQL8 開始,字元序增加了一個引數 Pad_attribute ,該引數的值不同,對字串末尾空格的處理方式不同

     NO PAD :字串末尾的空格會和其他字元一樣,不會被忽略

     PAD SPACE :字串末尾的空格會被忽略, LIKE 除外

  4、如上針對的都是非二進位制字串的排序和比較,而不是儲存

參考

  The CHAR and VARCHAR Types

  The CHAR and VARCHAR Types

  再見亂碼:5分鐘讀懂MySQL字元集設定