如何為 MySQL 表中的欄位選擇合適的數據類型

2022-01-09 SQL編程思想

我們在設計資料庫時需要為表中的每個欄位指定一個數據類型。數據類型決定了欄位中允許存儲的數據以及支持的操作,例如字符串允許的最大長度、數字類型可以進行算術運算等;另一方面,不同的類型佔用的存儲空間和處理性能也不同。因此,本文給大家介紹一下 MySQL 支持的各種數據類型,以及設計表時如何選擇合適的欄位類型。

常見數據類型

MySQL 實現了 SQL 標準中定義的大部分數據類型,主要可以分為以下幾類:數字類型、字符串類型、日期和時間類型、JSON 數據類型以及空間類型。

數字類型

MySQL 實現了 SQL 標準中的精確數字類型和近似數字類型,包括整數(INTEGER、SMALLINT)、定點數(DECIMAL、NUMERIC)和浮點數(FLOAT、REAL、DOUBLE PRECISION)。這些數字類型又可以分為有符號類型和無符號類型。

整數數字

下表列出了 MySQL 中的所有整數類型:

整數類型的定義如下:

TINYINT[(M)] [UNSIGNED] [ZEROFILL]
SMALLINT[(M)] [UNSIGNED] [ZEROFILL]
MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]
INT[(M)] [UNSIGNED] [ZEROFILL]
INTEGER[(M)] [UNSIGNED] [ZEROFILL]
BIGINT[(M)] [UNSIGNED] [ZEROFILL]

其中,INT 是 INTEGER 的同義詞。M 表示的是顯示寬度,不會影響數據的存儲;如果實際數據小於指定寬度,可以指定 ZEROFILL 在左側使用 0 填充顯示。UNSIGNED 表示無符號整數;如果指定了 ZEROFILL,MySQL 會自動加上 UNSIGNED。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t(id int(4) ZEROFILL);
INSERT INTO t VALUES (1), (99999);

mysql> SELECT * FROM t;
+--+
| id |
+--+
| 0001 |
| 99999 |
+--+
2 rows in set (0.00 sec)

mysql> desc t;
+--+-+-++----+--+
| Field | Type | Null | Key | Default | Extra |
+--+-+-++----+--+
| id | int(4) unsigned zerofill | YES | | NULL | |
+--+-+-++----+--+
1 row in set (0.00 sec)

⚠️從 MySQL 8.0.17 開始,整數類型的顯示寬度 M 和 ZEROFILL 選項已經被棄用,將來的版本中會刪除。可以考慮使用其他方法實現相同的效果,例如使用 LPAD() 函數將數字填充至指定寬度,或者使用 CHAR 類型存儲格式化的數字。

對於 BIGINT 類型,需要注意以下內容:

SERIAL 是 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的同義詞;

所有的算術運算都使用有符號的 BIGINT 或者 DOUBLE 數據進行運算,所以不要使用大於 9223372036854775807 的無符號大整數參與運算,除非使用位函數和運算符。如果參與了運算,由於將 BIGINT 轉換為 DOUBLE 時存在捨入錯誤,可能會導致結果中的最後幾位數字出錯。MySQL 會在以下情況中使用 BIGINT:使用整數類型存儲 BIGINT 欄位中的無符號大整數;針對 BIGINT 欄位的 MIN() 或者 MAX() 函數;兩個整數的算術運算(+、-、* 等);

可以將 BIGINT 數據存儲為字符串, MySQL 在運算時會執行字符串到數字的類型轉換,而不會涉及到雙精度格式的中間結果。

當兩個操作數都是整數類型時,-、+ 和 * 運算符使用 BIGINT 算術。這意味著如果將兩個大整數(或者返回整數值的函數)相乘,如果結果大於 9223372036854775807,可能會得到一個異常的結果。

定點數字

MySQL 使用 DECIMAL 和 NUMERIC 類型存儲精確的數字值,通常用於需要保留完整精確的欄位,例如財務系統中的貨幣餘額。MySQL 中的 NUMERIC 和 DECIMAL 是同義詞。

DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]
DEC[(M[,D])] [UNSIGNED] [ZEROFILL]
NUMERIC[(M[,D])] [UNSIGNED] [ZEROFILL]
FIXED[(M[,D])] [UNSIGNED] [ZEROFILL]

其中,DEC 和 FIXED 是 DECIMAL 的同義詞。M 表示可以存儲的總位數(精度),最大取值為 65,默認值為 10,小數點和負號(-)不算位數;D 是小數點後的位數(刻度),最大取值為 30 並且小於等於 M,默認值為 0(表示整數)。ZEROFILL 表示如果實際數據小於指定寬度,顯示時在左側使用 0 填充。UNSIGNED 表示無符號數字;如果指定了 ZEROFILL,MySQL 會自動加上 UNSIGNED。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t(salary DECIMAL(5,2));
INSERT INTO t VALUES (-999.99), (0), (999.99);

mysql> INSERT INTO t VALUES (1000);
ERROR 1264 (22003): Out of range value for column 'salary' at row 1

mysql> INSERT INTO t VALUES (0.001);
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql> show warnings;
+--+-++
| Level | Code | Message |
+--+-++
| Note | 1265 | Data truncated for column 'salary' at row 1 |
+--+-++
1 row in set (0.00 sec)

