5分钟搞懂MySQL数据类型

之浮点型--FLOAT类型

MySQL支持的浮点类型有两个:FLOAT和DOULE。单精度的FLOAT占用4个字节的空间,双精度的DOUBLE占用8个字节的空间。

对于FLOAT,MySQL支持非标准语法,如:FLOAT(M,D)、REAL(M,D)或DOUBLE PRECISION(M,D)。这里(M,D)分别是数值位数和小数点后的位数。例如FLOAT(7,4)的最大值即是999.9999。MySQL会对保存的数值进行四舍五入,例如插入999.00009到类型定义为FLOAT(7,4)的列时,保存的近似值是999.0001。

mysql> create table t1(v float(7,4));
Query OK, 0 rows affected, 1 warning (0.13 sec)
mysql> insert into t1 values (999.00009);
Query OK, 1 row affected (0.02 sec)
mysql> select * from t1;
+----------+
| v        |
+----------+
| 999.0001 |
+----------+
1 row in set (0.01 sec)

浮点数中保存的是近似值而非精确值,如果把它当成精确值处理,可能会造成一定的困扰。SQL语句里的浮点数可能会跟内部展现的不同,这个示例还是很能说明问题:

mysql> CREATE TABLE t1 (i INT, d1 DOUBLE, d2 DOUBLE);
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO t1 VALUES (1, 101.40, 21.40), (1, -80.00, 0.00),
    -> (2, 0.00, 0.00), (2, -13.20, 0.00), (2, 59.60, 46.40),
    -> (2, 30.40, 30.40), (3, 37.00, 7.40), (3, -29.60, 0.00),
    -> (4, 60.00, 15.40), (4, -10.60, 0.00), (4, -34.00, 0.00),
    -> (5, 33.00, 0.00), (5, -25.80, 0.00), (5, 0.00, 7.20),
    -> (6, 0.00, 0.00), (6, -51.40, 0.00);
Query OK, 16 rows affected (0.01 sec)
Records: 16  Duplicates: 0  Warnings: 0
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1 GROUP BY i HAVING a <> b;
+------+-------+------+
| i    | a     | b    |
+------+-------+------+
|    1 |  21.4 | 21.4 |
|    2 |  76.8 | 76.8 |
|    3 |   7.4 |  7.4 |
|    4 |  15.4 | 15.4 |
|    5 |   7.2 |  7.2 |
|    6 | -51.4 |    0 |
+------+-------+------+
6 rows in set (0.00 sec)

前5条记录a/b的值肉眼看起来完全相同,但为什么没能被having条件过滤掉呢,因为a/b两列只是看起来没有区别,而实际上确实不同,只不过数值的差异是在小数点10位以后,具体保存的数值基于计算机体系架构或编译版本和优化级别,比如说不同CPU类型计算的浮点型数值可能不同。

正确的执行浮点数值比较的方式,首先确定一个可接受的数值比较的精度值,比如说我们能够接受浮点数的精度在千分之一内就认为相同,那么我们可以将比较语句做些修改:

mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1 GROUP BY i HAVING ABS(a - b) > 0.0001;
+------+-------+------+
| i    | a     | b    |
+------+-------+------+
|    6 | -51.4 |    0 |
+------+-------+------+
1 row in set (0.00 sec)

如果D1/D2两列是DECIMAL数值类型,那么上述SELECT语句应该只返回一条记录。所以,如果确实对数值的存储精度没有要求,可以考虑使用FLOAT类型,否则还是使用精确数值的数据类型较好。

另外,从MySQL8.0.17版本开始,非标的FLOAT(M,D)和DOUBLE(M,D)语法即已被废弃,并且FLOAT和DOUBLE类型的列不再支持AUTO_INCREMENT属性,使用时也要注意。