面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

帶你聊技術發表於2022-12-13

上週發了一篇位元組一面:MySQL 的 NULL 值是怎麼存放的?,文章裡面有提及這個問題:「varchar(n) 中 n 最大取值為多少?」

當時這部分內容寫的不夠嚴謹,所以我重寫了這部分內容。

所以,這次就聊聊這個問題。

前置知識

要回答這個問題,首先我們得先知道 MySQL 儲存一條記錄的格式長什麼樣子。

以  Compact 行格式作為例子,它長這樣:

面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

可以看到,一條完整的記錄分為「記錄的額外資訊」和「記錄的真實資料」兩個部分。

這裡重點講講記錄的額外資訊,它包含 3 個部分:變長欄位長度列表、NULL 值列表、記錄頭資訊。

  • 變長欄位長度列表

用於儲存一行記錄中每個變長欄位的長度。

「變長欄位長度列表」所佔用的位元組數 = 所有「變長欄位長度」佔用的位元組數之和。

舉個例子,假設資料庫表中有 2 個 varchar(10) 型別的欄位,分別為 a 和 b,且資料庫表的字符集為 ascii 字符集(1 個字元佔用 1 位元組)。

那麼a和b欄位的資料值的長度分別只需要用1位元組表示就行了,因為1位元組能表示最大的位元組數是 255,而 varchar(10) 型別的欄位最大允許儲存的位元組數是 10 位元組,所以只需要用 1 位元組表示變長欄位的長度就行。

那麼這種情況下的 「變長欄位長度列表」所佔用的位元組數 = 1 位元組 + 1位元組 = 2 位元組。

「變長欄位長度列表」不是必須的,如果資料庫表沒有變長欄位,比如欄位型別都是int,那麼行格式中就不需要「變長欄位長度列表」。

  • NULL 值列表

用於標記一行記錄中欄位值為 NULL 的欄位,二進位制位的值為 1 時,代表該欄位的值為NULL,二進位制位的值為 0 時,代表該欄位的值不為 NULL。

另外,NULL 值列表必須用整數個位元組的位表示(1位元組8位),如果使用的二進位制位個數不足整數個位元組,則在位元組的高位補 0

如果表中允許為 NULL 值的記錄的個數小於等於 8 個,那麼 NULL 值列表就會用 1 位元組表示。

如果如果表中允許為 NULL 值的記錄的個數大於8 並且小於等於 16,那麼 NULL 值列表就會用 2 位元組表示,以此類推。

因此,如果表中有欄位允許為 NULL,那麼NULL 值列表」至少佔用 1 位元組空間。

NULL 值列表不是必須的,如果資料庫表中的欄位都定義成 NOT NULL,那麼行格式中就不需要NULL 值列表」。

  • 記錄頭資訊

記錄頭資訊中包含的內容很多,比如記錄的刪除標記位,指向下一條記錄的指標等等,不是本文問題的重點,所以我就不細講了。

varchar(n) 中 n 最大取值為多少?

我們要清楚一點,MySQL 規定除了 TEXT、BLOBs 這種大物件型別之外,其他所有的列(不包括隱藏列和記錄頭資訊)佔用的位元組長度加起來不能超過 65535 個位元組

也就是說,一行記錄除了 TEXT、BLOBs 型別的列,限制最大為 65535 位元組,注意是一行的總長度,不是一列

知道了這個前提之後,我們再來看看這個問題:「varchar(n) 中 n 最大取值為多少?」

varchar(n) 欄位型別的 n 代表的是最多儲存的字元數量,並不是位元組大小哦。

要算 varchar(n) 最大能允許儲存的位元組數,還要看資料庫表的字符集,因為字符集代表著,1個字元要佔用多少位元組。

比如 ascii 字符集, 1 個字元佔用 1 位元組,那麼  varchar(100) 意味著最大能允許儲存 100 位元組的資料。

單欄位的情況

前面我們知道了,一行記錄最大隻能儲存 65535 位元組的資料。

那假設資料庫表只有一個 varchar(n) 型別的列且字符集是 ascii,在這種情況下, varchar(n) 中 n 最大取值是 65535 嗎?

不著急說結論,我們先來做個實驗驗證一下。

我們定義一個 varchar(65535) 型別的欄位,字符集為 ascii 的資料庫表。

