mysql的varchar欄位最大長度真的是65535嗎?
來源:小白debug
在mysql建表sql裡,我們經常會有定義字串型別的需求。
CREATE TABLE `user` (
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '名字'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;
比方說user表裡的名字,就是個字串。mysql裡有兩個型別比較適合這個場景。
char和varchar。
宣告它們都需要在欄位邊上加個陣列,比如char(100)和varchar(100),這個100是指當前欄位能放的最大字元數。
char和varchar的區別在於,varchar雖然宣告瞭最大能放100個字元,但一開始不需要分配100個字元的空間,可以根據需要慢慢增加空間。而char一開始宣告是多少,就固定預留多少空間。
所以,varchar比起char更省空間,一般沒啥大事,大家都愛用varchar。
那問題來了,宣告varchar欄位時,它的最大長度是多少呢?
相信大家應該聽說過varchar欄位的最大長度是65535吧。
沒聽過也沒關係,你現在聽到了。
但實際上是這樣嗎?
我們來做個實驗。
varchar最大值是多少
我們直接拿65535來試一下。
很明顯報錯了。
報錯內容也說了, 由於列長度過大導致報錯,最長是16383。
把上面的65535改成 16383,確實是成功了。
哦?所以說varchar最大值是16383?
當然不是。
這其實還有好幾個因素影響這這個最大值。
不同字符集的影響
varchar裡放的是字串,而字串看起來可以是英文字母,也可以是數字或中文。但不管怎麼樣,都可以把這樣的中英文數字轉成二進位制的01串。
按照一定規則把符號和二進位制碼對應起來,這就是編碼。而把n多這種已經編碼的字元聚在一起,就是我們常說的字符集。
建表語句裡有個CHARSET,這裡填的是字符集。
不同的字符集要求使用的位元組個數也不同,我們可以透過 show charset;
看到mysql支援哪些字符集,以及這些字符集裡儲存一個字元所需的最大位元組數(Maxlen)。
我們嘗試下把建表sql語句裡的CHARSET改一改,比如改成utf8mb3。
我們再執行下,會發現,最大值又不一樣了。
並且,上面雖然提示max=21845,但要是真執行起來會發現還是報錯。在改為21844之後才成功。
不講武德。
再把字符集改為 latin1。會發現,最大值會是 65533。
這裡漸漸可以發現規律。
utf8mb4的maxlen=4,對應varchar最大長度=16383。4*16383 = 65532。
utf8mb3的maxlen=3,對應varchar最大長度=21844。3*21844 = 65532。
latin1的maxlen=1,對應varchar最大長度=65533。 1 * 65533 = 65533。
也就是說varchar邊上的長度代表的是這一列能放的最大字元數,而maxlen代表單個字元佔用的最大位元組數。相乘的結果很接近65535。說明65535是指的位元組數,而不是字元數。
也就是說varchar的最大長度,根據選擇的字符集的不同,會有區別。
總的來說接近於 65535 除以 字符集的maxlen。
但其實這樣還不夠嚴謹。還有其他影響因素。
是否可以為NULL的影響
上面的建表語句裡宣告瞭test欄位都是NOT NULL
,也就是非空,如果我們將這個改成可以為NULL,再用 CHARSET=latin1去試試。這時候就會發現,前面NOT NULL的時候最大能使用65533去建表,現在報錯了。
改成65532,就能成功了,也就是最長長度少了1個位元組。
這是因為一個欄位是否為NULL這件事情,是需要一個位元組去記錄下來的。
而當欄位為NOT NULL的時候,則可以省下這個位元組。
列數的影響
上面提到的情況都是在表裡只有一列時的結果,當我們表裡有更多的列時,我們會發現varchar的最大值還會有變化。比如同樣還是latin1字符集,我們再增加一列varchar型別,並且用的還是前面允許的最大值65533。
結果發現這次會失敗。
查了一下資料發現,原來65535是mysql單行的最大長度(不包含blob和text等型別的情況下)
mysql表裡單行中的所有列加起來(不考慮其他隱藏列和記錄頭資訊) ,佔用的最大長度是65535個位元組。
注意上面加粗的部分,加起來不超過65535。
比如如果還有int的列,那它佔用4個位元組,bigint佔用8個位元組,欄位越多,留給單個varchar列的空間就越少。
因此,前面提到的 varchar 的最大長度,接近於 65535 除以 字符集的maxlen,但前提是隻有一列not null 的varchar型別的欄位。
為什麼不是65535而是65533?
不過問題又來了,上面建表sql裡,不管是那種字符集,最後得到的字元數都約等於65533。
但資料庫單行最大值應該是65535。65535 - 65533 = 2 。這裡面還差了個2,為什麼呢?
這就要聊一下mysql單行裡資料到底是怎麼儲存的。
資料錶行儲存的格式
我們可以透過 show table status
命令,檢視到當前表格使用的行格式。
透過上面的 Row_format
欄位可以看到這個表用的是 Dynamic
行格式。
事實上,現在的mysql資料表一般都是採用Dynamic行記錄格式。
我們來看下Dynamic
行格式長什麼樣子。
Dynamic格式將行記錄分為兩部分,分為是行記錄的額外資訊和行記錄的真實資料。
行記錄的額外資訊:
變長欄位長度列表:指的是varchar,text,blob這種型別,它們屬於變長欄位,這裡表示的就是這些欄位的長度。
NULL值列表:用來記錄當前行裡哪些列是為null的。如果全部列都是not null的話,那就不需要有這個欄位。
記錄頭資訊:這是固定5個位元組,用來記錄一些特殊的資訊,比如這一行是否被刪了,這一行在這個16k的資料頁內是不是最小的,以及指向下一條記錄的指標之類的一些資訊,不需要太關注。
行記錄的真實資料:
裡面放的就是一行裡,每一列的真正內容。除了我們建表時裡涉及到的列以外,還有一些隱藏列。
比如Row_ID,這個是在建表是沒有宣告主鍵時,資料表自動會生成的隱藏主鍵。另外還有trx_id
欄位,用於記錄當前這一行資料行是被哪個事務修改的,和一個roll_pointer
欄位,這個欄位是用來指向當前這個資料行的上一個版本,透過這個欄位,可以為這行資料形成一條版本鏈,從而實現多版本併發控制(MVCC)。有沒有很眼熟,這個在之前寫的文章裡出現過。
所以我們回過頭來看我們建的表,當只有一列not null的 varchar欄位時,行記錄長下面這樣。
前面提到,行最大值65535位元組是不包含隱藏列和記錄頭資訊的,所以其實是指上圖中紅色的部分。
而最左邊的變長欄位長度列表中,為了表示varchar列的長度,佔用了兩個位元組,也就是16位,2的16次方,最大可以表示65535的長度,正好足夠用來表示varchar列當前的長度是65533。
所以65535 - 65533 = 2 。這裡面差的2,是用來存varchar欄位長度去了。
一個頁才16k,怎麼儲存65533(64k)資料?
之前的文章裡其實多次提到了mysql底層是以頁的形式去儲存資料的,而一個頁固定16k,而一個varchar欄位最大能放65533位元組資料,換算一下大概是64k,整整4個16k的頁。
這裡面是怎麼實現的?
對於這種情況,其實行資料裡針對這個超大的varchar欄位只儲存個20位元組的指標(實際上是個偏移量),這個指標會指向新的頁(off page),這些頁裡儲存的是實際的varchar欄位裡的65533位元組資料。這種由於欄位過長導致需要額外的頁來儲存資料的現象叫行溢位。
大於64k的字串該怎麼處理?
如果離譜點,資料量更大,比64k還大,這時候就不能繼續用varchar了,需要改用text和blob型別欄位。
而text和blob型別本身也是分TINY、MEDIUM,LONG三個檔位的,對應著不同的資料長度,最大到4G左右。
像下面這樣就可以將資料型別定義為LONGTEXT。
CREATE TABLE `test_max_length` (
`test` LONGTEXT NOT NULL COMMENT '測試長度欄位'
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
而他們的儲存方式也跟varchar的情況類似,只儲存20個位元組的指標,實際資料儲存在其他溢位頁裡。
以前我們查某一行資料,他們都在一個16k的資料頁裡,查詢時只要一次磁碟IO就能將這個資料頁讀取出來。
當一個資料庫裡某行資料裡有個特別大的字串時,我們如果還想把整行資料給讀出來,那我們還得把off page的資料給全部讀出來,這意味著更多的磁碟IO,效能就更差了。
為了規避這個問題,我們寫select sql的時候,如果發現某列欄位,是個特別長的字串時,能不讀它就儘量不加到select裡,這也是為什麼大家不建議使用select * from table
的原因。
blob和text的區別
一般來說,blob和text都可以用來放超長字串。但它們會有一點點區別。
我們知道字符集(charset)下還有個校對規則(collation)的概念,比如同樣是a,大寫A和小寫a能不能算作是一個字元,這會影響比較和排序,collation就是定義這個規則用的。
blob沒有字符集的概念,而text有。這意味如果用blob來存文字的話,就沒法用字符集的校對規則來排序和做比較。
還有一個區別,blob還能儲存二進位制資料,比如壓縮過的文字資料,圖片或者影片,別笑,雖然不合適,但我確實見過有人拿它來儲存影片。。。
總結
現在的mysql資料表一般採用Dynamic行記錄格式。它由行記錄的額外資訊和行記錄的真實資料組成。
mysql表裡單行中的所有列加起來(不考慮其他隱藏列和記錄頭資訊) ,佔用的最大長度是65535個位元組。
如果資料表裡只有一列 not null 的varchar欄位,它的最大長度,接近於 65535 除以 字符集的maxlen。
如果要存放大於64k的欄位資料,可以考慮使用longtext和longblob等型別。
mysql的資料頁大小是16k,為了儲存varchar或者text,blob這種長度可能大於16k的欄位,在Dynamic行格式中,會只保留20個位元組的指標,實際資料則放在其他溢位頁中。為了將它們讀取出來,會需要更多的磁碟IO。
blob和text很像,但blob沒有字符集的概念,並且還能存放二進位制的資料,比如圖片或影片,但實際上圖片和影片更推薦放在物件儲存(Object Storage Service,簡稱oss)中。
參考資料
《mysql技術內幕》
《從根兒理解mysql》
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2944653/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL 中 VARCHAR 最大長度及 CHAR 和 VARCHAR 的區別MySql
- mysql中varchar型別最大長度測試MySql型別
- MySQL8.0大表秒加欄位,是真的嗎?MySql
- MySQL VARCHAR型別欄位到底可以定義多長MySql型別
- MySQL的欄位數量以及長度限制MySql
- MYSQL單行長度不能超過 65535MySql
- MySQL動態修改varchar長度的方法MySql
- oracle 修改表欄位的長度Oracle
- mysql變長型別欄位varchar值更新變長或變短底層檔案儲存原理MySql型別
- 關於mysql設定varchar 欄位的預設值''和null的區別,以及varchar和char的區別MySqlNull
- Mysql varchar型別欄位為什麼經常定義為255MySql型別
- MySQL中修改一個資料庫下包含有某個相同欄位的所有表的欄位長度MySql資料庫
- [20180613]縮短欄位長度.txt
- Oracle 修改欄位型別和長度Oracle型別
- DM8 varchar型別長度型別
- [20210423]建立檢視以及欄位長度.txt
- MySQL 5.6使用pt-online-schema-change線上修改大表欄位長度MySql
- mongodb 取欄位最大值MongoDB
- IT真的是萬能的嗎?
- 欄位長度前後端是否都需要做限制?後端
- ORANCLE 資料已存在,修改欄位型別長度型別
- MySQL 預設排序真的是按主鍵來排序的嗎MySql排序
- Mysql真的有缺點嗎?MySql
- MySQL欄位的取值範圍MySql
- 動態規劃最大欄位和動態規劃
- MySQL 欄位約束MySql
- 面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?面試MySql
- MySQL欄位新增註釋,但不改變欄位的型別MySql型別
- [BUG反饋]username欄位的長度設定有點小問題。
- DDL操作導致欄位長度變更修復方案
- MySQL 中 JSON 欄位的使用技巧MySqlJSON
- MySQL中JSON欄位的使用技巧MySqlJSON
- MySQL-刪除欄位MySql
- MySQL 欄位擷取拼接MySql
- MySQL 大欄位問題MySql
- 要慎用mysql的enum欄位的原因MySql
- MySQL varchar詳解MySql
- String字串的最大長度是多少?字串