(MariaDB)MySQL數據類型詳解和存儲機制

1.1 數據類型概覽

數據類型算是一種字段約束,它限制每一個字段能存儲什麼樣的數據、能存儲多少數據、能存儲的格式等。MySQL/MariaDB大體有5類數據類型,分別是:整形、浮點型、字符串類型、日期時間型以及特殊的ENUM和SET類型。mysql

這5種數據類型的意義、限制和相關說明以下圖所示:sql

各數據類型佔用字節數,參見mariadb官方手冊數據庫

1.2 存儲機制和操做方式

數據類型之因此能限定字段的數據存儲長度,是由於在建立表時在內存中嚴格劃定了地址空間,地址空間的長度是多少就能存儲多少字節的數據。固然,這是一個很粗獷的概念,更具體的存儲方式見下面的描述。ui

數據類型限定範圍的方式有兩種:一是嚴格限定空間,劃分了多少空間就只能存儲多少數據,超出的數據將被切斷;二是使用額外的字節的bit位來標記某個地址空間的字節是否存儲了數據,存儲了就進行標記,不存儲就不標記。spa

1.2.1 整型的存儲方式

此處主要說明整型的存儲方式,至於浮點型數據類型的存儲方式要考慮的東西太多。code

對於整型數據類型來講,它嚴格限定空間,但它和字符不一樣,由於每一個已劃分的字節上的bit位上的0和1直接能夠計算出數值,因此它的範圍是根據bit位的數量值來計算的。一個字節有8個Bit位,這8個bit位能夠構成2^8=256個數值,同理2字節的共2^16=65536個數值,4字節的int佔用32bit,能夠表示的範圍爲0-2^32。也就是說,在0-255之間的數字都只佔用一個字節,256-65535之間的數字須要佔用兩個字節。blog

須要注意,在MySQL/mariadb中的整型數據類型可使用參數M,M是一個正整數,例如INT(M),tinyint(M)。這個M表示的是顯示長度,如int(4)表示在輸出時將顯示4位整數,若是實際值的位數小於顯示值寬度,則默認使用空格填充在左邊。而結果位數超出時將不影響顯示結果。通常該功能都會配合zerofill屬性用0代替空格填充,可是使用了zerofill後,該列就會自動變成無符號字段。例如:排序

CREATE TABLE test3(id INT(2) ZEROFILL NOT NULL); INSERT INTO test3 VALUES(1),(2),(11),(111); SELECT id FROM test3; +-----+ | id | +-----+ | 01 | | 02 | | 11 | | 111 | +-----+ 4 rows in set (0.00 sec) 

惟一須要注意的是,顯示寬度僅僅影響顯示效果,不影響存儲、比較、長度計算等等任何操做內存

1.2.2 字符類型的存儲方式

此處主要說明char和varchar的存儲方式以及區別。rem

char類型是常被稱爲"定長字符串類型",它嚴格限定空間長度,但它限定的是字符數,而非字節數,但之前老版本中限定的是字節數。所以char(M)嚴格存儲M個字符,不足部分使用空格補齊,超出M個字符的部分直接截斷。

因爲char類型有"短了就使用空格補足"的能力,所以爲了體現數據的真實性,在從地址空間中檢索數據時將自動刪除尾隨的空格部分。這正是char的一個特殊性,即便是咱們手動存儲的尾隨空格也會被認爲是自動補足的,因而在檢索時被刪除。也就是說在where語句中name='gaoxiaofang 'name='gaoxiaofang'的結果是同樣的。

例如:

create table test2(a char(4) charset utf8mb4);
insert into test2 values('恭喜你'),('恭喜你成功晉級'),('hello'),('he ');
select concat(a,'x') from test2;
+---------------+
| concat(a,'x') |
+---------------+
| 恭喜你x       |
| 恭喜你成x     |
| hellx         |
| hex           |
+---------------+
4 rows in set

從上面的結果能夠看到,char(4)只能存儲4個字符,並刪除尾隨空格。

varchar常被稱爲"變長字符串類型",它存儲數據時使用額外的字節的bit位來標記某個字節是否存儲了數據。每存儲一個字節(不是字符)佔用一個bit位進行記錄,所以一個額外的字節能夠標記共256個字節,2個額外的字節能夠標記65536個字節。但MySQL/mariadb限制了最大能存儲65536個字節。這表示,若是是單字節的字符,它最多能存儲65536個字符,若是是多字節字符,如UTF8的每一個字符佔用3個字節,它最多能存儲65536/3=21845個utf8字符。

所以,varchar(M)存儲時除了真實數據佔用空間長度,還要額外計算1或2個字節的Bit位長度,即對於單字節字符實際佔用的空間爲M+1M+2個字節,對於多字節字符(如3字節)實際佔用的空間爲M*3+1M*3+2個字節。