CREATE TABLE test ( 
`name` VARCHAR(65535)  NULL
ENGINE = InnoDB DEFAULT CHARACTER SET = ascii ROW_FORMAT = COMPACT;

看能不能成功建立一張表:

面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

結果顯示,建立失敗了。

從報錯資訊就可以知道一行資料的最大位元組數是 65535(不包含 TEXT、BLOBs 這種大物件型別),其中包含了 storage overhead

問題來了,這個 storage overhead 是什麼呢?其實就是「變長欄位長度列表」和 「NULL 值列表」。

也就是說一行資料的最大位元組數 65535,其實是包含「變長欄位長度列表」和 「NULL 值列表」所佔用的位元組數的

所以, 我們在算 varchar(n) 中 n 最大值時,需要減去 storage overhead  佔用的位元組數。

這是因為我們儲存欄位型別為 varchar(n)  的資料時,其實分成了三個部分來儲存:

  • 真實資料
  • 真實資料佔用的位元組數
  • NULL 標識,如果不允許為NULL,這部分不需要

本次案例中,「NULL 值列表」所佔用的位元組數是多少?

前面我建立表的時候,欄位是允許為 NULL 的,所以會用 1 位元組來表示「NULL 值列表」

本次案例中,「變長欄位長度列表」所佔用的位元組數是多少?

「變長欄位長度列表」所佔用的位元組數 = 所有「變長欄位長度」佔用的位元組數之和。

所以,我們要先知道每個變長欄位的「變長欄位長度」需要用多少位元組表示?具體情況分為:

  • 條件一:如果變長欄位允許儲存的最大位元組數小於等於 255 位元組,就會用 1 位元組表示「變長欄位長度」;
  • 條件二:如果變長欄位允許儲存的最大位元組數大於 255 位元組,就會用 2 位元組表示「變長欄位長度」;

我們這裡欄位型別是 varchar(65535) ,字符集是 ascii,所以代表著變長欄位允許儲存的最大位元組數是 65535,符合條件二,所以會用 2 位元組來表示「變長欄位長度」。

因為我們這個案例是隻有 1 個變長欄位,所以「變長欄位長度列表」= 1 個「變長欄位長度」佔用的位元組數,也就是 2 位元組

因為我們在算 varchar(n) 中 n 最大值時,需要減去 「變長欄位長度列表」和 「NULL 值列表」所佔用的位元組數的。

所以,在資料庫表只有一個 varchar(n)  欄位且字符集是 ascii 的情況下,varchar(n) 中 n 最大值 =  65535 - 2 - 1 = 65532

我們先來測試看看  varchar(65533)  是否可行?

面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

可以看到,還是不行,接下來看看 varchar(65532)  是否可行?

面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

可以看到,建立成功了。說明我們的推論是正確的,在算 varchar(n) 中 n 最大值時,需要減去 「變長欄位長度列表」和 「NULL 值列表」所佔用的位元組數的。

當然,我上面這個例子是針對字符集為 ascii 情況,如果採用的是 UTF-8,varchar(n)  最多能儲存的資料計算方式就不一樣了:

  • 在 UTF-8 字符集下,一個字串最多需要三個位元組,varchar(n) 的 n 最大取值就是 65532/3 = 21844。

上面所說的只是針對於一個欄位的計算方式。

多欄位的情況

如果有多個欄位的話,要保證所有欄位的長度 + 變長欄位位元組數列表所佔用的位元組數 + NULL值列表所佔用的位元組數 <= 65535

這裡舉個多欄位的情況的例子。

面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?


實驗結果:


面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?

總結

varchar(n) 中 n 最大取值為多少?

一行記錄最大能儲存 65535 位元組的資料,但是這個是包含「變長欄位位元組數列表所佔用的位元組數」和「NULL值列表所佔用的位元組數」。所以, 我們在算 varchar(n) 中 n 最大值時,需要減去這兩個列表所佔用的位元組數。

如果一張表只有一個 varchar(n)  欄位,且允許為 NULL,字符集為 ascii。varchar(n) 中 n 最大取值為 65532。

計算公式:65535 - 變長欄位位元組數列表所佔用的位元組數 - NULL值列表所佔用的位元組數 = 65535 - 2 - 1 = 65532。

如果有多個欄位的話,要保證所有欄位的長度 + 變長欄位位元組數列表所佔用的位元組數 + NULL值列表所佔用的位元組數 <= 65535。


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

相關文章