MySQL中VARCHAR最大長度是多少?CHAR和VARCHAR有哪些區別?

如果要了解MySQL其他類型長度,可以參考《MySQL字段長度、取值範圍、存儲開銷》

以我多年經驗來看,VARCHAR的最大長度、字符串類型選擇,用MySQL的人中十之七八是不清楚的。網上文章魚目混珠,以訛傳訛居多。
本文不止介紹了原理,還提供了案例手把手教你自己分析,徹底解決你的疑惑。

假設有個VARCHAR(64) CHARSET utf8mb4列,存儲了中國cn這個字符串。
那你猜一猜,MySQL存儲時用了多少字節?

  • A:4 Bytes
  • B:5 Bytes
  • C:8 Bytes
  • D:9 Bytes
  • E:10 Bytes
  • F:10.125 Bytes
  • G:11 Bytes
  • H:12 Bytes
  • I:12.125 Bytes
  • K:13 Bytes

正確答案是F和G。
有疑惑的話,建議你讀完本文。只需7~10分鐘,成長快樂輕輕鬆鬆。

本文內容適用於MySQL 5.5/5.6/5.7/8.x

VARCHAR的定義

VARCHAR是變長字符串。
考慮其變長原理中有較多要素,在具體分解前,有必要一起重溫下官方定義。

爲了便於理解,我用CHAR定長類型來對比介紹。先看兩個小例子:

  • VARCHAR(4),最多存儲4個字符,有幾個字符存儲幾個。存儲字節數 = 數據值的字節和 + 1字節(長度標識)
  • CHAR(4),最多存儲4個字符,不足4個尾部用空格填滿。存儲字節數 = 數據值的字節和 + 補位空格數

概括地說,VARCHARCHAR都是MySQL的字符串類型,存儲多個字符、可設置最大存儲的字符數,存儲開銷都與數據長度、字符集有關。是MySQL最常用的字符串類型。

CHARVARCHAR具體對比:

特性 CHAR VARCHAR
長度 定長,固定字符數
最大255個字符
數據長度不足聲明值時,在尾部自動填充空格
長度可變,可設置最大存儲字符數
最大不超過行大小(默認65535字節,注意是字節,下面會講原因)
前綴 1~2字節,看列長度是否可能超過255字節
比如VARCHAR(100),字符集爲UTF8,則字節最大可能爲300字節,所以會使用2個字節標識長度
有否尾部空格 長度不足默認用空格填滿
檢索和獲取時會自動去除
不會自動填充空格輸入值就包含空格,則會存儲,檢索和獲取數據都會體現
超長處理 超長部分如果是空格自動截斷
如果是字符,嚴格模式下會報錯
超長部分如果是空格自動截斷,並生成警告
如果是字符,嚴格模式下會報錯
存儲開銷 數據值的字節和 + 補位空格數 數據值的字節和 + 長度標識字節數
  • 如果開啓PAD_CHAR_TO_FULL_LENGTH模式,檢索時尾部空格不會去除
  • CHAR超過255字符會報錯,提示使用TEXTBLOB
    ERROR 1074 (42000): Column length too big for column ''long_char'' 	(max = 255); use BLOB or TEXT instead
    

這是兩個類型的簡單介紹。

接下來我們具體介紹長度計算的要點:行大小、可空標識位、VARCHAR類型字節數計算。

VARCHAR的最大長度

VARCHAR的最大長度 = (最大行大小 - NULL標識列佔用字節數 - 長度標識字節數) / 字符集單字符最大字節數。有餘數時向下取整。

下面一步步演示如何計算出最大長度。

最大行大小

MySQL行默認最大65535字節,是所有列共享的,所以VARCHAR的最大值受此限制。

接下來我們通過實例驗證下。
創建一個65536VARCHAR(不指定字符集,默認latin1,每個字符只佔1字節):

mysql> create table test_varchar_length(v varchar(65536) not null);
ERROR 1074 (42000): Column length too big for column 'v' (max = 65535); use BLOB or TEXT instead

可以看到報錯了,提示我們行最大長度爲65535字節。
如果我們要插入一個非空的VARCHAR,其最大長度不能超過65535(行最大值) - 2(長度標識位) = 65533字節:

/** 測試邊界值65534,確認仍然過大 */
mysql> create table test_varchar_length(v varchar(65534) not null); 
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

/** 測試邊界值65533,創建成功,說明行最大值爲65535 */
mysql> create table test_varchar_length(v varchar(65533) not null); 
Query OK, 0 rows affected (0.02 sec)

