最近需要對表加一個字段,同時覺得前期建立表的時候有點粗暴,沒有加很對限制,比如有些字符串長度是有限制的,在創(chuàng)建表時字段也沒有對其進行限制。所以想借著這次加字段對表字段也進行一個優(yōu)化,在優(yōu)化之前先看了點理論知識,理論指導實踐
寫在前面
選擇合適的字段類型既可以節(jié)省空間,又可以在查詢上提高效率,因此字段類型選擇是很重要的。本篇文章將介紹常用字段類型:
- 整數(shù)類型
- 實數(shù)類型
- 字符串類型
- 日期和時間
- 枚舉類型
整數(shù)類型
整數(shù)類型有TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT,存儲空間及數(shù)值范圍如下表
類型 | 存儲空間(單位為位) | 數(shù)值范圍 |
---|---|---|
TINYINT | 8 | -128 ~ 127 |
SMALLINT | 16 | -32768 ~ 32767 |
MEDIUMINT | 24 | -8388608 ~ 8388607 |
INT | 32 | -2147483648 ~ 2147483647 |
BIGINT | 64 | 太大了 |
數(shù)值范圍為-2^(N-1) ~ 2^N, 其中N為存儲空間大小
整數(shù)類型有可選的UNSIGNED屬性,不允許出現(xiàn)負值。設置UNSIGNED屬性可以使正數(shù)的上限提高一倍,數(shù)值范圍大小為 0 ~ 2^(N-1) + 2^N
實數(shù)類型
FLOAT
單精度浮點型,使用8位
DOUBLE
雙精度浮點型,使用16位存儲
DECIMAL
float和double進行計算時會發(fā)生精度損失,損精度損失原因可參考這篇文章:老板,用float存儲金額為什么要扣我工資
需要精度計算的時候可以使用DECIMAL,使用DECIMAL需要額外的空間和計算開銷,因此當且僅當需要精度計算時才使用
字符串類型
1. VARCHAR和CHAR
varchar和char是非常非常常用的字符串類型
VARCHAR
VARCHAR用于存儲變長字符串,使用該類型存儲字符串時需要額外使用1或2個額外字節(jié)記錄字符串的長度:
- 列的最大長度小于或等于255 => 使用1字節(jié)
- 列的長度大于255 => 使用2字節(jié)
適用VARCHAR作為存儲類型的場景:
- 列更新很少 => 列經常更新容易產生頁分裂
- 列長度非固定 => VARCHAR存儲時只使用必要空間,因此會省空間
CHAR
CHAR用于存儲定長字符串,在存儲CHAR類型時,會刪除所有的末尾空格
使用CHAR最為存儲類型的場景
- 列幾乎定長
- 列長度很短 => VARCHAR需要額外字節(jié)存儲長度
- 列經常更新
2. BLOB和TEXT類型
BLOB和TEXT類型都是用來存儲很大的數(shù)據(jù),比如文章內容這些
BLOB
采用二進制方式存儲, BLOB細分又可以分為TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB, LONGBLOB
TEXT
采用字符方式存儲,TEXT細分又可以分為TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT, LONGTEXT
當BLOB和TEXT值太大時,InnoDB存儲會使用外部存儲區(qū)域來存儲值,然后保存一個1~4字節(jié)的指針指向外部存儲
日期和時間類型
常用的日期類型有DATETIME和TIMESTAMP
DATETIME
使用8字節(jié)存儲,可以保存大范圍的值,從1001~9999年
TIMESTAMP
使用4字節(jié)存儲,保存范圍比DATETIME小,從1970~2038年
對于需要存儲更小粒度的日期和時間可以使用DOUBLE或BIGINT,當然不是存儲小粒度也可以使用BIGINT
DATETIME和TIMESTAMP如何選擇類
之前曾因為時間類型搞出過線上慢查詢,這篇文章記錄了慢查詢原因:很高興!終于踩到了慢查詢的坑, 對于需要對時間進行范圍查找、排序、分組等操作之類的建議使用BIGINT,如果對時間類型字段沒有任何操作,建議使用TIMESTAMP,可以參考這篇文章:mysql數(shù)據(jù)庫時間類型datetime、bigint、timestamp的查詢效率比較
在stackoverflow下找到如下:
枚舉類型
可以使用枚舉列代替常用的字符串類型,通過枚舉可以限制值的取值范圍
枚舉使用
創(chuàng)建表語句:
CREATE TABLE `dataset_enum` (
`name` varchar(48) DEFAULT NULL,
`status` enum('NEW','UPLOADING','USING','DELETING') DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
插入語句:
insert into dataset_enum(name, status) values("t4", "DELETING")
對于status字段底層存儲的是整數(shù)而不是字符串,在底層會維護一個 數(shù)字 - 字符串的映射關系
查詢語句并根據(jù)status字段進行排序:
select * from dataset_enum order by status;
查詢結果:
+------+-----------+
| name | status |
+------+-----------+
| t1 | NEW |
| t2 | UPLOADING |
| t4 | DELETING |
+------+-----------+
說明:
- 排序的結果是根據(jù)內部存儲的整數(shù)來的而不是定義的字符串進行排序的
- 底層存儲的是整數(shù),根據(jù)映射關系轉化為字符串,因此會有一定的開銷
為什么使用TINYINT而不用ENUM
之前創(chuàng)建表的時候對于常用字符串的代替選擇的都是TINYINT類型,應用層在做轉換。當看到ENUM類型時有點困惑,為什么沒選擇使用ENUM而是TINY,網上查找了一下原因,如下圖:
總結原因如下:
- 不方便遷移,可擴展性弱,如比較熟悉的PostgreSQL數(shù)據(jù)庫就不支持ENUM類型
- ENUM字段添加或刪除字符串時會進行表重構,這個操作非常耗時和耗性能
- 有坑
以之前的表dataset_enum為例插入數(shù)據(jù):
成功插入了數(shù)據(jù)mysql> insert into dataset_enum values("t1", "NEW"), ("t2", 2); Query OK, 2 rows affected (0.01 sec) Records: 2 Duplicates: 0 Warnings: 0
查詢數(shù)據(jù):
數(shù)值類型做轉化以后也可以插入mysql> select * from dataset_enum; +------+-----------+ | name | status | +------+-----------+ | t1 | NEW | | t2 | UPLOADING | +------+-----------+
- 無法與其他表做關聯(lián)
參考文章:
Should I use the datetime or timestamp data type in MySQL?
8 Reasons Why MySQL's ENUM Data Type Is Evil
為什么辣么多人喜歡用 tinyint而不用 enum?