我們在數據庫建表時,經常會困擾某個字段應該選擇什么數據類型,以及填寫什么長度。選擇數據類型方面一般不會有什么大問題,但是在填寫對應的長度的時候,很多人就會困擾,對應長度填寫的數字到底是什么含義,以及會影響到哪些東西。筆者在翻閱網上的相關文章時,發現一大半文章寫的都是錯的,主要的問題在於搞混了“字符”和“字節”這兩者的含義,甚至有的人覺得這就是一回事。如果對字符和字節不理解的讀者,可以先閱讀《一文搞懂字符和字節的含義》。本文我們通過實例來介紹MySQL的數據類型中長度的含義,讀完本文能夠讓你在數據庫建表的時候不再困惑。
字符串類型
常用的字符串類型的數據類型有 CHAR 和 VARCHAR 兩種,兩者后面都需要跟上一個數字表示長度,例如
CHAR(10)
VARCHAR(10)
CHAR(n) 和 VARCHAR(n) 兩者中的 n 含義均為該字段最大可容納的字符數。(注意早期的版本中,n指的是字節數,你也不需要關注是哪些版本,因為是十多年前的版本了,估計一般人也用不到)。
占用空間
- CHAR(n) 和 VARCHAR(n) 字段值的占用空間不是固定的,而是由實際存入的內容決定的,但在細節上兩者有一些不同。我們均以 n=4 為例。
對於 CHAR(4) 表示固定容納4個字符,當少於4個字符時,會使用空格填充空缺的部分,使其達到4個字符。如果超過4個字符,會自動截斷超出部分。例如你存入數據為 'ab' ,實際會存入 'ab ' (ab后有2個空格),因此占用4個字節。以下以幾個案例作為演示:
- 'a啊b' —— 字符數為3,少1個用空格補齊,因此實際存入 'a啊b ' ,字符數:4,字節數:1+3+1+1=6
- 'a啊b哈ccccccccc' —— 字符數超出4,僅保留前4個字符,因此實際存入 'a啊b哈' ,字符數:4,字節數:1+3+1+3=8
- 'a啊和哈' —— 字符數剛好為4,不需要截斷和補齊,因此實際存入 'a啊和哈' ,字符數:4,字節數:1+3+3+3=10
對於 CHAR 字段,你在使用 CHAR_LENGTH() 和 LENGTH() 函數查詢時,會發現和以上描述的情況不一致,我們放上代碼演示:
(備注: CHAR_LENGTH() 函數返回字符串的字符數, LENGTH() 函數返回字符串的字節數)
-- 假定已存在表 tb ,其中包含字段 s_char 的數據類型定義為 CHAR(4) ,我們先進行插入操作,獲取插入行id=1
INSERT INTO `tb`(`s_char`) VALUES ('啊a');
-- 接下去查詢該行 SELECT s_char, CHAR_LENGTH(s_char), LENGTH(s_char) FROM `tb` WHERE id=1;
-- 結果為:s_char=>'啊a',CHAR_LENGTH(s_char)=>2,LENGTH(s_char)=>4
你會發現以上結果跟預想中的不一致,按照一般理解預期存入 '啊a' ,僅為2個字符,需補充2個空格,實際存入為 '啊a ' ,因此字符數為4,字節數為 3+1+1+1=6 。
這里造成偏差的原因並不是錯誤,而是 CHAR 字段在檢索輸出時,自動省略了右側的空格。我們來演示一遍完整的流程:
預期存入 '啊a' ,少於4個字符,補充2個空格,因此實際存入的值為 '啊a ' ,該值字符數為4,字節數為6。在檢索時,原值為 '啊a ' ,輸出時自動省略右側空格,實際輸出為 '啊a' ,該字符串字符數為2,字節數為4。
下面再來說說 VARCHAR 類型,依然以 n=4 為例。區別於 CHAR 類型的補空, VARCHAR 類型對於未達到 n 字符的情況不會補空。
關於計算 VARCHAR 類型字符串的占用空間,有一點需要說明的是, VARCHAR 類型字符串的占用空間實際上包含2部分,一是存儲數據本身占用的空間,二是描述數據的元數據占用的空間,例如 VARCHAR 類型會使用1個字節記錄存入數據實際的字符數。下述描述的“占用空間”特指前者,即存儲數據本身占用的空間,不包含描述數據的元數據占用的空間。(其他數據類型等同)
以下以幾個案例作為演示:
(1) 'a啊b' —— 字符數為3,不補空,實際存入為 'a啊b' ,字符數為3,字節數為 1+3+1=5 。
(2)'a啊b哈ccccccccc' —— 字符數超出4,僅保留前4個字符,因此實際存入 'a啊b哈' ,字符數:4,字節數:1+3+1+3=8 。這種情況和 CHAR 類型處理一致。
(3)'a啊和哈' —— 字符數剛好為4,不需要截斷和補齊,因此實際存入 'a啊和哈' ,字符數:4,字節數:1+3+3+3=10
整數類型
常用的整數數據類型有 tinyint ,smallint ,mediumint , int ,bigint 共計5種,在聲明列時,后面也可以跟上 n ,例如 int(n) 。實際上這里的 n 非常雞肋,幾乎沒有任何使用場景。它的含義是“顯示位寬”,這個 n 無論填任何數,不影響存儲環節,僅影響在檢索時的輸出格式,而且在非常嚴格的情況下才成立。我們描述一種應用場景:我們聲明某列(列名取int_5)為 int(5) ,在聲明列的時候,要使用到該特性,必須加上 zerofill (填充0)屬性,即語句為
`int_5` int(5) unsigned zerofill DEFAULT NULL
-- 備注:加zerofill必須同時加unsigned
當插入的數字小於5位時,在特定客戶端檢索輸出時,會在數字前“補0”,湊足5位數字。(大於5位則原數字原樣顯示)例如存儲的數字是123,那么輸出00123 。說它雞肋,主要有以下幾個原因:
(1)對存儲環節沒有任何幫助,僅改變輸出顯示環節。而“格式化顯示”一般在前端或者后端的應用層操作就可以了,無需在數據庫中輸出時操作。
(2)格式化方式僅僅只有“補0”一種方式。
(3)僅針對特定客戶端輸出時才有顯示效果,目前僅發現使用MySQL Shell才有顯示效果,其他客戶端連接時均無。
由於以上原因,所以幾乎沒有開發者會使用這個特性。
占用空間這5種整型的占用空間是固定的,均與其后設置的 n 無關,例如設置字段類型為 int ,則無論 n 設置什么,它占用的空間就是4個字節。這5種整型的占用空間分別是: tinyint :1個字節,smallint :2個字節,mediumint :3個字節,int :4個字節,bigint :8個字節。
很多人說經常記不住他們的取值范圍,實際上很好算,例如 tinyint 占用1個字節,也就是8位,每1位都包含0和1兩種情況,因此共2的8次方為256種情況,如果是無符號(unsigned),取值范圍就是0至255。如果是有符號情況,由於第1位要用來表示符號,因此可用7位表示數字,2的7次方為128,再加上符號,取值范圍為 -128至127 。其它幾種數據類型也可以按照這個方法計算。
怕有的人還是難以理解,這里再重復一遍,以 int 為例,無論 int(n) 中的 n 設置什么值,無論插入的這個值或大或小,只要在取值范圍內,那這個字段就是占用4個字節。
另外再補充一點,當插入的值,超出取值范圍的時候,MySQL並不會報錯,而是自動變成成在取值范圍內最接近該值的邊界值。例如字段為 tinyint ,有符號型時取值范圍 -128至127 ,當你輸入-222時,不會報錯,會自動存入最接近-222的-128,當你輸入222時,會自動存入127。這一點需要尤其注意,否則很容易造成巨大的bug。
浮點型
FLOAT 類型固定占用4個字節, DOUBLE 類型固定占用8個字節,邏輯和上述的整型類似,不再贅述。
下面我們來說說 DECIMAL 類型,它的定義方式是 DECIMAL(M,D) ,其中 M 表示最大位數,D 表示小數點右側的位數。這里的“位”不是二進制的比特位,而是指十進制的數字的位數。
例如我們定義 DECIMAL(5,2) ,則表示最大位數為5位,小數點后2位,因此小數點前還剩下3位,於是取值范圍為 -999.99至999.99 。可以這樣理解:M-D 的值為小數點前的位數,D 的值為小數點后的位數,要算取值范圍則各個位置填充9,取正負范圍。那么容易計算 DECIMAL(5,1) 的取值范圍是 -9999.9至9999.9 ; DECIMAL(4,2) 的取值范圍是 -99.99至99.99 。
占用空間
DECIMAL(M,D) 的存儲方式和其他數字類型都完全不同,它是以字符串形式進行存儲的。這可能有點不好理解,以整型 tinyint 為例,它存儲的值是直接為十進制到二進制的轉換,以無符號型為例,當需要存入的值為100值,將100轉化為二進制為1100100 ,使用1個字節即8位記錄,實際存入的是 01100100 。但是用 DECIMAL 類型存儲時,比如定義 DECIMAL(3,0) ,存入100時,實際存入的是由字符“1”,“0”,“0”拼接而成的字符串“100”的二進制值,存入時占用3個字節,分別是31,30,30(注意這是十六進制)。
1個數字字符占用1個字節,因此定義為 DECIMAL(M,D) 占用 M 個字節。
(同上所述,M個字節為數據本身的占用空間,另外描述該數據的元數據還固定占用2個字節的空間)。需要注意的是, DECIMAL 類型在存儲時有補0操作。小數點前不足,向更高位補0,小數點后不足,向更低位補0。以 DECIMAL(5,2) 為例,如果准備存入9.5,小數點前應為3位,缺2位,小數點后應為2位,缺1位,各補0后,實際存入 '009.50' ,轉化為十六進制為30 30 39 2E 35 30 。但是在檢索輸出時,小數點前的0一般會省略,而小數點后的0會保留,這一點也需要注意。以上就是最長使用的3種數據類型的長度含義以及其占用空間,理解了以上概念,在使用MySQL時,將會更得心應手。