因爲varchar存儲時須要採用額外的bit位記錄每個字節,短了的數據不會自動使用補齊,所以顯式存儲的尾隨空格也會被存儲並在Bit位上進行標記,也就是說不會刪除尾隨空格。

和char(M)同樣,當指定varchar(2)時,只能存儲兩個字節的字符,若是超出了,則切斷。

關於char、varchar以及text字符串類型,它們在比較時不會考慮尾隨空格,但作like匹配或正則匹配時會考慮空格,由於匹配時字符是精確的例如:

create table test4(a char(4),b varchar(5));
insert into test4 values('ab ','ab ');
select a='ab ',b='ab ',a=b from test4;
+-----------+--------------+-----+
| a='ab   ' | b='ab      ' | a=b |
+-----------+--------------+-----+
|         1 |            1 |   1 |
+-----------+--------------+-----+
1 row in set select a like 'ab ' from test4;
+-------------------+
| a like 'ab      ' |
+-------------------+
|                 0 |
+-------------------+
1 row in set

最後須要說明的是,數值在存儲(或調入內存)時,以數值型方式存儲比字符型或日期時間類型更節省空間由於整數值存儲時是直接經過bit計算數值的,0-255之間的任意整數都只佔一個字節,256-65535之間的任意整數都佔2個字節,而佔用4個字節時即可以表明幾十億個整數之間的任意一個,這顯然比字符型存儲時每一個字符佔用一個字節節省空間的多。例如值"100"存儲爲字符型時佔用三個字節,而存儲爲數值型將只佔用一個字節。所以數據庫默認將不使用引號包圍的值看成數值型,若是明確要存儲爲字符型或日期時間型則應該使用引號包圍以免歧義。

1.2.3 日期時間型的存儲方式

日期時間型數據存儲時須要使用引號包圍,避免和數值類型的數據產生歧義。關於日期時間的輸入方式是很是寬鬆的,如下幾種方式都是被容許的:任意容許的分隔符,建議使用4位的年份。

20110101
2011-01-01 18:40:20
2011/01/01 18-40-20
20110101184020

1.2.4 ENUM數據類型

ENUM數據類型是枚舉型。定義方式爲ENUM('value1','value2','value3',...),在向該類型的字段中插入數據時只能插入value中的某一個或NULL,插入其餘值或空(即'')時都將截斷爲空數據。存儲時會忽略大小寫(將轉換爲ENUM中的字符),且會截斷尾隨空格

mysql> create table test6(id int auto_increment primary key,name char(20),gender enum('Male','f'));
mysql> insert into test6(name,gender) values('malongshuai','Male'),('gaoxiaofang','F'),('wugui','x'),('tuner',null),('woniu','');
Query OK, 5 rows affected
Records: 5  Duplicates: 0  Warnings: 2

mysql> show warnings;
+---------+------+---------------------------------------------+
| Level   | Code | Message                                     |
+---------+------+---------------------------------------------+
| Warning | 1265 | Data truncated for column 'gender' at row 3 |
| Warning | 1265 | Data truncated for column 'gender' at row 5 |
+---------+------+---------------------------------------------+
2 rows in set

mysql> select * from test6;
+----+-------------+--------+
| id | name        | gender |
+----+-------------+--------+
|  1 | malongshuai | Male   |
|  2 | gaoxiaofang | f      |
|  3 | wugui       |        |
|  4 | tuner       | NULL   |
|  5 | woniu       |        |
+----+-------------+--------+
5 rows in set

ENUM類型的數據存儲時是經過index數值進行存儲的,相比於字符串類型,它只須要1或2個字節進行存儲便可理論上,當value的數量少於256個時只需一個字節,超出256個但少於65536個時使用2個字節存儲。MySQL/MariaDB限制最多隻能存儲65536個value。固然,這是理論上的限制,實際存儲時要考慮的因素有不少,例如NULL也會佔用bit位,因此實際存儲時可能250個value就須要2個字節。

ENUM的每一個value都經過index號碼進行編號,不管是檢索仍是操做該字段時都會經過index的值來操做。value1的index=1,value2的index=2,依次類推。但須要注意有兩個特殊的index值:NULL值的index=NULL,空數據的index=0。

例如ENUM('a','b','c'),向該字段依次插入'','b','a','c',NULL,'xxx'時,因爲第一個和最後一個都會截斷爲空數據,因此它們的index爲0,插入的NULL的index爲NULL,插入的'b','a','c'的index值分別爲2,1,3。因此index號碼和值的對應關係爲:

index value
NULL NULL
0 ''
0 ''
1 'a'
2 'b'
3 'c'

使用ENUM的index進行數據檢索:

mysql> select * from test6 where gender=2;
+----+-------------+--------+
| id | name        | gender |
+----+-------------+--------+
|  2 | gaoxiaofang | f      |
+----+-------------+--------+
1 row in set

