MySQL 中數據類型常用的就三大類:
- 數字類型/numeric types
- 日期和時間/date and time types
- 字符類型/string (character and byte) types
另外還包含兩個沒那么常用的大類:
- 特殊類型/spatial types
- JSON
繼續之前,先來看一些單位上的約定和概念,
- M:根據具體不同的類型,其表示的意思不一樣,見下方關於這個參數的討論。
- D 用於定點及浮點數,表示小數點后有多少位。最大可能取值為 30,但不應該超過 M-2。
- fsp 適用於 TIME, DATETIME 及 TIMESTAMP。可理解秒后面的小數點位數。它應該是介於 0~6 之間的,0 表示沒有小數部分(fractin part)。默認為 0。
[]
方括號表示類型中可選的部分。
存儲字符串時指定的類型 VARCHAR(50)
中可接收一個數字作為長度,其實除了字符串類型,數字類型也是可指定該參數的,比如 INT(10)
,BIGINT(20)
。假設后續討論中這個參數使用字母 M 來表示,即上面提到的。該參數被用在不同類型上時,其表示的意思不一樣。
- 對於整形,它表示 展示寬度/display width。
- 對於定點數(fixed point)或浮點數(floating point),表示能夠存儲的總位數,即精度。
- 對於字符串,表示能夠存儲的字符串長度。
展示寬度/Display Width
那么什么是展示寬度。展示寬度這個參數具有迷惑性,它不像 CHAR(M)
中有實際意義表示能夠存儲的字符串長度,在數字類型中,它指數字展示時需要的寬度,是 MySQL 格式化時使用的。即 INT(5)
,INT(15)
,INT(25)
能夠存儲的數字范圍都是 INT
類型的范圍 -2147483648 ~ 2147483647。如果指定了 ZEROFILL
,MySQL 在返回該數字時,對於實際位數小於展示寬度的數字,將自動在左邊補零。比如列的類型為 INT(5)
,實際存儲了數字 5,返回時會得到 00005
。對於沒有指定 ZEROFILL
或實際存儲的位數大於指定的展示寬度,則不會自動補零,此時看上去沒有任何效果。
CREATE TABLE test_zero_fill
(
with_fill INT(5) UNSIGNED ZEROFILL NOT NULL PRIMARY KEY,
without_fill INT(5) UNSIGNED NOT NULL
);
mysql> INSERT INTO test_zero_fill (with_fill, without_fill) VALUES (5, 5),(123456, 123456);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from test_zero_fill;
+-----------+--------------+
| with_fill | without_fill |
+-----------+--------------+
| 00005 | 5 |
| 123456 | 123456 |
+-----------+--------------+
2 rows in set (0.00 sec)
另外,如果使用了 ZEROFILL
,該列將自動設置為 UNSIGNED
類型。
mysql> ALTER TABLE test_zero_fill ADD signed_num INT(5) signed ZEROFILL NOT NULL after without_fill;
mysql> describe test_zero_fill;
+--------------+--------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------------------+------+-----+---------+-------+
| with_fill | int(5) unsigned zerofill | NO | PRI | NULL | |
| without_fill | int(5) unsigned | NO | | NULL | |
| signed_num | int(5) unsigned zerofill | NO | | NULL | |
+--------------+--------------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
所以對於數據存儲層面來說,展示寬度其實沒什么用途。如果真的需要格式化,程序中能夠請求 MySQL 的 meta 信息以獲取到相應的展示寬度。
假如在 Node.js 中使用 mysqljs/mysql 作為數據庫連接的模塊,在執行請求時,其回調中返回的 fields
入參便包含了列相應的 meta 信息。
connection.query("SELECT * from test_zero_fill", function(
error,
results,
fields
) {
if (error) throw error;
console.log(fields);
});
fields 中包含列的 meta 信息
FieldPacket {
catalog: 'def',
db: 'data_type',
table: 'test_zero_fill',
orgTable: 'test_zero_fill',
name: 'with_fill',
orgName: 'with_fill',
charsetNr: 63,
length: 5,
type: 3,
flags: 20579,
decimals: 0,
default: undefined,
zeroFill: true,
protocol41: true
},
FieldPacket {
catalog: 'def',
db: 'data_type',
table: 'test_zero_fill',
orgTable: 'test_zero_fill',
name: 'without_fill',
orgName: 'without_fill',
charsetNr: 63,
length: 5,
type: 3,
flags: 4129,
decimals: 0,
default: undefined,
zeroFill: false,
protocol41: true
},
FieldPacket {
catalog: 'def',
db: 'data_type',
table: 'test_zero_fill',
orgTable: 'test_zero_fill',
name: 'signed_num',
orgName: 'signed_num',
charsetNr: 63,
length: 5,
type: 3,
flags: 4193,
decimals: 0,
default: undefined,
zeroFill: true,
protocol41: true
}
]
因此,在設計表時,應該關注使用哪種具體的數據類型能夠滿足數據存儲的需要,而不要被展示寬度所迷惑。
數字類型
數字類型分為有符號 SIGNED
和無符號 UNSIGNED
的情況,有符號即最前面有一位符呈位,可表示正負數。默認情況下為 SIGNED
即有符號。
整型
MySQL 中支持標准的 SQL 整型,
- INTEGER (INT)
- SMALLINT
並且擴展了一些類型:
- TINYINT
- MEDIUMINT
- BIGINT
以下是 MySQL 中支持的整型,及其對應所需存儲空間和取值范圍。
類型 | 空間 (字節) | 有符號時最小取值 | 無符號時最小取值 | 有符號時最大取值 | 無符號時最大取值 |
---|---|---|---|---|---|
TINYINT | 1 | -128 | 0 | 127 | 255 |
SMALLINT | 2 | -32768 | 0 | 32767 | 65535 |
MEDIUMINT | 3 | -8388608 | 0 | 8388607 | 16777215 |
INT | 4 | -2147483648 | 0 | 2147483647 | 4294967295 |
BIGINT | 8 | -263 | 0 | 263-1 | 264-1 |
具體到每種類型:
- TINYINT[(M)] [UNSIGNED] [ZEROFILL]:微整型,取值范圍 -128 ~ 127,無符號情況下為 0 ~ 255。
- BOOL, BOOLEAN:效果等同
TINYINT(1)
,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 |
+------------------------+
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 |
+--------------------------------+
- SMALLINT[(M)] [UNSIGNED] [ZEROFILL]:小整型。取值范圍 -32768 ~ 32767,無符號情況下為 0 ~ 65535。
- MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]:中整型。取值范圍 -8388608 ~ 8388607,無符號情況下為 0 ~ 16777215。
- INT[(M)] [UNSIGNED] [ZEROFILL]: 整型。取值范圍 -2147483648 ~ 2147483647,無符號情況下為 -2147483648 ~ 2147483647。
- INTEGER[(M)] [UNSIGNED] [ZEROFILL]:同
INT
。 - BIGINT[(M)] [UNSIGNED] [ZEROFILL]:大整型。取值范圍 -9223372036854775808 ~ 9223372036854775807,無符號情況下為 0 ~ 18446744073709551615。
關於大整型,關鍵字 SERIAL
等同於 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE
。
還記得創建表時一般需要指定一個自增的整形 ID 字段么,
CREATE TABLE table_name (id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT)
SERIAL
關鍵字其實是 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE
的別名,所以下次創建表時可直接使用該關鍵字,會省事很多。
CREATE TABLE table_name (id SERIAL PRIMARY KEY)
如果你不想要 BIGINT,SERIAL DEFAULT VALUE
是 NOT NULL AUTO_INCREMENT UNIQUE
的別名,那么可以這樣來簡寫 ID 字段:
CREATE TABLE table_name (id INT SERIAL DEFAULT VALUE PRIMARY KEY)
定點型
DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL] 定點型數字,其中 M 表示總的位數(不包含正負號及小數點),D 表示小數位數。D 為 0 則表示沒有小數部分。M 最大取值 65,默認 10;D 最大支持到 30,默認 0。所有的算術運算(+
,-
,*
,/
)都基於 65 位的 DECIMAL。
DEC[(M[,D])] [UNSIGNED] [ZEROFILL], NUMERIC[(M[,D])] [UNSIGNED] [ZEROFILL], FIXED[(M[,D])] [UNSIGNED] [ZEROFILL] 同 DECIMAL
。
定點型數字存儲精確的數字,用於准確性要求高的場合,比如涉及金錢。底層實現上,MySQL 使用二進制形式存儲該類型的值。
通常的用法如下:
salary DECIMAL(5,2)
上面示例中,salary 為一個 5 位精度兩位小數的定點型。取值范圍 -999.99 ~ 999.99。
因為 D 缺省時默認為 0,所以 DECIMAL(M)
表示 DECIMAL(M,0)
,現時,MySQL 中,M 缺省時默認為 10,所以 DECIMAL
表示 DECIMAL(10,0)
。
當實際存儲的值其小數大於指定的位數時,其精度會自動轉換成所存儲的值的精度。
浮點型
區別於 DECIMAL,浮點型存儲的數字是個近似值。內部存儲時,MySQL 為單精度使用 4 字節(bytes),雙精度使用 8 字節。
浮點型包含以下這些類型:
- FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]:小型的單精度浮點型。根據 IEEE 標准理論取值范圍 -3.402823466E+38 ~ -1.175494351E-38, 0, 1.175494351E-38 ~ 3.402823466E+38,實際的取值范圍因硬件和操作系統而異,會比理論值要小。
- M 表示總位數,D 表示小數位數。兩者省略的情況下,其值為硬件允許的最大值。比如
FLOAT(7,4)
看起來會是這個樣子:-999.9999
。 FLOAT[(M,D)
這種形式的類型不是標准的 SQL 類型,后續會廢棄掉。
- M 表示總位數,D 表示小數位數。兩者省略的情況下,其值為硬件允許的最大值。比如
- FLOAT(p) [UNSIGNED] [ZEROFILL]:是標准的 SQL 類型,p 表示精度。但 MySQL 中,根據 p 取值的不同,底層實際將其處理成別的類型。比如 0 ~ 24 時,當成 4 字節單精度 FLOAT 類型來處理,25 ~ 53 時處理成 8 字節雙精度的 DOUBLE 類型。
- DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]:雙精度浮點型。取值范圍 -1.7976931348623157E+308 ~ -2.2250738585072014E-308, 0, 2.2250738585072014E-308 ~ 1.7976931348623157E+308。同
FLOAT(M,D)
,DOUBLE(M,D)
這種形式的雙精度類型也是非標准 SQL 類型,后續會廢棄。 - DOUBLE PRECISION[(M,D)] [UNSIGNED] [ZEROFILL], REAL[(M,D)] [UNSIGNED] [ZEROFILL]:DOUBLE 的別名。
所以實際使用時,為了最大限度的兼容性,直接使用 FLOAT
,DOUBLE
,PRECISION
而不要指定精度及小數。
BIT 類型
BIT[(M)] 類型用於存儲單個狀態值,M 表示包含幾位。默認為1,最大可取 64。
該類型的值可通過 b'value'
的形式書寫,其中 value 部分以二進制的形式呈現,比如 b'111' 和 b'10000000' 分別表示 7 和 128。更加詳細的信息可參考 9.1.5 Bit-Value Literals。
如果賦值到該類型上的值小於 M 指定的位數,值左邊會補零,比如將 b'101' 存儲到類型為 BIT(6) 的列,實際會是 b'000101'。
存儲的值溢出的情況
將要存儲的值超出數字類型的范圍時,其表現跟當前設置的 SQL 模式有關。具體來說,
- 開啟 SQL 嚴格模式時,超出范圍的值會寫入失敗,MySQL 會中斷操作並且直接拋錯。
- 非嚴格模式下,MySQL 會將值裁剪到合適的大小進行寫入。即超出的情況下存成該類型能夠接收的最大值。
考察一個通過如下語句創建的表 t1
:
CREATE TABLE t1 (i1 TINYINT, i2 TINYINT UNSIGNED);
SQL 嚴格模式下,嘗試寫入一個超出范圍的值時拋錯:
mysql> SET sql_mode = 'TRADITIONAL';
mysql> INSERT INTO t1 (i1, i2) VALUES(256, 256);
ERROR 1264 (22003): Out of range value for column 'i1' at row 1
mysql> SELECT * FROM t1;
Empty set (0.00 sec)
以下是非嚴格模式下進行裁剪存儲的情況:
mysql> SET sql_mode = '';
mysql> INSERT INTO t1 (i1, i2) VALUES(256, 256);
mysql> SHOW WARNINGS;
+---------+------+---------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------+
| Warning | 1264 | Out of range value for column 'i1' at row 1 |
| Warning | 1264 | Out of range value for column 'i2' at row 1 |
+---------+------+---------------------------------------------+
mysql> SELECT * FROM t1;
+------+------+
| i1 | i2 |
+------+------+
| 127 | 255 |
+------+------+
上述表現同樣會出現在涉及到對列進行轉換修改的一些操作上,比如 ALTER TABLE
,LOAD DATA
,UPDATE
以及使用 INSERT
同時插入多行數據時。嚴格模式下會拋錯失敗,非嚴格模式下值會進行裁剪。但失敗的情況不盡相同,如果是事務類型的表,會整個全失敗,其他情況根據具體的值會部分成功,部分失敗。
進行數字計算時如果有溢出,也會拋錯,比如對於 BIGINT 其最大值為 9223372036854775807,因為 MySQL 中默認對數字類型是有符號類型,如下操作會拋錯,
mysql> SELECT 9223372036854775807 + 1;
ERROR 1690 (22003): BIGINT value is out of range in '(9223372036854775807 + 1)'
對於上述情況,可顯式將 被操作數進行類型轉換,轉成無符號的 BIGINT:
mysql> SELECT CAST(9223372036854775807 AS UNSIGNED) + 1;
+-------------------------------------------+
| CAST(9223372036854775807 AS UNSIGNED) + 1 |
+-------------------------------------------+
| 9223372036854775808 |
+-------------------------------------------+
通過帶上小數后,轉成 DECIMAL 也能修正上面的錯誤,因為 DECIMAL 比整形要大,
mysql> SELECT 9223372036854775807.0 + 1;
+---------------------------+
| 9223372036854775807.0 + 1 |
+---------------------------+
| 9223372036854775808.0 |
+---------------------------+
兩數相減時,其中一個為無符號數,得出的結果默認為也為無符號。所以如果想減之后結果是負數,則會拋錯。
mysql> SET sql_mode = '';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0 as unsigned) - 1)'
除非開啟了 NO_UNSIGNED_SUBTRACTION
:
mysql> SET sql_mode = 'NO_UNSIGNED_SUBTRACTION';
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
+-------------------------+
| CAST(0 AS UNSIGNED) - 1 |
+-------------------------+
| -1 |
+-------------------------+
總結
對於整型或浮點型,可指定 AUTO_INCREMTN
屬性。指定該屬性性,將不能接收負值。同時 CHECK
屬性與該屬性沖突,也不能同時使用。但對於 FLOAT 和 DOUBLE,AUTO_INCREMENT
屬性的支持將逐漸廢棄掉,實際使用時盡量避免。
對於需要精確數值的場合,使用 DECIMAL,比如涉及金錢的情況。
對於整形,展示寬度不是其存儲的值范圍,只用來格式化。