mysql> SELECT * FROM t;
+----+
| salary |
+----+
| -999.99 |
| 0.00 |
| 999.99 |
| 0.00 |
+----+
4 rows in set (0.00 sec)

其中,salary 欄位的精度為 5,刻度為 2。因此,該欄位可以存儲 -999.99 到 -999.99 之間的數值。此時,插入超過精度的數據返回錯誤,插入超過刻度的數據會進行四捨五入。

MySQL 中的 DECIMAL 和 DECIMAL(M) 等價於 DECIMAL(M,0),M 的默認值為 10。

⚠️從 MySQL 8.0.17 開始,DECIMAL 類型的 UNSIGNED 選項已經被棄用,將來的版本中會刪除。可以考慮使用 CHECK 約束實現相同的功能。

DECIMAL 數據使用二進位格式進行存儲,9 位十進位數字存儲為 4 個字節,整數部分和小數部分的數值分別存儲。每 9 位數字需要 4 字節存儲,剩餘的位數需要的存儲空間如下表所示:

剩餘位數字節數001–213–425–637–94

例如,DECIMAL(18,9) 在小數點兩邊各有 9 位,所以整數部分和小數部分各需要 4 字節存儲;DECIMAL(20,6) 擁有 14 位整數和 6 位小數,整數部分需要 4 字節(9 位數字)加上 3 字節(7 位數字),6 位小數部分需要 3 字節存儲。

DECIMAL 欄位不會存儲前置的 +、- 字符或者 0。如果為 DECIMAL(5,1) 欄位插入 +0003.1,最終會存儲 3.1。對於負數,不會存儲負號。

浮點數字

MySQL 使用 FLOAT 和 DOUBLE 類型表示近似數字,對於單精度使用 4 字節存儲,雙精度使用 8 字節存儲。

FLOAT(p) [UNSIGNED] [ZEROFILL]

其中,p 是以比特為單位的精度。MySQL 中的 p 只用於決定最終類型為 FLOAT 還是 DOUBLE,如果 p 的值為 0 到 24 則為 FLOAT,如果 p 的值為 25 到 53 則為 DOUBLE;ZEROFILL 表示如果實際數據小於指定寬度,顯示時在左側使用 0 填充。UNSIGNED 表示無符號數字;如果指定了 ZEROFILL,MySQL 會自動加上 UNSIGNED。

對於單精度 FLOAT 類型,理論上支持 -3.402823466E+38 到 -1.175494351E-38、0、以及 1.175494351E-38 到 3.402823466E+38 之間的數值,實際上取決於硬體和作業系統。

對於雙精度 DOUBLE 類型,理論上支持 -1.7976931348623157E+308 到 -2.2250738585072014E-308、0 以及 2.2250738585072014E-308 到 1.7976931348623157E+308 之間的數值,實際上取決於硬體和作業系統。

📝DOUBLE 是 DOUBLE PRECISION 的同義詞。如果指定了 SQL 模式 REAL_AS_FLOAT,REAL 是 FLOAT 的同義詞;否則它是 DOUBLE PRECISION 的同義詞。

由於浮點類型存儲的是近似數值而不是精確數值,如果嘗試將它們進行比較可能會返回錯誤的結果,而且結果和平臺或者具體實現相關。例如:

mysql> SELECT (1.0/3)*3, (1.0/3)*3=1.0;
+-++
| (1.0/3)*3 | (1.0/3)*3=1.0 |
+-++
| 1.00000 | 0 |
+-++
1 row in set (0.00 sec)

雖然 (1.0/3)*3 的結果顯示為 1.00000,但是在系統內部它並不等於 1.0。

📝為了方便移植,存儲近似數值的數據類型應該使用 FLOAT 或者 DOUBLE PRECISION 定義,而不需要指定精度或者位數。

除了以上定義之外,MySQL 還提供了一些非標準的語法:

FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]
DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]
DOUBLE PRECISION[(M,D)] [UNSIGNED] [ZEROFILL]
REAL[(M,D)] [UNSIGNED] [ZEROFILL]

其中,M 表示可以存儲的總位數(精度),D 是小數點後的位數(刻度);如果省略 M 和 D,具體的範圍取決於硬體實現,單精度浮點數大概支持 7 位小數,雙精度浮點數大概支持 15 位小數。例如,FLOAT(7,4) 類型顯示時的內容可能為 -999.9999。MySQL 存儲數據值會進行捨入,如果插入 999.00009,近似的結果為 999.0001。

⚠️從 MySQL 8.0.17 開始,FLOAT、DOUBLE 類型的 UNSIGNED 屬性已經被棄用,將來的版本中會刪除。可以考慮使用 CHECK 約束實現相同的功能。從 MySQL 8.0.17 開始,非標準的 FLOAT(M,D) 和 DOUBLE(M,D) s語法已經被棄用,將來的版本中會刪除。

BIT 類型

MySQL 中的 InnoDB、MyISAM、MEMORY 以及 NDB 存儲引擎支持特殊的位類型(BIT),用於存儲比特數據值。

BIT(M)

