不少時候,咱們開發應用系統,底層的數據庫表結構都須要開發人員親自設計,設計的合理與否,關乎着整個系統的穩定性和運行速率,系統上線後再頻繁地對後端數據庫表結構進行修改更是大忌。因此咱們須要更多地儲備數據庫層面的知識和技能,以便最初就能設計好表結構。數據庫
本篇基於MySQL數據庫,儘量詳細地爲你們解析MySQL基本的數據類型和設計庫表結構時應注意的點。後端
MySQL整數類型有5個:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT緩存
整數類型 | 存儲空間(位) | 存儲值範圍 | 範圍計算公式 |
---|---|---|---|
TINYINT | 8(1字節) | -128 ~ 127 | ![]() |
SMALLINT | 16(2字節) | -32768 ~ 32767 | ![]() |
MEDIUMINT | 24(3字節) | -8388608 ~ 8388607 | ![]() |
INT | 32(4字節) | -2147483648 ~ 2147483647 | ![]() |
BIGINT | 64(8字節) | -(9.223372e+18) ~ 9.223372e+18 | ![]() |
範圍計算公式爲:bash
整數類型有可選的UNSIGNED
屬性:表示非負值,可以使正數的範圍擴大一倍,例如:TINYINT
的存儲範圍是:-128 ~ 127,TINYINT UNSIGNED
的存儲範圍是:0 ~ 255。服務器
【疑問】: 定義數據庫表字段爲整數類型,例如INT,那麼INT(1),INT(11),INT(20)...等等,有什麼意義和區別嗎?應該怎麼定義?數據結構
【解答】: 定義數據庫表字段時,爲整數類型指定寬度,例如INT(11),INT(20),對大多數應用沒有意義,不會限制所對應存儲值範圍, 對於存儲和計算來講,INT(11)和INT(20)是相同的,沒有區別。函數
INT(1),INT(11),INT(20)...存儲有符號範圍始終都是:-2147483648 ~ 2147483647,無符號範圍:0 ~ 4294967295
TINYINT(1),TINYINT(2),TINYINT(4)...存儲有符號範圍始終都是:-128 ~ 127,無符號範圍:0 ~ 255
複製代碼
那指定整數類型寬度用來幹什麼呢?如 ITN(4)工具
只是一些MySQL客戶端工具用來顯示字符的個數。
複製代碼
舉例:以下goods表結構 性能
若是num字段(或type字段)存儲的值的字符數超過了所定義的寬度4,那麼該值會顯示全嗎? ui
通常用法是:指定整數類型寬度的同時,定義該字段Zero Fill
屬性(意思是達不到指定寬度時用0填充)
那麼在設計數據庫表字段,並定義爲整數類型時,應注意:【重點,敲黑板】
- 通常不用指定整數類型寬度,如ITN(20),TINYINT(2)...等,除非設定
Zero Fill
屬性,用來填充字符個數。- 字段不會出現負值時,將其設定爲
unsigned
,能擴大正數的存儲範圍,同時也能免去負值帶來的干擾。- 整型比字符操做代價更低,由於字符集和校對規則(排序規則)使字符比較比整型比較更復雜。因此定義字段類型時,先考慮整型。
- 表的標識列最好選擇整數類型,由於很快而且可使用
AUTO_INCREMENT
(自增)。- 阿里巴巴Java開發手冊中,建表規約 - 第9條,對設計數據庫表強制要求以下:
![]()
實數是帶有小數部分的數字。
MySQL的實數類型有3個:FLOAT,DOUBLE,DECIMAL
對好比下:
實數類型 | 存儲空間(字節) | 數值計算 | 使用 |
---|---|---|---|
FLOAT | 4字節 | CPU支持原生浮點計算 所以使用PC標準的浮點運算進行近似計算 |
單精度,存小數時存在精度損失 |
DOUBLE | 8字節 | CPU支持原生浮點計算 所以使用PC標準的浮點運算進行近似計算 |
雙精度(比單的精度更高,範圍更大) 存小數時也存在精度損失 |
DECIMAL | 65個數字 (MySQL>=5.0版本) |
CPU不支持對DECIMAL直接計算 MySQL(>=5.0版本)服務器本身實現了DECIMAL的高精度計算 |
對小數進行精確計算時使用,如財務數據 |
說明:DECIMAL比DOUBLE,FLOAT存儲空間大,存儲值範圍大,精度高,可是他倆比DECIMAL計算更快(FLOAT/DOUBLE使用CPU原生浮點運算,DECIMAL使用MySQL(>=5.0版本)自身實現的DECIMAL高精度計算)。
那麼在設計數據庫表字段,並定義爲實數類型時,應注意:【重點,敲黑板】
- 儘可能只在只在對小數進行精確計算時,使用DECIMAL類型,例如財務數據。
- 只指定數據類型,不指定精度(MySQL中FLOAT/DOUBLE/DECIMAL均可以指定精度,但會影響列的空間消耗)。
- 使用DECIMAL存儲,但數據量很大(上千萬條或更多),能夠考慮使用BIGINT代替DECIMAL,把小數乘以相應倍數存在BIGINT中,這樣能夠同時避免:浮點存儲計算不精確和DECIMAL精確計算代價高的問題。
- 使用DECIMAL存儲,但存儲的數據範圍超過了DECIMAL的範圍,建議將數據拆成整數和小數分開存儲。
- 阿里Java開發手冊,建表規約 - 第6條,對設計數據庫表強制要求以下:
![]()
關於VARCHAR類型我會從如下三個方面詳細解析:
VARCHAR類型是最多見的字符串數據類型,用於存儲可變長字符串,通常比定長類型更節省空間,緣由:VARCHAR存儲時僅使用必要的空間,越短的字符串使用越少的空間。
定義庫表的字段類型時,VARCHAR(100)表示什麼?
VARCHAR(100)表示:能存儲100個字符,注意字符數是100個,無論你存儲的是漢字,符號,字母等等,它的總個數不能超過100個。
複製代碼
VARCHAR(100)中的 100 不表示存儲空間的大小,那麼存儲空間大小怎麼計算呢?
<1> 首先VARCHAR須要使用 1 或 2 個額外字節記錄字符串的長度,這裏的字符串長度是指佔用的空間大小,單位是字節。
若是存儲的數據最大長度(空間大小) <= 255字節,則只額外使用 1 個字節記錄長度(1個字節8位最大表示的無符號數就是255),
不然使用 2 個字節記錄。
<2> 存儲的數據是多大字節?這就跟咱們具體存什麼東西以及編碼相關了,好比:
utf-8編碼(小於5.5.3版本):
存漢字:一個漢字 = 3個字節
存英文:一個字母 = 1個字節
gbk編碼:
存漢字:一個漢字 = 2個字節
存英文:一個字母 = 1個字節
utf8bm4字符集(大於5.5.3版本,注意是字符集,後面詳細討論):
存普一般用漢字:一個漢字 = 3個字節 (普通漢字:包含在Basic Multiling Plane(BMP)字符中的漢字)
不經常使用漢字:一個漢字 = 4個字節
經常使用的表情字符emoji:一個表情字符emoji = 4個字節
存英文:一個字母 = 1個字節
複製代碼
關於MySQL的utf8和utf8bm4之間的愛恨情仇,我會用一遍文章爲你們儘量詳細的解析,請關注,這裏只簡單介紹:
MySQL在5.5.3版本以前使用uft8字符集,MySQL的 'utf8' 實際上不是真正的UTF-8。(可認爲是MySQL的一個BUG)
標準的 UTF-8 字符集編碼能夠用 1 ~ 4 個字節去編碼 21 位字符,幾乎包含了世界上所能看到的語言了,可是MySQL的'utf8'支持的字符最大長度是 3 個字節,而標準的 UTF-8 >支持的字符最大長度是 4 個字節。
也就是說:MySQL的'utf8'只支持到了標準的 UTF-8 字符集中的部分,即基本多文本平面(Basic Multiling Plane(BMP)),包含了控制符,拉丁文,中,日,韓等絕大多數國際字符,但並非全部,不支持:手機端經常使用的表情字符emoji和一些不經常使用的漢字,這些須要4個字節才能編碼出來。
在2010年,MySQL在5.5.3版本後增長了utf8mb4 字符編碼,mb4:most bytes 4(最多4字節),用來兼容以前MySQL-'utf8'不支持的四字節字符,是它的超集。
如今MySQL版本都到8以上了,因此通常在設計數據庫表時,默認都使用utf8mb4字符集
實戰舉例:【實戰很重要,敲黑板~】
<1> 基於命令行(黑窗口)的MySQL客戶端
先查數據庫版本:看數據庫版本號基本就知道設計庫表時,默認使用的字符集了
select LENGTH('XXX'); -- 返回字符串'XXX'的存儲長度,以字節爲單位。
select CHAR_LENGTH('XXX'); -- 返回字符串'XXX'的字符數,即'XXX'含了多少個漢字,字母,符號等等,加起來共多少個字符。
select CHARACTER_LENGTH('XXX'); -- 返回字符串'XXX'的字符數。
複製代碼
以school表爲例:表的默認字符集和各個列的字符集都是utf8bm4
,headmaster(校長)列數據類型爲varchar(20)
,最多存儲字符數是20個,存儲佔空間長度知道一下就行。
<2> MySQL Workbench客戶端
VARCHAR節省了存儲空間,對性能有幫助。
update操做時可能使行變得比原來更長,由於行是變長的,這就致使須要作額外工做,則影響性能。
若是一個行佔用的空間增加,且在頁內沒有更多的空間能夠存儲:
InnoDB:須要分裂頁來使行能夠放進頁內
MyISAM:將行拆成不一樣的片斷存儲
複製代碼
- 通常狀況下,數據庫的表/字段,請使用
utf8bm4
編碼的字符集。- 字符串列的最大長度比平均長度大不少,列更新不多,適合用VARCHAR類型
- 阿里Java開發手冊,建表規約 - 第8條,對設計數據庫表強制要求以下:
![]()
咱們也從如下三個方面解析:
記住兩點便可:
<1> CHAR類型是定長的,CHAR(10)表示能夠存儲10個字符,MySQL根據定義的字符數分配足夠的存儲空間。
對比一下CHAR(1) 與 VARCHAR(1) 存儲空間:
存儲單字節字符(如一個英文字母):
CHAR(1) : 1個字節
VARCHAR(1) : 1個字節 + 額外記錄長度(佔用空間大小)的1字節 = 2字節
複製代碼
<2> 存儲CHAR值時,MySQL會刪除全部的末尾空格,不會刪除前面和中間的空格;存儲VARCHAR值時(MySQL>=5.0版本),則會保留末尾空格。
舉例:定義school表的code(學校代號)字段爲CHAR(10)
update school set code = ' 985_0011' where name = '西安電子科技大學'; (code前面有兩個空格)
update school set code = '010_0039 ' where name = '明尼蘇達大學雙城分校'; (code末尾有四個空格)
複製代碼
對比看一下VARCHAR(10):(注意:MySQL>=5.0版本時VARCHAR會保留末尾空格,以前版本也會刪除末尾空格)
create table v_test(v_col varchar(10));
insert into v_test(v_col) values('string1'), (' string2'), ('string3 '); (string2前面有兩個空格,string3末尾有兩個空格)
複製代碼
很是短的列:CHAR 比 VARCHAR 在存儲空間上更有效率。
常常變動的列:CHAR 不易產生碎片,比 VARCHAR 更好。
很短的字符串,或者全部字符串都接近同一個長度(如密碼的MD5值是定長),適合定義爲CHAR類型。
阿里Java開發手冊,建表規約 - 第7條,對設計數據庫表強制要求以下:
![]()
BLOB 和 TEXT 都是用來存儲很大的數據,BLOB採用二進制存儲,TEXT採用字符方式存儲。
MySQL把每一個 BLOB 和 TEXT值看成一個獨立的對象處理,存儲引擎在存儲時一般會作特殊處理。
怎麼個特殊處理呢?
當BLOB和TEXT值太大時,InnoDB會使用專門的「外部」存儲區域來進行存儲:每一個值在錶行內用1 ~ 4字節存儲一個指針,指向外部存儲區域存儲的實際值。
複製代碼
對比:
BLOB: 存二進制數據,沒有排序規則或字符集
TEXT:存字符串,有排序規則和字符集,只對每列的最前max_sort_length字節而不是整個字符串作排序。
若只須要排序前面一小部分字符,能夠減少max_sort_length的配置,或使用ORDER BY SUSTRING(column, length)。
複製代碼
MySQL中使用ENUM('xxx1','xxx2','xxx3')
定義字段爲枚舉列。
咱們從如下四個方面解析:
枚舉列是把不重複的字符串存儲成一個預約義的集合,如ENUM('fish','dog','pig')
,在MySQL內部會將每一個值在列表中的位置保存爲整數,並在表的.frm文件中保存 「數字 - 字符串」 映射關係的 「查找表」。
舉例:
新建表:create table enum_test(e_col ENUM('fish','dog','pig') NOT NULL); (e_col列爲枚舉)
插入數據:insert into enum_test(e_col) values('fish'),('dog'),('pig'); (只能插枚舉列表中的值,不然報錯)
複製代碼
MySQL在存儲枚舉時很是緊湊,根據列表值的數量(注意是數量)壓縮到一個或兩個字節中。
枚舉字段按照內部存儲的整數而不是定義的字符串進行排序。
使用FIELD()函數
必須使用ALTER TABLE
命令:
alter table enum_test change column e_col e_col ENUM('fish', 'dog') NOT NULL;
alter table enum_test change column e_col e_col ENUM('fish', 'dog', 'monkey') NOT NULL;
複製代碼
MySQL提供了兩種類似的日期類型:DATETIME 和 TIMESTAMP
咱們從如下三個方面對比這兩種日期類型:
DATETIME:能保存大範圍的值,從1001年到9999年,精度爲秒。
TIMESTAMP:保存了從1970年1月1日零點(格林尼治標準時間)以來的秒數,它和UNIX時間戳相同,只能表示的範圍是:1970年 ~ 2038年。
DATETIME:把日期和時間封裝到格式爲YYYYMMDDHHMMSS
的整數中,與時區無關,使用8個字節的存儲空間。
TIMESTAMP:使用4字節的存儲空間。
DATETIME:2008-01-16 22:37:08
(MySQL默認狀況下)
TIMESTAMP:2008-01-16 22:37:08
(MySQL>=4.1版本,按照DATETIME方式格式化),顯示的值依賴時區。
怎麼個依賴時區呢?
舉例:TIMESTAMP類型字段存儲值爲0,則在美國東部時區顯示爲:1969-12-31 19:00:00(不是1970-01-01 00:00:00),由於與格林尼治時間差了5個小時。
若是在多個時區存儲或訪問數據,TIMESTAMP提供的值與時區有關係,DATETIME則保留文本表示的日期和時間,跟時區無關,因此它們很不同。
複製代碼
- 除特殊以外,通常狀況儘可能使用TIMESTAMP,由於比DATETIME空間效率更高。
- 通常不要把Unix時間戳存儲爲整數值,不方便處理。
- MySQL能存儲的最小時間粒度爲秒,若是要存儲比秒更小粒度的日期和時間,可使用BIGINT類型存儲微秒級別的時間戳,或者使用DOUBLE存儲秒以後的小數部分。
- 建立表時通常增長create_time(建立時間)字段,update_time(更新時間)字段,根據操做自動填入當前時間,以下:
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', 複製代碼
MySQL有少數幾種存儲類型使用緊湊的位存儲數據,這些位類型,無論從底層存儲格式和處理方式上,都是字符串類型。
咱們以 BIT 和 SET 爲例,來解析位數據類型。
BIT
BIT(1):存儲1個位,BIT(2):存儲2個位,BIT列最大長度是:64個位。
存儲二進制值 b'00111001'(前面加b表示二進制,必需要有)
查詢返回值是:ASCII碼爲該二進制數值所對應的字符'9'('00111001'表示數是57,在ASCII碼中,編碼爲57的字符'9',即查到的就是字符'9')
數字上下文場景中查詢返回值是:數字57,即ASCII碼的編碼數字
存儲空間:InnoDB引擎使用可以存儲下的最小整數類型來存放,如:8位bit的話,用TINYTINT就能存放下,因此存儲空間爲1字節。
複製代碼
SET
SET:MySQL內部是以一系列打包的位的集合來表示,每一個位(bit)或者SET元素表明一個值。
舉例:
建立一個權限訪問控制表acl:create table acl(perms SET('READ', 'WRITE', 'DELETE') NOT NULL);
其中把權限'READ', 'WRITE', 'DELETE'打包成一個位的集合SET,每一個SET元素表明一個值:
'READ' -- 00000001(二進制位表示) -- 1
'WRITE' -- 00000010(二進制位表示) -- 2
'DELETE' -- 00000100(二進制位表示) -- 4
MySQL在列定義裏存儲位到值的映射關係,即:"bit位值 - 元素字符串",與枚舉ENUM類似。
咱們acl表插入權限值:insert into acl(perms) values('READ'),('WRITE'),('DELETE'),('READ,DELETE'); 看一下查詢acl表數據:
(注意:能夠在一列中存儲多個SET元素)
複製代碼
- 謹慎使用BIT類型,最好避免使用。
- SET類型雖有效利用了存儲空間,但改變列定義須要ALTER TABLE,代價較高,能夠考慮使用一個整數包裝一系列的位,如:TINYINT包裝8個位。
- 表名不使用複數名詞(表名應該僅僅表示表裏的實體內容,不該表示實體數量)(阿里規範-建表規約-3) 。
- 表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只出現數字(阿里-建表規約-2)。
正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name 複製代碼
- 儘可能避免 NULL,由於可爲 NULL 的列會使用更多的存儲空間,並且索引,索引統計和值比較都更復雜。
- 儘可能使用能夠正確存儲數據的最小數據類型,由於佔用更少的磁盤、內存和CPU緩存,因此更快。
- 整型比字符串操做代價更低,由於字符集和校對規則(排序規則)使字符比較比整型比較更復雜。
IPV4地址其實是32位無符號整數,不是字符串,因此應該用無符號整數(INT UNSIGNED)存儲IP地址 MySQL提供函數:INET_ATON(), INET_NTOA(), 在字符串表示和整數表示之間轉換。 說明:用小數點將地址分爲4段的表示法只是讓人們閱讀容易。 複製代碼
- 一個表中不要有太多的列,由於InnoDB在MySQL服務器層和存儲引擎層之間會將列轉換成行數據結構,依賴列的數量。
- 不要關聯太多表,單個查詢最好在12個表之內作關聯。(MySQL限制了每一個關聯操做最多隻能有61張表)
- 通常不要指定整數類型寬度,如ITN(20),TINYINT(2)等,除非設定
Zero Fill
屬性,用來填充字符個數。- 整型字段不會出現負值時,將其設定爲
UNSIGNED
,能擴大正數存儲範圍,同時也能免去負值的干擾。- 儘可能只在對小數進行精確計算時,使用DECIMAL類型,如財務數據。(float和double在存儲時,存在精度損失問題)
- 使用DECIMAL存儲,數據量大時(上千萬條或更多),考慮把小數乘以相應倍數,使用BIGINT存儲;存儲數據超範圍時,考慮把數據拆成整數和小數分開存儲。
- 通常狀況下,數據庫的表/字段,請使用utf8bm4編碼的字符集。
- VARCHAR是可變字符串,不預先分配存儲空間,長度不要超過5000,若是超過,定義字段類型爲TEXT,獨立出一張表,用主鍵對應關聯。(阿里-建表規約-8)
- 很短的字符串,或者全部字符串都接近同一個長度,如密碼的MD5值是定長,考慮使用CHAR類型。
- 不要把Unix時間戳存儲爲整數值,不方便處理。
- 通常狀況儘可能使用TIMESTAMP,由於比DATETIME空間效率高,
- 建立表時通常增長create_time(建立時間)字段,update_time(更新時間)字段,根據操做自動填入當前時間,以下:
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', 複製代碼
- MySQL能存儲的最小時間粒度爲秒,若是要存儲比秒更小粒度的日期和時間,可使用BIGINT類型存儲微秒級別的時間戳,或者使用DOUBLE存儲秒以後的小數部分。
- 謹慎使用BIT類型,最好避免使用。
- 選擇表的列標識符時,整數類型是最好的選擇,整數類型 > 字符串類型 > ENUM和SET類型(通常避免)