理解 MySQL 中的數字型別
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_fillINT(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: 2Duplicates: 0Warnings: 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,比如涉及金錢的情況。
對於整形,展示寬度不是其儲存的值範圍,只用來格式化。