其中,M 表示比特個數,範圍從 1 到 64,默認為 1;BIT(M) 大概需要 (M+7)/8 字節的存儲。例如:

CREATE TABLE work_days(
year INT,
week INT,
days BIT(7),
PRIMARY KEY(year, week)
);
INSERT INTO work_days
VALUES(2020, 1, b'0110110');

其中,days 表示這一周中的每一天是否是工作日,1 表示工作日,0 表示周末或者假期。INSERT 語句表示 2020 年第一周的周日、周三和周六是休息日。

位類型的常量可以使用以下格式指定:

b'111'
B'111'
0b111

以上數據表示十進位中的 3。查詢時可以使用 BIN 函數進行轉換顯示:

mysql> select year, week, bin(days) from work_days;
+-+-+-+
| year | week | bin(days) |
+-+-+-+
| 2020 | 1 | 110110 |
+-+-+-+
1 row in set (0.00 sec)

布爾類型

MySQL 沒有提供內置的 BOOLEAN 或者 BOOL 數據類型,而是使用 TINYINT(1) 進行表示。以下三種語法等價:

BOOL
BOOLEAN
TINYINT(1)

在 MySQL 中,0 被看作 false,非 0 的數值被看作 true。布爾類型的常量可以使用 TRUE 和 FALSE 表示,結果分別為 1 和 0。

mysql> SELECT IF(0, 'true', 'false');
+----+
| IF(0, 'true', 'false') |
+----+
| false |
+----+

mysql> SELECT IF(1, 'true', 'false');
+----+
| IF(1, 'true', 'false') |
+----+
| true |
+----+

mysql> SELECT IF(2, 'true', 'false');
+----+
| IF(2, 'true', 'false') |
+----+
| true |
+----+

不過, TRUE 和 FALSE 僅僅是 1 和 0 的別名。例如:

mysql> SELECT IF(0 = FALSE, 'true', 'false');
+--+
| IF(0 = FALSE, 'true', 'false') |
+--+
| true |
+--+

mysql> SELECT IF(1 = TRUE, 'true', 'false');
+-+
| IF(1 = TRUE, 'true', 'false') |
+-+
| true |
+-+

mysql> SELECT IF(2 = TRUE, 'true', 'false');
+-+
| IF(2 = TRUE, 'true', 'false') |
+-+
| false |
+-+

mysql> SELECT IF(2 = FALSE, 'true', 'false');
+--+
| IF(2 = FALSE, 'true', 'false') |
+--+
| false |
+--+

最後兩個語句都返回了 false,因為 2 即不等於 1,也不等於 0。

字符串類型

MySQL 字符串類型用於存儲字符和字符串數據,包括二進位數據,例如圖片或者文件。字符串數據可以支持比較運算符和模式匹配運算符,例如 LIKE、正則表達式匹配以及全文檢索。

MySQL 支持的字符串類型包括 CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM 以及 SET,其中 CHAR、VARCHAR、TEXT、ENUM 以及 SET 包含字符集(Charset)和排序規則(Collation)屬性,默認繼承表的字符集和排序規則。MySQL 8.0 默認使用 utf8mb4 字符集。

CHAR

CHAR(n) 和 CHARACTER(n) 類型表示長度固定的字符串,其中 n 表示字符串中字符的最大數量,取值範圍從 0 到 255。例如:

CREATE TABLE t (c1 CHAR, c2 CHAR(5));
INSERT INTO t VALUES ('a','a');

其中,c1 只能存儲 1 個字符;c2 最多能夠存儲 5 個字符。對於定長字符串,如果輸入的字符串長度不夠,將會使用空格進行填充。因此,欄位 c2 中實際存儲的內容為「a」加上 4 個空格。

mysql> select concat(c1, '!'), concat(c2, '!') from t;
+--+--+
| concat(c1, '!') | concat(c2, '!') |
+--+--+
| a! | a! |
+--+--+
1 row in set (0.00 sec)

mysql> SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select concat(c1, '!'), concat(c2, '!') from t;
+--+--+
| concat(c1, '!') | concat(c2, '!') |
+--+--+
| a! | a ! |
+--+--+
1 row in set (0.00 sec)

mysql> SET sql_mode = default;
Query OK, 0 rows affected (0.00 sec)

默認情況下沒有設置 SQL 模式 PAD_CHAR_TO_FULL_LENGTH,MySQL 讀取 CHAR 欄位時自動截斷了尾部的空格。

MySQL 使用比較運算符(=、<>、>、< 等)和 LIKE 操作符比較和匹配 CHAR 欄位數據時不考慮尾部的空格。例如:

mysql> select c2 from t where c2='a';
+-+
| c2 |
+-+
| a |
+-+
1 row in set (0.00 sec)

mysql> select c2 from t where c2 like 'a ';
Empty set (0.00 sec)

📝通常來說,只有存儲固定長度的數據時,才會考慮使用定長字符串類型。例如 18 位身份證,6 位郵政編碼等。

VARCHAR

VARCHAR(n) 和 CHARACTER VARYING(n) 類型表示長度不固定的字符串,其中 n 表示字符串中字符的最大數量,取值範圍從 0 到 65535。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (c VARCHAR(5));
INSERT INTO t VALUES ('a '), ('abcde');