特別建議,不要使用ENUM存儲數值,由於不管是排序仍是檢索或其餘操做,都是根據index值做爲條件的,這很容易產生誤解。例如,下面是用ENUM存儲兩個數值,而後進行檢索和排序操做。

mysql> create table test7(id enum('3','1','2'));
mysql> insert into test7 values('1'),('2'),('3');

# 檢索時id=2,但結果查出來卻爲1,由於id=2的2是enum的index值,在enum中index=2的值爲1
mysql> select * from test7 where id=2;
+----+
| id |
+----+
| 1  |
+----+
1 row in set

# 按照id進行排序時,也是經過index大小進行排序的
mysql> select * from test7 order by id asc;
+----+
| id |
+----+
| 3  |
| 1  |
| 2  |
+----+
3 rows in set

所以,強烈建議不要在ENUM中存放數值,即便是浮點型數值也很容易出現歧義。

1.2.5 SET數據類型

對於SET類型,和enum相似,不區分大小寫,存儲時刪除尾隨空格,null也是有效值。但不一樣的是能夠組合多個給出的值。如set('a','b','c','d')能夠存儲'a,b','d,b'等,多個成員之間使用逗號隔開。因此,使用多個成員的時候,成員自己的值中不能出現逗號。若是要存儲的內容不在set列表中,則截斷爲空值。

SET數據類型佔用的空間大小和SET成員數量M有關,計算方式爲(M+7)/8取整。因此:

1-8個成員佔用1個字節;
9-16個成員佔用2個字節;
17-24個成員佔用3字節;
25-32個成員佔用4個字節;
33-64個成員佔用8字節。

MySQL/MariaDB限制最多隻能有64個成員。

存儲SET數據類型的數據時忽略重複成員並按照枚舉時的順序存儲。如set('b','b','a'),存儲'a,b,a','b,a,b'的結果都是'b,a'。

mysql> create table test8(a set('d','b','a'));
mysql> insert into test8 values('b,b,a'),('b,a,b'),('bab');
Query OK, 3 rows affected
Records: 3  Duplicates: 0  Warnings: 1

mysql> select * from test8;
+-----+
| a   |
+-----+
| b,a |
| b,a |
|     |
+-----+
3 rows in set

使用find_in_set(set_value,set_column_name)能夠檢索出包含指定set值set_value的行。例如檢索a字段中包含成員b的行:

mysql> select * from test8 where find_in_set('b',a);
+-----+
| a   |
+-----+
| b,a |
| b,a |
+-----+
2 rows in set

1.3 數據類型屬性:unsigned

unsigned屬性就是讓數值類型的數據變得無符號化。使用unsigned屬性將會改變數值數據類型的範圍,例如tinyint類型帶符號的範圍是-128到127,而使用unsigned時範圍將變成0到255。同時unsigned也會限制該列不能插入負數值。

create table t(a int unsigned,b int unsigned);
insert into t select 1,2;
insert into t select -1,-2;

上面的語句中,在執行第二條語句準備插入負數時將會報錯,提示超出範圍。

使用unsigned在某些狀況下確有其做用,例如通常的ID主鍵列不會容許使用負數,它至關於實現了一個check約束。可是使用unsigned有時候也會出現些不可預料的問題:在進行數值運算時若是獲得負數將會報錯。例如上面的表t中,字段a和b都是無符號的列,且有一行a=1,b=2。

mysql> select * from t;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set

此時若是計算a-b將會出錯,不只如此,只要是unsigned列參與計算並將獲得負數都會出錯。

mysql> select a-b from t;
1690 - BIGINT UNSIGNED value is out of range in '(`test`.`t`.`a` - `test`.`t`.`b`)'
mysql> select a-2 from t;
1690 - BIGINT UNSIGNED value is out of range in '(`test`.`t`.`a` - 2)'

若是計算結果不是負數時將沒有影響。

mysql> select 2-a,a*3 from t;
+-----+-----+
| 2-a | a*3 |
+-----+-----+
|   1 |   3 |
+-----+-----+
1 row in set

這並非MySQL/MariaDB中的bug,在C語言中的unsigned也同樣有相似的問題。這個問題在MySQL/MariaDB中設置set sql_mode='no_unsigned_subtraction'便可解決。

因此我的建議不要使用unsigned屬性修飾字段。

1.4 數據類型屬性:zerofill

zerofill修飾字段後,不足字段顯示部分將使用0來代替空格填充,啓用zerofill後將自動設置unsigned。zerofill通常只在設置了列的顯示寬度後一塊兒使用。關於列的顯示寬度在上文已經介紹過了。

mysql> create table t1(id int(4) zerofill);
mysql> select * from t1;
+-------+
| id    |
+-------+
|  0001 |
|  0002 |
|  0011 |
| 83838 |
+-------+
4 rows in set (0.00 sec)

zerofill只是修飾顯示結果,不會影響存儲的數據值。

相關文章
相關標籤/搜索