/** 查看默認字符集,確認是latin1,每個字符只佔用1個字節 */
mysql> show create table test_varchar_length;
+----------------------+------------------------------------------------------------------------------------------------------------+
| Table                | Create Table                                                                                               |
+----------------------+------------------------------------------------------------------------------------------------------------+
| test_varchar_length | CREATE TABLE `test_varchar_length` (
  `v` varchar(65533) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+----------------------+------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

可空列標識位

COMPACTDYNAMIC行格式下,行大小除了數據列長度,還包括可空列標識,即NULL標識位。

  • 如果有一個列允許爲空,則需要1 bit來標識,每8 bits的標識會組成一個字段,該字段會存放在每行最開始的位置

    注意,這個標識位不是放在每列,而是每行共享。

  • 假設一張表中存在N個可空字段,NULL標識位需要N/8\lceil{N/8}\rceil (向上取整)個字節。此時整行可用於數據存儲的空間只有65535N/865535 - \lceil{N/8}\rceil個字節。

接下來通過試驗驗證下。
在行大小的例子中,我們知道最大可創建65533長度的非空VARCHAR列。現在要創建一個可空列,每行需要1 bitNULL標識位、MySQL會將其組裝成1 byte的字段存放,那麼我們應該可創建最大爲65533(最大非空VARCHAR列) - 1(NULL標識列)= 65532字節的可空VARCHAR列:

/** 刪除前面創建的表 */
mysql> drop table test_varchar_length;
Query OK, 0 rows affected (0.01 sec)

/** 測試邊界值65533,確認仍然過大 */
mysql> create table test_varchar_length(v varchar(65533));
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

/** 測試邊界值65532,創建成功,說明可空標識列確實佔去了1字節 */
mysql> create table test_varchar_length(v varchar(65532));
Query OK, 0 rows affected (0.03 sec)

計算VARCHAR的最大長度,可空標識位是最容易忽略的。

字符集的單字符最大字節數

字符集單字符最大字節數不難理解,列舉MySQL常見的三個字符集:

  • GBK:單字符最大可佔用2個字節。
  • UTF8:單字符最大可佔用3個字節。
  • UTF8MB4:單字符最大佔4個字節。
    假設還有6字節可以存放字符,按單字符佔用最大字節數來算,可以存放3個GBK、2個UTF8、1個UTF8MB4

VARCHAR的長度標識位

長度標識位是相對比較複雜的,網上的介紹錯的很多,也容易算錯。

其作用是記錄數據的字節數

存儲開銷是小於255只要1字節、大於255後使用兩字節。是因爲按照可能的數據大小,分爲0 - 255(28)、256 - 65535(216),剛好對應1字節和2字節。

但要注意,其計算根據的是字段聲明的字符長度、計算可能的字節數,再決定長度標誌的字節數。如VARCHAR(100),字符集爲UTF8,可能的字節數爲300,長度標識則爲2字節。這是網上介紹錯的最多的。

另外長度標誌位只是存儲開銷,不影響佔用聲明的字符長度。聲明的字符長度的是數據的字符數,允許的最大字符數與字符集有關。

VARCHAR(1)爲例,可以存1個字符,MySQL會額外找一個字節存放長度標識

樣例

公式應該都理解了:VARCHAR的最大長度 = (最大行大小 - NULL標識列佔用字節數 - 長度標識字節數) / 字符集單字符最大字節數。有餘數時向下取整。

接下來通過實驗來驗證,爲了便於理解計算,例子做了一些調整:

  • 不設置可空列、這樣可以去掉NULL標識列
  • 爲了便於體現長度標識位的差距,採用多個列的形式放大其存在
  • 爲了體現按可能字節數計算長度,這裏採用多字節的字符集GBK

創建一個表,包含2個非空VARCHAR(127),每個列可能的最大字節數爲254、長度標識位是1字節。那麼還可以添加最大65535 - 127*2*2(2個列,每個列最大佔127*2個字節)- 1*2(2個長度標識位) = 65023字節的非空VARCHAR列,約等於32511個字符:

mysql> drop table test_varchar_length;
Query OK, 0 rows affected (0.01 sec)
/** 測試邊界值32512,確認仍然過大 */
mysql> create table test_varchar_length(v1 varchar(127) not null,v2 varchar(127) not null,vm varchar(32512) not null) CHARSET=GBK;    
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

/** 測試邊界值32511,創建成功,說明兩個長度標識位共佔去了2字節 */
mysql> create table test_varchar_length(v1 varchar(127) not null,v2 varchar(127) not null,vm varchar(32511) not null) CHARSET=GBK; 
Query OK, 0 rows affected (0.02 sec)

接下來將兩個字段調大到128字符,每個列可能的最大字節數爲256、理論上長度標識位是2字節。那麼還可以添加最大65535 - 128*2*2(2個列,每個列最大佔127*2個字節)- 2*2(2個長度標識位) = 65017字節的非空VARCHAR列,約等於32508個字符:

mysql> drop table test_varchar_length;
Query OK, 0 rows affected (0.01 sec)
/** 測試邊界值32509,確認仍然過大 */
mysql> create table test_varchar_length(v1 varchar(128) not null,v2 varchar(128) not null,vm varchar(32509) not null) CHARSET=GBK; 
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

/** 測試邊界值32508,創建成功,說明兩個長度標識位共佔去了4字節 */
mysql> create table test_varchar_length(v1 varchar(128) not null,v2 varchar(128) not null,vm varchar(32508) not null) CHARSET=GBK; 
Query OK, 0 rows affected (0.02 sec)



恭喜你,能看到這裏的人估計不多,堅持下來的你已經得到了提升。
在這裏插入圖片描述

那麼再一起解下最初的問題:

  • UTF8MB4字符中,中文字符需要3個字節(大部分中文只需要3字節,4字節主要是emoji等輔助平面字符),那麼“中國cn”需要3+3+1+18個字節
  • VARCHAR(64) CHARSET utf8mb4字段,數據最大可能的字節數是64*4=256,所以需要 2個字節 作爲長度標識位;
  • 該字段是可以爲空的,那麼還需要NULL標識位,MySQL會生成一個 1字節NULL標識列來記錄;
  • 所以要存儲“中國cn”,列需要8 + 2個字節,還需要1字節作爲NULL標識列;因爲該列是多個列共享的,如果該表只有一個字段,那麼可以存儲開銷應該是11個字節,否則只能算作10.125字節(1/8等於0.125

所以答案是10.12511字節。

😂哈哈哈我花了4個小時,佔用了你10分鐘,但你不用像我這麼掉頭髮啦!摸着秀髮給我點個贊吧~~

同類文章推薦:


以上,感謝您的閱讀。

後續更新收集中:

  • 行格式解析
  • 其他引擎表現
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章