欄位 c 最多存儲 5 個字符。

📝VARCHAR 欄位的實際最大長度受限於最大的行大小(65536 字節,所有欄位長度之和)以及字符集。例如,utf8mb4 字符集中的一個字符最多佔用 4 個字節,因此這種字符集的 VARCHAR 欄位可以聲明的最大長度為 16383。

對於變長字符串,如果輸入的字符串長度不夠,存儲實際的內容。例如類型為 VARCHAR(5) 的欄位,如果輸入值為「a」,實際存儲的內容為「a」。SQL 模式 PAD_CHAR_TO_FULL_LENGTH 對 VARCHAR 欄位沒有影響,MySQL 讀取 VARCHAR 欄位時不會截斷尾部的空格。

mysql> select c,length(c) from t;
+--+-+
| c | length(c) |
+--+-+
| a | 5 |
| abcde | 5 |
+--+-+
2 rows in set (0.00 sec)

MySQL 存儲的 VARCHAR 數據包括 1 字節或 2 字節的長度信息前綴加上具體數據,長度前綴標識了數據的字節數。如果 VARCHAR 欄位的數據小於等於 255 字節,使用 1 個字節存儲長度;如果數據可能大於等於 256 字節,使用 2 個字節存儲長度。

📝通常來說,變長字符串類型一般用於存儲長度不固定的內容,例如名字、電子郵箱、產品描述等。

TEXT

MySQL 提供了 4 種形式的 TEXT 類型:TINYTEXT、TEXT、MEDIUMTEXT 以及 LONGTEXT。

TINYTEXT,最大長度為 255 個字節,類似於 VARCHAR(255)。每個 TINYTEXT 值需要 1 字節額外的存儲表示長度;

TEXT,最大長度為 65535 個字節,類似於 VARCHAR(65535)。每個 TEXT 值需要 2 字節額外的存儲表示長度;

MEDIUMTEXT,最大長度為 16777215 個字節。每個 MEDIUMTEXT 值需要 3 字節額外的存儲表示長度;

LONGTEXT,最大長度為 4294967295 個字節。每個 LONGTEXT 值需要 4 字節額外的存儲表示長度。

TEXT 類型可以用於存儲長文本字符串,長度支持 1 字節到 4 GB;但是 MySQL 不會在伺服器內存中緩存 TEXT 數據,而是從磁碟中讀取,所有訪問時比 CHAR 和 VARCHAR 類型更慢一些。MySQL 插入或者查詢時不會對 TEXT 數據尾部空格進行任何處理。

📝TEXT 數據類型通常用於存儲文章內容、產品描述等信息。

二進位字符串

BINARY(M) 和 VARBINARY(M) 類型與 CHAR 和 VARCHAR 類型類似,但是存儲的內容為二進位字節串,而不是普通字符串。其中 M 表示最大的字節長度,分別為 255 和 65535。這兩種類型使用 binary 字符集和排序規則,基於字節數值進行比較和排序。

存儲 BINARY 數據時,在尾部使用 0x00(字節 0)填充到指定長度,查詢時不會刪除尾部的 0 字節。所有的字節對應比較操作都有意義,包括 ORDER BY 和 DISTINCT 操作,0x00 和空格比較的結果不相等,0x00 的排序在空格前面。

DROP TABLE IF EXISTS t;
CREATE TABLE t (c BINARY(3));
INSERT INTO t VALUES ('a');

mysql> SELECT * FROM t where c='a';
Empty set (0.00 sec)

mysql> SELECT * FROM t where c='a\0\0';
+--+
| c |
+--+
| 0x610000 |
+--+
1 row in set (0.00 sec)

存儲 VARBINARY 數據時,不會使用 0x00(字節 0)填充,查詢時不會刪除尾部的 0 字節。所有的字節對應比較操作都有意義,包括 ORDER BY 和 DISTINCT 操作,0x00 和空格比較的結果不相等,0x00 的排序在空格前面。

BINARY(M) 和 VARBINARY(M) 類型與 CHAR 和 VARCHAR 類型的存儲需求也類似,但是以字節為單位。

BLOB

MySQL 提供了 4 種形式的 BLOB 類型:TINYBLOB、BLOB、MEDIUMBLOB 以及 LONGBLOB。

TINYBLOB,最大長度為 255 個字節,類似於 VARBINARY(255)。每個 TINYBLOB 值需要 1 字節額外的存儲表示長度;

BLOB,最大長度為 65535 個字節,類似於 VARBINARY(65535)。每個 BLOB 值需要 2 字節額外的存儲表示長度;

MEDIUMBLOB,最大長度為 16777215 個字節。每個 MEDIUMBLOB 值需要 3 字節額外的存儲表示長度;

LONGBLOB,最大長度為 4294967295 個字節。每個 LONGBLOB 值需要 4 字節額外的存儲表示長度。

BLOB 類型可以用於存儲二進位大對象,長度支持 1 字節到 4 GB;BLOB 類型使用 binary 字符集和排序規則,基於字節數值進行比較和排序。MySQL 不會在伺服器內存中緩存 BLOB 數據,而是從磁碟中讀取,所有訪問時比 BINARY 和 VARBINARY 類型更慢一些。MySQL 插入或者查詢時不會對 BLOB 數據尾部空格進行任何處理。

