整型
先從最基本的數據類型整型說起,首先用一張表格歸納一下:
數據類型 | 字節數 | 帶符號最小值 | 帶符號最大值 | 不帶符號最小值 | 不帶符號最大值 |
TINYINT | 1 | -128 | 127 | 0 | 255 |
SMALLINT | 2 | -32768 | 32767 | 0 | 65535 |
MEDIUMINT | 3 | -8388608 | 8388607 | 0 | 16777215 |
INT | 4 | -2147483648 | 2147483647 | 0 | 4294967295 |
BIGINT | 8 | -9223372036854775808 | 9223372036854775807 | 0 | 18446744073709551616 |
即使是帶符號的BIGINT,其實也已經是一個天文數字了,什么概念,9223372036854775807我們隨便舉下例子:
- 以byte為例可以表示8589934592GB-->8388608TB-->8192PB
- 以毫秒為例可以表示292471208年
所以從實際開發的角度,我們一定要為合適的列選取合適的數據類型,即到底用不用得到這種數據類型?舉個例子:
- 一個枚舉字段明明只有0和1兩個枚舉值,選用TINYINT就足夠了,但在開發場景下卻使用了BIGINT,這就造成了資源浪費
- 簡單計算一下,假使該數據表中有100W數據,那么總共浪費了700W字節也就是6.7M左右,如果更多的表這么做了,那么浪費的更多
要知道,MySQL本質上是一個存儲,以Java為例,可以使用byte類型的地方使用了long類型問題不大,因為絕大多數的對象在程序中都是短命對象,方法執行完畢這塊內存區域就被釋放了,7個字節實際上不存在浪不浪費一說。但是MySQL作為一個存儲,8字節的BIGINT放那兒就放那兒了,占據的空間是實實在在的。
最后舉個例子:
1 drop table if exists test_tinyint; 2 create table test_tinyint ( 3 num tinyint 4 ) engine=innodb charset=utf8; 5 6 insert into test_tinyint values(-100); 7 insert into test_tinyint values(255);
執行第7行的代碼時候報錯"Out of range value for column 'num' at row 1",即很清楚的我們可以看到插入的數字范圍越界了,這也同樣反映出MySQL中整型默認是帶符號的。
把第3行的num字段定義改為"num tinyint unsigned"第7的插入就不會報錯了,但是第6行的插入-100又報錯了,因為無符號整型是無法表示負數的。
整型(N)形式
在開發中,我們會碰到有些定義整型的寫法是int(11),這種寫法從我個人開發的角度看我認為是沒有多大用,不過作為一個知識點做一下講解吧。
int(N)我們只需要記住兩點:
- 無論N等於多少,int永遠占4個字節
- N表示的是顯示寬度,不足的用0補足,超過的無視長度而直接顯示整個數字,但這要整型設置了unsigned zerofill才有效
下面舉個例子,寫一段SQL:
drop table if exists test_int_width; create table test_int_width ( a int(5), b int(5) unsigned, c int(5) unsigned zerofill, d int(8) unsigned zerofill ) engine=innodb charset=utf8; insert into test_int_width values(1, 1, 1, 1111111111); select * from test_int_width;
從上面的兩點,我們應該預期結果應該是1,1,00001,1111111111
我們看一下結果:
不符合預期是吧,因為這個問題我也有過困擾,后來查了一下貌似是Navicat工具本身的問題,我們使用控制台就不會有這個問題了:
不過實際工作場景中反正我是沒有碰到過指定zerofill的,也不知道具體應用場景,如果有使用這種寫法的朋友可以留言告知具體在哪種場景下用到了這種寫法。
浮點型
整型之后,下面是浮點型,在MySQL中浮點型有兩種,分別為float、double,它們三者用一張表格總結一下:
數據類型 | 字節數 | 備注 |
float | 4 | 單精度浮點型 |
double | 8 | 雙精度浮點型 |
下面還是用SQL來簡單看一下float和double型數據,以float為例,double同理:
drop table if exists test_float; create table test_float ( num float(5, 2) ) engine=innodb charset=utf8; insert into test_float values(1.233); insert into test_float values(1.237); insert into test_float values(10.233); insert into test_float values(100.233); insert into test_float values(1000.233); insert into test_float values(10000.233); insert into test_float values(100000.233); select * from test_float;
顯示結果為:
從這個結果我們總結一下float(M,D)、double(M、D)的用法規則:
- D表示浮點型數據小數點之后的精度,假如超過D位則四舍五入,即1.233四舍五入為1.23,1.237四舍五入為1.24
- M表示浮點型數據總共的位數,D=2則表示總共支持五位,即小數點前只支持三位數,所以我們並沒有看到1000.23、10000.233、100000.233這三條數據的插入,因為插入都報錯了
當我們不指定M、D的時候,會按照實際的精度來處理。
定點型
介紹完float、double兩種浮點型,我們介紹一下定點型的數據類型decimal類型,有了浮點型為什么我們還需要定點型?寫一段SQL看一下就明白了:
drop table if exists test_decimal; create table test_decimal ( float_num float(10, 2), double_num double(20, 2), decimal_num decimal(20, 2) ) engine=innodb charset=utf8; insert into test_decimal values(1234567.66, 1234567899000000.66, 1234567899000000.66); insert into test_decimal values(1234567.66, 12345678990000000.66, 12345678990000000.66);
運行結果為:
看到float、double類型存在精度丟失問題,即寫入數據庫的數據未必是插入數據庫的數據,而decimal無論寫入數據中的數據是多少,都不會存在精度丟失問題,這就是我們要引入decimal類型的原因,decimal類型常見於銀行系統、互聯網金融系統等對小數點后的數字比較敏感的系統中。
最后講一下decimal和float/double的區別,個人總結主要體現在兩點上:
- float/double在db中存儲的是近似值,而decimal則是以字符串形式進行保存的
- decimal(M,D)的規則和float/double相同,但區別在float/double在不指定M、D時默認按照實際精度來處理而decimal在不指定M、D時默認為decimal(10, 0)
日期類型
接着我們看一下MySQL中的日期類型,MySQL支持五種形式的日期類型:date、time、year、datetime、timestamp,用一張表格總結一下這五種日期類型:
數據類型 | 字節數 | 格式 | 備注 |
date | 3 | yyyy-MM-dd | 存儲日期值 |
time |
3 | HH:mm:ss | 存儲時分秒 |
year | 1 | yyyy | 存儲年 |
datetime | 8 | yyyy-MM-dd HH:mm:ss | 存儲日期+時間 |
timestamp | 4 | yyyy-MM-dd HH:mm:ss | 存儲日期+時間,可作時間戳 |
下面我們還是用SQL來驗證一下:
drop table if exists test_time; create table test_time ( date_value date, time_value time, year_value year, datetime_value datetime, timestamp_value timestamp ) engine=innodb charset=utf8; insert into test_time values(now(), now(), now(), now(), now());
看一下插入后的結果:
MySQL的時間類型的知識點比較簡單,這里重點關注一下datetime與timestamp兩種類型的區別:
- 上面列了,datetime占8個字節,timestamp占4個字節
- 由於大小的區別,datetime與timestamp能存儲的時間范圍也不同,datetime的存儲范圍為1000-01-01 00:00:00——9999-12-31 23:59:59,timestamp存儲的時間范圍為19700101080001——20380119111407
- datetime默認值為空,當插入的值為null時,該列的值就是null;timestamp默認值不為空,當插入的值為null的時候,mysql會取當前時間
- datetime存儲的時間與時區無關,timestamp存儲的時間及顯示的時間都依賴於當前時區
在實際工作中,一張表往往我們會有兩個默認字段,一個記錄創建時間而另一個記錄最新一次的更新時間,這種時候可以使用timestamp類型來實現:
create_time timestamp default current_timestamp comment "創建時間", update_time timestamp default current_timestamp on update current_timestamp comment "修改時間",
char和varchar類型
最后看一下常用到的字符型,說到MySQL字符型,我們最熟悉的應該就是char和varchar了,關於char和varchar的對比,我總結一下:
- char是固定長度字符串,其長度范圍為0~255且與編碼方式無關,無論字符實際長度是多少,都會按照指定長度存儲,不夠的用空格補足;varchar為可變長度字符串,在utf8編碼的數據庫中其長度范圍為0~21844
- char實際占用的字節數即存儲的字符所占用的字節數,varchar實際占用的字節數為存儲的字符+1或+2或+3
- MySQL處理char類型數據時會將結尾的所有空格處理掉而varchar類型數據則不會
關於第一點、第二點,稍后專門開一個篇幅解釋,關於第三點,寫一下SQL驗證一下:
drop table if exists test_string; create table test_string ( char_value char(5), varchar_value varchar(5) ) engine=innodb charset=utf8; insert into test_string values('a', 'a'); insert into test_string values(' a', ' a'); insert into test_string values('a ', 'a '); insert into test_string values(' a ', ' a ');
使用length函數來看一下結果:
驗證了我們的結論,char類型數據並不會取最后的空格。
varchar型數據占用空間大小及可容納最大字符串限制探究
接上一部分,我們這部分來探究一下varchar型數據實際占用空間大小是如何計算的以及最大可容納的字符串為多少,首先要給出一個結論:這部分和具體編碼方式有關,且MySQL版本我現在使用的是5.7,當然5.0之后的都是可以的。
先寫一段SQL創建表,utf8的編碼格式:
drop table if exists test_varchar; create table test_varchar ( varchar_value varchar(100000) ) engine=innodb charset=utf8;
執行報錯:
Column length too big for column 'varchar_value' (max = 21845); use BLOB or TEXT instead
按照提示,我們把大小改為21845,執行依然報錯:
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
改為21844就不會有問題,因此在utf8編碼下我們可以知道varchar(M),M最大=21844。那么gbk呢:
drop table if exists test_varchar; create table test_varchar ( varchar_value varchar(100000) ) engine=innodb charset=gbk;
同樣的報錯:
Column length too big for column 'varchar_value' (max = 32767); use BLOB or TEXT instead
把大小改為32766,也是和utf8編碼格式一樣的報錯:
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
可見gbk的編碼格式下,varchar(M)最大的M=32765,那么為什么會有這樣的區別呢,分點詳細解釋一下:
- MySQL要求一個行的定義長度不能超過65535即64K
- 對於未指定varchar字段not null的表,會有1個字節專門表示該字段是否為null
- varchar(M),當M范圍為0<=M<=255時會專門有一個字節記錄varchar型字符串長度,當M>255時會專門有兩個字節記錄varchar型字符串的長度,把這一點和上一點結合,那么65535個字節實際可用的為65535-3=65532個字節
- 所有英文無論其編碼方式,都占用1個字節,但對於gbk編碼,一個漢字占兩個字節,因此最大M=65532/2=32766;對於utf8編碼,一個漢字占3個字節,因此最大M=65532/3=21844,上面的結論都成立
- 舉一反三,對於utfmb4編碼方式,1個字符最大可能占4個字節,那么varchar(M),M最大為65532/4=16383,可以自己驗證一下
同樣的,上面是表中只有varchar型數據的情況,如果表中同時存在int、double、char這些數據,需要把這些數據所占據的空間減去,才能計算varchar(M)型數據M最大等於多少。
varchar、text和blob
最后講一講text和blob兩種數據類型,它們的設計初衷是為了存儲大數據使用的,因為之前說了,MySql單行最大數據量為64K。
先說一下text,text和varchar是一組既有區別又有聯系的數據類型,其聯系在於當varchar(M)的M大於某些數值時,varchar會自動轉為text:
- M>255時轉為tinytext
- M>500時轉為text
- M>20000時轉為mediumtext
所以過大的內容varchar和text沒有區別,同事varchar(M)和text的區別在於:
- 單行64K即65535字節的空間,varchar只能用63352/65533個字節,但是text可以65535個字節全部用起來
- text可以指定text(M),但是M無論等於多少都沒有影響
- text不允許有默認值,varchar允許有默認值
varchar和text兩種數據類型,使用建議是能用varchar就用varchar而不用text(存儲效率高),varchar(M)的M有長度限制,之前說過,如果大於限制,可以使用mediumtext(16M)或者longtext(4G)。
至於text和blob,簡單過一下就是text存儲的是字符串而blob存儲的是二進制字符串,簡單說blob是用於存儲例如圖片、音視頻這種文件的二進制數據的。