最近在準備給開發作一個mysql數據庫開發規範方面培訓,一步一步來,結合在生產環境發現的數據庫方面的問題,從幾個經常使用的數據類型提及。html
它們都是(精確)整型數據類型,可是佔用字節數和表達的範圍不一樣。首先沒有這個表就說不過去了:java
Type | Storage | Minimum Value | Maximum Value |
---|---|---|---|
(Bytes) | (Signed/Unsigned) | (Signed/Unsigned) | |
TINYINT | 1 | -128 | 127 |
0 | 255 | ||
SMALLINT | 2 | -32768 | 32767 |
0 | 65535 | ||
MEDIUMINT | 3 | -8388608 | 8388607 |
0 | 16777215 | ||
INT | 4 | -2147483648 | 2147483647 |
0 | 4294967295 | ||
BIGINT | 8 | -9223372036854775808 | 9223372036854775807 |
0 | 18446744073709551615 |
只須要知道對應類型佔多少字節就能推算出範圍了,好比int佔 4 bytes,即4*8=32bits,大約10位數字,也能理解爲何int默認顯示位數是11。mysql
遇到比較多的是tinyint和bigint,tinyint通常用於存放status,type這種數值小的數據,不夠用時可能會用smallint。bigint通常用於自增主鍵。sql
爲了不數據庫被過分設計,布爾、枚舉類型也採用tinyint。數據庫
還有一點也是常常被提到的關於 int(M) 中M的理解,int型數據不管是int(4)仍是int(11),都已經佔用了 4 bytes 存儲空間,M表示的只是顯示寬度(display width, max value 255),並非定義int的長度。編程
例如:segmentfault
mysql> CREATE TABLE `tc_integer` ( `f_id` bigint(20) PRIMARY KEY AUTO_INCREMENT, `f_type` tinyint, `f_flag` tinyint(1), `f_num` smallint(5) unsigned ZEROFILL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; mysql> desc tc_integer; +----------------+-------------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+-------------------------------+------+-----+---------+----------------+ | f_id | bigint(20) | NO | PRI | NULL | auto_increment | | f_type | tinyint(4) | YES | | NULL | | | f_flag | tinyint(1) | YES | | NULL | | | f_num | smallint(5) unsigned zerofill | YES | | NULL | | +----------------+-------------------------------+------+-----+---------+----------------+ 4 rows in set (0.01 sec)
插入幾條數據看一下:
<!-- more -->工具
mysql> insert into tc_integer values(1, 1, 1, 1); Query OK, 1 row affected (0.02 sec) mysql> insert into tc_integer values(9223372036854775808, 127, 127, 65535); Query OK, 1 row affected, 1 warning (0.01 sec) mysql> show warnings; +---------+------+-----------------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------------+ | Warning | 1264 | Out of range value for column 'f_id' at row 1 | +---------+------+-----------------------------------------------+ 1 row in set (0.00 sec) mysql> select i.*, length(i.f_flag) as len_flag from tc_integer i; +---------------------+--------------+---------------+----------------+----------+ | f_id | f_type | f_flag | f_num | len_flag | +---------------------+--------------+---------------+----------------+----------+ | 1 | 1 | 1 | 00001 | 1 | | 9223372036854775807 | 127 | 127 | 65535 | 3 | +---------------------+--------------+---------------+----------------+----------+ 2 rows in set (0.00 sec) mysql> select * from tc_integer where f_num=' 01' and f_num=1 and f_num=f_flag; +------+--------------+---------------+----------------+ | f_id | f_type | f_flag | f_num | +------+--------------+---------------+----------------+ | 1 | 1 | 1 | 00001 | +------+--------------+---------------+----------------+ 1 row in set (0.00 sec)
上面的實驗說明了幾個問題:.net
f_id列插入比最大值還大的數,出現warnings,而且最終的值自動變成 9223372036854775807 。這個坑曾經在遷移到阿里RDS時遇到過,他們的遷移工具是java寫的,結果咱們的主鍵值大於java INTEGER裏面的最大限制,致使 duplicate key問題。設計
f_flag的顯示寬度爲1,但並不影響更多位數的顯示。也證明了tinyint(1)並不像char(1)那樣限制存儲長度
f_num定義成無符號的zerofill類型,能存儲的最大數值是65535,而signed纔是32767。(當列上使用zerofill時,unsigned會自動加上)
zerofill的做用是在顯示檢索結果的時候,左邊用0補齊到display width,實際存儲時不補0的,僅做爲返回結果meta data的一部分。查詢的條件值忽略0和空格
length()在numeric類型中做用於char_length()同樣,由於字節數已經固定了。
zerofill的使用可能會在複雜join時由於瞭解不夠深刻而帶來問題,因此最終的結論也很簡單:除非極端的特殊須要,儘可能不用zerofill,建表時這類int無需指定 (11) 這樣的顯示寬度。
MySQL使用DECIMAL
類型去存儲對精度要求比較高的數值,好比金額,也叫定點數,decimal在mysql內存是以字符串存儲的。聲明語法是DECIMAL(M,D)
,佔用字節 M+2 bytes。M是數字最大位數(精度precision),範圍1-65;D是小數點右側數字個數(標度scale),範圍0-30,但不得超過M。
好比定義DECIMAL(7,3)
:
能存的數值範圍是 -9999.999 ~ 9999.999,佔用9個字節
123.12 -> 123.120,由於小數點後未滿3位,補0
123.1245 -> 123.125,小數點只留3位,多餘的自動四捨五入截斷
12345.12 -> 保存失敗,由於小數點未滿3位,補0變成12345.120,超過了7位。嚴格模式下報錯,非嚴格模式存成9999.999
MySQL使用FLOAT
和DOUBLE
來表示近似數值類型,這是由於十進制0.1在電腦裏用二進制是沒法精確表示的,只能儘量的接近。
單精度浮點數float佔4字節,float標準語法容許經過FLOAT(M)
的形式指定精度,可是這個精度值M只是決定存儲大小: 0-23與默認不指定效果相同,24-53就變成雙精度的DOUBLE
了。
float還有非MySQL本身實現的非標準語法FLOAT(M,D)
,表明最多存儲M個數字長度,其中小數點後數字個數爲D。效果與 DECIMAL(M,D)很類似。
double 和 float 的區別是double精度高,有效數字16位(float精度7位)。但double消耗內存是float的兩倍,佔8字節,double的運算速度比float慢得多。
msyql> create table tc_float(fid int primary key auto_increment,f_float float, f_float10 float(10), f_float25 float(25), f_float7_3 float(7,3), f_float9_2 float(9,2), f_float30_3 float(30,3), f_decimal9_2 decimal(9,2)); mysql> insert into tc_float(f_float,f_float10,f_float25) values(123456,123456,123456); mysql> insert into tc_float(f_float,f_float10,f_float25) values(1234567.89,12345.67,1234567.89); mysql> select * from tc_float; +-----+----------+-----------+------------+------------+------------+-------------+--------------+ | fid | f_float | f_float10 | f_float25 | f_float7_3 | f_float9_2 | f_float30_3 | f_decimal9_2 | +-----+----------+-----------+------------+------------+------------+-------------+--------------+ | 1 | 123456 | 123456 | 123456 | NULL | NULL | NULL | NULL | | 2 | 1234570 | 12345.7 | 1234567.89 | NULL | NULL | NULL | NULL | +-----+----------+-----------+------------+------------+------------+-------------+--------------+
能夠看到float與float(10)是沒區別的,float默認能精確到6位有效數字
mysql> insert into tc_float(f_float9_2,f_decimal9_2) values(123456.78,123456.78); mysql> insert into tc_float(f_float9_2,f_decimal9_2) values(1234567.1,1234567.125); Query OK, 1 row affected, 1 warning (0.00 sec) mysql> show warnings; +-------+------+---------------------------------------------------+ | Level | Code | Message | +-------+------+---------------------------------------------------+ | Note | 1265 | Data truncated for column 'f_decimal9_2' at row 1 | +-------+------+---------------------------------------------------+ 1 row in set (0.00 sec) mysql> select * from tc_float; +-----+----------+-----------+------------+------------+------------+-------------+--------------+ | fid | f_float | f_float10 | f_float25 | f_float7_3 | f_float9_2 | f_float30_3 | f_decimal9_2 | +-----+----------+-----------+------------+------------+------------+-------------+--------------+ | 6 | NULL | NULL | NULL | NULL | 123456.78 | NULL | 123456.78 | | 9 | NULL | NULL | NULL | NULL | 1234567.12 | NULL | 1234567.13 | +-----+----------+-----------+------------+------------+------------+-------------+--------------+ mysql> insert into tc_float(f_float7_3) values(12345.1); ERROR 1264 (22003): Out of range value for column 'f_float7_3' at row 1
float(9,2)與decimal(9,2)是很像的,並無前面提到24位一下6位有效數字的限制
他們倆之間的差異就在精度上,f_float9_2本應該是 1234567.10,結果小數點變成 .12 。f_decimal9_2由於標度爲2,因此 .125 四捨五入成 .13
將 12345.1 插入f_float7_3列,由於轉成標度3時 12345.100,整個位數大於7,因此 out of range 了
另外在編程中應儘可能避免作浮點數的比較,不然可能會致使一些潛在的問題。
堅定不容許使用float去存money,使用decimal更加穩妥,但使用decimal作除法依舊會產生浮點型,因此特殊狀況請考慮使用整型,貨幣單位使用 分 ,或者除法在最後進行。
本文連接地址:http://seanlook.com/2016/04/29/mysql-numeric-int-float/