📝BLOB 數據類型通常用於存儲圖片、文檔、視頻等信息。

ENUM

ENUM(『value1』,『value2』,…) 類型定義了一個枚舉,即取值限定為 『value1』、『value2』、…、NULL 或者 『』 之一的字符串對象。ENUM 數據在內部使用整數表示,最多包含 65535 個不同的值。

每個枚舉元素最大的長度為 M <= 255 並且 (M x w) <= 1020,其中 M 是元素的字面長度,w 是字符集中字符可能佔用的最大字節數。

使用枚舉類型的優勢在於:

CREATE TABLE shirts (
name VARCHAR(40),
size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
);
INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'), ('polo shirt','small');

SELECT name, size FROM shirts WHERE size = 'medium';
+----+---+
| name | size |
+----+---+
| t-shirt | medium |
+----+---+

如果插入 100 萬條 『medium』 數據,需要 100 萬字節存儲;如果直接使用 VARCHAR 類型,需要 6 倍存儲。

另一方面,使用枚舉類型時需要注意枚舉值的排序使用內部的索引數字,而不是字符串。例如,對於 ENUM(『b』, 『a』) 字符 b 排在 a 之前。

SET

SET(『value1』,『value2』,…) 類型定義了一個集合,即取值限定為 『value1』、『value2』、…中零個或多個的字符串對象。SET 數據在內部使用整數表示,最多包含 64 個不同的成員。

每個集合元素最大的長度為 M <= 255 並且 (M x w) <= 1020,其中 M 是元素的字面長度,w 是字符集中字符可能佔用的最大字節數。

例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (c SET('a','b'));
INSERT INTO t VALUES (''),('a'),('b'),('a,b');

mysql> SELECT c+0 FROM t;
+-+
| c+0 |
+-+
| 0 |
| 1 |
| 2 |
| 3 |
+-+
4 rows in set (0.00 sec)

SET 對象的存儲空間由集合成員的個數決定;如果個數為 N,對象佔用 (N+7)/8 字節,向上取整為 1、2、3、4 或者 8 字節。

日期時間類型

MySQL 提供了以下存儲時間值的數據類型:DATE、TIME、DATETIME、TIMESTAMP 以及 YEAR。其中,TIME、DATETIME、TIMESTAMP 支持小數秒,最多 6 位小數(微秒)。

日期類型

DATE 表示日期類型,支持的範圍從 『1000-01-01』 到 『9999-12-31』,佔用 3 個字節。DATE 數據的顯示格式為 『YYYY-MM-DD』。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (birth_date date);
INSERT INTO t VALUES ('2020-10-01');

mysql> SELECT * FROM t;
+--+
| birth_date |
+--+
| 2020-10-01 |
+--+
1 row in set (0.00 sec)

MySQL 使用 4 位數字存儲日期數據中的年份,如果輸入 2 位年份,將會使用以下規則:

INSERT INTO t VALUES ('01-10-31'), ('81-10-31');

mysql> SELECT * FROM t;
+--+
| birth_date |
+--+
| 2020-10-01 |
| 2001-10-31 |
| 1981-10-31 |
+--+
3 rows in set (0.00 sec)

以上規則同樣適用於其他數據類型中的年份信息,包括 DATETIME、TIMESTAMP 以及 YEAR。

時間類型

MySQL 使用 TIME 類型表示一天中的時間,格式為 『HH:MM:SS』,範圍小於 24 小時。另外,也可以使用 TIME 表示兩個事件之間的時間間隔,格式為 『hhh:mm:ss』,範圍從 『-838:59:59』 到 『838:59:59』。TIME 類型需要 3 字節的存儲。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (start_time time, end_time time);
INSERT INTO t VALUES ('09:00:00', '10:00:00');

mysql> SELECT * FROM t;
+--++
| start_time | end_time |
+--++
| 09:00:00 | 10:00:00 |
+--++
1 row in set (0.00 sec)

MySQL 使用 TIME(N) 表示包含小數部分的時間,最多包含 6 位小數(微秒),默認為 0 位。如果包含了小數秒,TIME 需要額外的存儲,TIME(1) 和 TIME(2) 需要 4 字節,TIME(3) 和 TIME(3) 需要 5 字節,TIME(5) 和 TIME(6) 需要 6 字節存儲。

時間戳類型

DATETIME(N) 和 TIMESTAMP(N) 類型可以同時存儲日期(DATE)和時間(TIME)信息,也就是時間戳。

DATETIME 類型使用 『YYYY-MM-DD hh:mm:ss[.fraction]』 格式顯示,支持範圍 『1000-01-01 00:00:00.000000』 到 『9999-12-31 23:59:59.999999』,默認 0 位小數秒,需要 5 字節存儲。如果支持小數秒,額外的存儲和 TIME(N) 類似。

TIMESTAMP 類型使用 UTC 時區進行存儲,支持範圍 『1970-01-01 00:00:01.000000』 UTC 到 『2038-01-19 03:14:07.999999』 UTC,默認 0 位小數秒,需要 4 字節存儲。如果支持小數秒,額外的存儲和 TIME(N) 類似。

例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (dt DATETIME, ts TIMESTAMP);

SET time_zone = '+00:00';
INSERT INTO t VALUES (now(), now());

mysql> SELECT * FROM t;
+-+-+
| dt | ts |
+-+-+
| 2020-09-10 14:09:57 | 2020-09-10 14:09:57 |
+-+-+
1 row in set (0.00 sec)

兩者在 UTC 時區相同,然後修改會話的時區:

mysql> SET time_zone = '+08:00';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t;
+-+-+
| dt | ts |
+-+-+
| 2020-09-10 14:09:57 | 2020-09-10 22:09:57 |
+-+-+
1 row in set (0.00 sec)

結果顯示,TIMESTAMP 類型會隨著當前時區進行調整。

DATETIME 和 TIMESTAMP 類型支持自動初始化或者更新為當前日期時間,在欄位定義時分別使用 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 屬性進行設置。例如:

DROP TABLE IF EXISTS t;
CREATE TABLE t (
id int,
dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO t(id) VALUES (1);

mysql> SELECT * FROM t;
+-+-+-+
| id | dt | ts |
+-+-+-+
| 1 | 2020-09-10 14:33:25 | 2020-09-10 22:33:25 |
+-+-+-+
1 row in set (0.00 sec)

YEAR

如果只需要存儲年份信息,可以使用 YEAR 類型。YEAR 類型佔用 1 個字節,顯示格式為 『YYYY』,範圍從 1901 到 2155,以及 0000。

JSON 數據類型

MySQL 5.7.8 開始支持原生 JSON 數據類型,可以支持更加高效的 JSON 文檔存儲和管理。原生 JSON 數據類型提供了自動的格式驗證以及優化的存儲格式,可以快速訪問文檔中的元素節點。例如:

CREATE TABLE employee_json(
emp_id INTEGER NOT NULL PRIMARY KEY,
emp_info JSON NOT NULL
);
INSERT INTO employee_json
VALUES (1, '{"emp_name": "劉備", "sex": "男", "dept_id": 1, "manager": null, "hire_date": "2000-01-01", "job_id": 1, "income": [{"salary":30000}, {"bonus": 10000}], "email": "liubei@shuguo.com"}');

mysql> INSERT INTO employee_json VALUES (2,'{"emp_name": "劉備" ');
ERROR 3140 (22032): Invalid JSON text: "Missing a comma or '}' after an object member." at position 23 in value for column 'employee_json.emp_info'.

除了 JSON 數據類型之外,MySQL 還提供了許多 JSON 處理函數和操作符,例如構造 JSON 對象的 JSON_OBJECT、JSON_ARRAY,查詢指定元素的 ->(JSON_EXTRACT)、->> (JSON_UNQUOTE + JSON_EXTRACT),將 JSON 數據轉換為 SQL 數據的 JSON_TABLE、更新 JSON 數據的 JSON_SET、JSON_INSERT、JSON_REPLACE、JSON_REMOVE 以及格式驗證的 JSON_VALID 函數。例如:

SELECT emp_id, jt.*
FROM employee_json,
JSON_TABLE(emp_info, '$'
COLUMNS (emp_name VARCHAR(50) PATH '$.emp_name',
sex VARCHAR(10) PATH '$.sex',
dept_id INTEGER PATH '$.dept_id',
manager INTEGER PATH '$.manager',
hire_date DATE PATH '$.hire_date',
job_id INTEGER PATH '$.job_id',
salary INTEGER PATH '$.income[0].salary',
bonus INTEGER PATH '$.income[1].bonus',
email VARCHAR(100) PATH '$.email')
) jt;
emp_id|emp_name|sex|dept_id|manager|hire_date |job_id|salary|bonus|email |
-|---|---|--|--||-|-||----|
1|劉備 |男 | 1| |2000-01-01| 1| 30000|10000|liubei@shuguo.com |

一般來說,JSON 欄位所需的存儲和 LONGBLOB 或者 LONGTEXT 差不多。不過,JSON 文檔的二進位編碼需要額外的存儲,包括元數據和字典信息。舉例來說,JSON 文檔中的字符串需要額外的 4 到 10 個字節存儲。除此之外,JSON 文檔的最大長度不能超過系統變量 max_allowed_packet 的限制。

空間數據類型

MySQL 支持許多包含各種幾何和地理數據的空間數據類型,包括:

GEOMETRY,任何類型的空間數據值;

POINT,X-Y 坐標系中的點;

LINESTRING,曲線,一個或多個 POINT 數據值;

POLYGON,多邊形;

GEOMETRYCOLLECTION,GEOMETRY 數據集合;

MULTILINESTRING,LINESTRING 數據集合;

MULTIPOINT,POINT 數據集合;

MULTIPOLYGON,POLYGON 數據集合。

MySQL 存儲空間數據時使用 4 字節標識 SRID(空間參照標識符),然後存儲 Well-Known Binary 格式的數據。LENGTH() 函數可以返回數據佔用的字節數。

MySQL 中的 MyISAM、InnoDB、NDB 以及 ARCHIVE 存儲引擎支持空間數據類型的存儲和處理函數,MyISAM 和 InnoDB 存儲引擎支持空間欄位的 SPATIAL 索引。

關於 MySQL 空間數據擴展,具體參考官方文檔。

選擇合適的數據類型

最後我們來看看如何選擇合適的數據類型。首先,應該滿足存儲業務數據的需求;其次,還需要考慮性能和使用方便。一般來說,先確定基本的類型:

文本數據,使用字符串類型;

數值數據,尤其是需要進行數學運算的數據,選擇數字類型;

日期和時間信息,最好使用原生的日期時間類型,也可以考慮數字類型;

文檔、圖片、音頻和視頻等,使用二進位類型;或者可以考慮存儲在文件伺服器上,然後在資料庫中存儲文件的路徑。

接下來需要進一步確定具體的數據類型。在滿足數據存儲和擴展的前提下,儘量使用更小的數據類型,可以節省一些存儲,通常性能也會更好。例如,對於一個小型公司而言,員工編號通常不會超過幾百,使用 SMALLINT 已經足夠。對於 MySQL 而言,不需要支持負數的話可以考慮 UNSIGNED 類型。

對於字符數據,一般使用 VARCHAR 類型;如果數據長度能夠確保一致,可以使用 CHAR;指定最大長度時,滿足存儲需求的前提下儘量使用更小的值。只有在普通字符串類型長度無法滿足時才考慮 TEXT 欄位類型。

如果需要存儲精確的數字,不要使用浮點數類型。對於金額,可以使用 DECIMAL(p, s);或者將數據乘以 10 的 N 次方,例如將 10.35 元存儲為整數 103500,然後在應用程式中進行處理和前端顯示轉換。

只需要年月日信息時使用 DATE 類型,例如出生日期;只需要時間信息時使用 TIME 類型,例如發車時間;選擇時間戳類型時需要注意 DATETIME 和 TIMESTAMP 的區別。

可以考慮將日期時間拆成多個數字類型存儲,或者使用 UNIX 時間戳表示;但是不要使用字符串存儲日期時間數據,它們無法支持數據的運算。例如獲得兩個日期之間的間隔,需要依賴應用程式進行轉換和處理。最好也不要使用整數類型存儲當前時間距離 1970 年 1 月 1 日的毫秒數來表示時間,這種方式在顯示時需要進行轉換,不是很方便。

對於特殊應用場景,考慮使用其他類型。例如電商的產品信息可以考慮使用 JSON 數據類型,處理地理位置信息使用空間數據類型等。

另外,如果一個欄位同時出現在多個表中,使用相同的數據類型。例如,員工表中的部門編號(dept_id)欄位與部門表的編號(dept_id)欄位保持類型一致。

總結

本文詳細分析了 MySQL 中的各種數據類型以及選擇數據類型時的一些通用的原則,使用任何數據類型之前都應該查看相關的資料庫文檔。

如果覺得文章對你有用,歡迎關注❤️、點讚👍、推薦🎁

相關焦點

  • 數據分析-mysql-入門到精通(2)
    ,能表示的範圍是從1000-01-01 到9999-12-12,初始值為0000-00-00Time    時間類型:能夠表示某個指定的時間,但是系統同樣是提供3個字節來存儲,對應的格式為:HH:ii:ss,但是mysql中的time類型能夠表示時間範圍要大的多,能表示從-838:59:59~838:59:59,在mysql中具體的用處是用來描述時間段
  • 匯總|MySQL常用數據類型
    MySQL是一種關係資料庫管理系統,關係資料庫將數據保存在不同的表中,而不是將所有數據放在一個大倉庫內,這樣就增加了速度並提高了靈活性
  • MySQL 入門常用命令大全
    > use Student;4.3.6 創建數據表命令格式:mysql> create table [表名] ( [欄位名1] [類型1] [is null] [key] [default value] [extra] [comment],...
  • mysql入門
    1、quit;2、exit;3、\q;2.2、DDL資料庫定義語言定義數據,定義資料庫表MySQL中的數據類型2.2.1、命令行界面創建資料庫命令:` ( `欄位名` 列類型 [屬性] [索引] [注釋], `欄位名` 列類型 [屬性] [索引] [注釋], ······ `欄位名` 列類型 [屬性] [索引] [注釋]) [表類型][字符集設置][注釋]表類型常規使用操作:外鍵1、建表時就添加外鍵
  • mysql資料庫1127
    stu 表名add 添加 column 列 addr要添加的列的列名  varchar(50) 要添加列的數據類型     修改表stu添加新欄位addr 欄位數據類型為varchar(50)   4.3 刪除列       desc stu;       alter table stu drop column
  • 你需要的 MySQL 基礎內容,全在這裡了!
    如圖所示的一個表格:表頭(header):每一列的名稱;列(col): 具有相同數據類型的數據的集合;行(row):每一行用來描述某個人/物的具體信息;值(value):行的具體信息, 每個值必須與該列的數據類型相同;mysql -h 127.0.0.1 -u 用戶名 -pmysql -D 所選擇的資料庫名
  • 47 張圖帶你 MySQL 進階!!!
    MEMORY 類型的表訪問速度很快,因為其數據是存放在內存中。默認使用 HASH 索引。MERGEMERGE 存儲引擎是一組 MyISAM 表的組合,MERGE 表本身沒有數據,對 MERGE 類型的表進行查詢、更新、刪除的操作,實際上是對內部的 MyISAM 表進行的。
  • 圖解 MySQL 索引,寫得實在太好了!
    我們需要找一個最合適的數據結構算法來實現查找功能。下面一起看下常見的查找策略,如下圖:如何通過B-tree快速查找數據?1.2 Hash 索引(哈希表)哈希索引是基於哈希表來實現的,只有精確匹配所有的所有列才能生效。另外,推薦大家關注下公眾號Java技術棧回復mysql可以獲取系列mysql教程。
  • 別再問我MySQL和Python怎麼操作MySQL了!
    MySQL是一種關係資料庫管理系統,關係資料庫將數據保存在不同的表中,而不是將所有數據放在一個大倉庫內,這樣就增加了速度並提高了靈活性。MySQL所使用的 SQL 語言是用於訪問資料庫的最常用標準化語言。
  • 一個小時學會MySQL資料庫
    為實現區分通常需要為表加上一個列,以存儲各個實例的唯一標識。簡而言之,第二範式就是在第一範式的基礎上屬性完全依賴於主鍵。例如:表1-1中,一個表描述了工程信息,員工信息等。這樣就造成了大量數據的重複。,關係複雜了,查詢數據變的麻煩了,編程中的難度也提高了,但是各個表中內容更清晰了,重複的數據少了,更新和維護變的更容易了,哪麼如何平衡這種矛盾呢?
  • Node - Sequelize -Mysql 入門(一)
    安裝依賴這次的基本技術棧為 express、sequelize、mysql$ yarn add express sequelize mysql2連接到數據資料庫首先確保本地存在 資料庫,由於筆者熟悉 mysql。
  • MySQL 資料庫 InnoDB 和 MyISAM 數據引擎的差別
    基本的差別為:MyISAM類型不支持事務處理等高級處理,而InnoDB類型支持。MyISAM類型的表強調的是性能,其執行數度比InnoDB類型更快,但是不提供事務支持,而InnoDB提供事務支持已經外部鍵等高級資料庫功能。MyIASM是IASM表的新版本,有如下擴展:  1. 二進位層次的可移植性。  2. NULL列索引。  3.
  • MySQL存儲引擎MyISAM與InnoDB區別總結整理
    從MySQL 5.5之後的版本中,默認的搜尋引擎變更為InnoDB。(2)基於磁碟的資源是InnoDB表空間數據文件和它的日誌文件,InnoDB 表的 大小隻受限於作業系統文件的大小,一般為 2GB。6、MyISAM與InnoDB表鎖和行鎖的解釋MySQL表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。
  • MySQL 注入攻擊與防禦
    表和欄位確定欄位數ORDER BYORDER BY用於判斷表中的欄位個數查表名以下提過幾種方式對庫中表進行查詢該變量用於限制數據的導入和導出操作,例如SELECT … INTO OUTFILE語句和LOAD_FILE()如果secure_file_priv變量為空那麼直接可以使用函數,如果為null是不能使用但在mysql的5.5.53之前的版本是默認為空,之後的版本為null,所有是將這個功能禁掉了
  • MySQL快速入門 學習筆記
    constraintCondition);創建一個名為database_name的數據表,數據表內有field1、2....等欄位,各個欄位的行級約束條件分別為各自的constraintCondition,然後再加上表級的constraintCondition。USE database_name;後續操作將在database_name資料庫中進行。
  • MySQL和PostgreSQL資料庫安全配置
    (一)、MySQL資料庫          MySQL資料庫在安裝後默認存在mysql資料庫,該資料庫為系統表所在的資料庫,所有用戶記錄在mysql資料庫的user三個權限表的用戶列(包括host、user、password三個欄位)。
  • 什麼是MySQL資料庫?看這一篇乾貨文章就夠了!
    ,然後把數據插入到一個已存在的表中。外鍵對應的是參照完整性,一個表的外鍵可以為空值,若不為空值,則每一個外鍵必須等於另一個表中主鍵的某個值。作用:保持數據的一致性,完整性。;11.多表連接查詢多表連接查詢是從多個表中獲取數據。
  • 小岑帶你看懂Mybatis欄位映射 - 駝峰式命名
    解決了資料庫類型到Java類型的轉換工作。在轉換過程中具備一定的容錯能力。其實核心就是:資料庫中的列名怎麼和對象中的欄位對應起來。資料庫中的列的類型怎麼轉換到合適的Java類型,不引起轉換失敗。今天我們先來看第一點,資料庫中的列名怎麼和對象中的欄位對應起來。
  • 性能測試 —— MySQL 基準測試
    點擊上方「芋道源碼」,選擇「設為星標」做積極的人,而不是積極廢人!
  • 商業銀行​數據倉庫中的各種表
    寬表,窄表寬表是將多個維度的信息放在一張表中,通常是指業務主題相關的指標、維度、屬性關聯在一起,組成的一張資料庫表。寬表廣泛應用於數據挖掘模型訓練前的數據準備,通過把相關欄位放在同一張表中,可以大大提高數據挖掘模型訓練過程中,數據在迭代計算時的效率問題。