25.9 MySQL 数据类型

image-20240326205406860

1. 数据类型


MySQL支持多种数据类型, 每种数据类型都有其特定的用途和存储要求, 常用类型如下:
* 1. 整形(Integer Types): 主要用于存储没有小数部分的数字, 即整数.
     它们非常适合存储如用户ID, 订单数量, 年龄等整数值.
     MySQL提供了多种整形数据类型, 以满足不同范围和存储需求,

* 2. 浮点型(Floating-Point Types): 用于存储带有小数点的数字, 即浮点数或双精度数.
     这些数据类型特别适用于需要小数表示的场景, 如价格, 重量, 高度等.

* 3. 位数据类型(Bit Types): 用于存储固定长度的位串.
     这种数据类型在处理需要精确控制每个位的值时非常有用, 如在某些硬件或网络通信场景中.

* 4. 字符串类型(String Types): 用于存储文本数据.
     MySQL提供了多种字符串数据类型, 以满足不同长度的文本存储需求.

* 5. 二进制数据类型(Binary Types): 二进制数据类型用于存储二进制数据, 如图像, 音频或视频文件.

* 6. 枚举和集合类型(Enumeration and Set Types):
     枚举类型(ENUM)允许在预定义的值列表中选择一个值, 适用于那些只能取固定集合中的某个值的情况.
     集合类型(SET)则允许从预定义的值列表中选择多个值, 每个值作为集合的一个元素.

* 7. 日期和时间类型(Date and Time Types): 日期和时间类型用于存储日期, 时间或日期时间组合的值.

* 8. JSON数据类型: MySQL 5.7.8及更高版本引入了JSON数据类型, 允许以JSON格式直接在MySQL表中存储和检索数据.

* 9. 空间数据类型(Spatial Types): 空间数据类型用于存储地理空间数据, 如点, 线, 多边形等.

在设计和使用MySQL数据库时, 充分了解和利用这些特性和数据类型, 可以帮助你更有效地管理和查询数据.

2. 整数类型


2.1 存储范围

在MySQL中, 整形数据通常用于存储没有小数部分的数值.
整数类型描述范围
TINYINT非常小的整数有符号范围: -128 到 127.
无符号范围: 0 到 255.
SMALLINT小的整数有符号范围: -32,768 到 32,767.
无符号范围: 0 到 65,535.
MEDIUMINT中等大小的整数有符号范围: -8,388,608 到 8,388,607.
无符号范围: 0 到 16,777,215.
INT 或 INTEGER标准整数有符号范围: -2,147,483,648 到 2,147,483,647.
无符号范围: 0 到 4,294,967,295.
BIGINT大整数有符号范围: -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807.
无符号范围: 0 到 18,446,744,073,709,551,615.
整数类型的可选属性主要有三个, 它们分别是:
* 1. 显示宽度(M): 这个属性用于指定整数的显示宽度, 其取值范围是从0255.
* 2. UNSIGNED: 这个属性表示整数是无符号的, 也就是说, 它只能是非负整数.
* 3. ZEROFILL: 这个属性通常与显示宽度一起使用. 当整数的位数少于指定的显示宽度时, ZEROFILL会用0来填充剩余的宽度.
这些属性可以在定义整数类型的字段时使用, 以便更好地控制整数的显示和存储方式.
但是, 需要注意的是, 这些属性通常只影响整数的显示方式, 而不会限制其实际的值范围.

2.2 显示长度

显示宽度(M): 用于指定整数的显示宽度, 其取值范围是从0255.
例如, INT(5)表示一个显示宽度为5的整数类型.
当数据宽度小于5位的时候在数字前面需要用字符填满宽度.
该项功能需要配合'ZEROFILL'使用, 表示用'0'填满宽度, 否则指定显示宽度无效.

需要注意的是, 这个显示宽度并不会限制整数的实际值范围, 它只是在整数的显示时起到作用.
然而, 从MySQL 8.0.17开始, 整数数据类型的显示宽度属性已经不再推荐使用.
-- MySQL 5.7中显示宽度:
-- 创建数据库
mysql> CREATE DATABASE db0;
Query OK, 1 row affected (0.01 sec)

-- 使用数据库
mysql> USE db0;
Database changed

-- 创建表格:
mysql> CREATE TABLE test_int1 ( x TINYINT, y SMALLINT, z MEDIUMINT, m INT, n BIGINT);
Query OK, 0 rows affected (0.01 sec)

-- 查看表结构:
mysql> DESC test_int1;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| x     | tinyint(4)   | YES  |     | NULL    |       |
|  y   | smallint(6)  | YES  |     | NULL    |       |
|  z   | mediumint(9) | YES  |     | NULL    |       |
|  m   | int(11)      | YES  |     | NULL    |       |
|  n   | bigint(20)   | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
5 rows in set (0.01 sec)
TINYINT有符号数和无符号数的取值范围分别为-128~1270~255, 由于负号占了一个数字位.
因此, TINYINT默认的显示宽度为4.
同理, 其他整数类型的默认显示宽度与其有符号数的最小值的宽度相同.
-- MySQL 8.1 中不显示宽度:
mysql> USE db0;
Database changed

-- 创建表格:
mysql> CREATE TABLE test_int1 ( x TINYINT, y SMALLINT, z MEDIUMINT, m INT, n BIGINT);
Query OK, 0 rows affected (0.01 sec)

-- 查看表结构:
mysql> desc test_int1;
+-------+-----------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+-------+
| x     | tinyint   | YES  |     | NULL    |       |
|  y   | smallint  | YES  |     | NULL    |       |
|  z   | mediumint | YES  |     | NULL    |       |
|  m   | int       | YES  |     | NULL    |       |
|  n   | bigint    | YES  |     | NULL    |       |
+-------+-----------+------+-----+---------+-------+
5 rows in set (0.02 sec)
显示长度需要配合'ZEROFILL'使用, 表示用'0'填满宽度, 否则指定显示长度无效.
* M参数只在整形中是显示宽度, 不限制整数的范围或存储大小. 其他类型中长度确实限制了可以存储的字符数.
-- 创建表:
mysql> CREATE TABLE test_int2(
    f1 INT,
    f2 INT(5),  -- 需要搭配ZEROFILL否则看不出效果.
    f3 INT(5) ZEROFILL  -- 显示长度为5, 如果长度不够使用0填充.
);
Query OK, 0 rows affected, 3 warnings (0.01 sec)
下面是对这个SQL语句的详细解释:
* 1. f1 INT: 这是一个标准的整数字段, 可以存储从-2,147,483,6482,147,483,647之间的值.
     显示宽度在这里没有指定, 因此, 存储的数值会按照其实际大小来显示.
     
* 2. f2 INT(5): 这也是一个整数字段, 但是这里指定了一个显示宽度为5.
     这意味着, 当你查询这个字段的值时, 它会在输出中占用至少5个字符的宽度.
     但是, 请注意, 这个显示宽度并不限制你可以存储的整数的范围或大小.
     也就是说, 你可以存储一个大于99999的数, 只是当查询时, 它不会按照你指定的宽度来显示.
     要看到INT(5)的效果, 通常你需要配合ZEROFILL选项, 这样当数值的位数不足5位时, 会在其前面填充0, 直到达到5个字符的宽度.

* 3. f3 INT(5) ZEROFILL: 这个字段与f2类似, 也是一个整数字段并指定了显示宽度为5, 并使用了ZEROFILL选项.
     当使用ZEROFILL时, 如果存储的数值的位数少于指定的显示宽度(在这里是5), MySQL会在该数值的前面填充0, 直到达到指定的宽度.
     例如, 如果你存储数值42, 它会在查询时显示为00042.
另外. 当你使用ZEROFILL时, MySQL会自动为字段添加UNSIGNED属性, 这意味着该字段只能存储非负整数.
-- 查看表结构
mysql> DESC test_int2;
+-------+--------------------------+------+-----+---------+-------+
| Field | Type                     | Null | Key | Default | Extra |
+-------+--------------------------+------+-----+---------+-------+
| f1    | int                      | YES  |     | NULL    |       |
| f2    | int                      | YES  |     | NULL    |       |
| f3    | int(5) unsigned zerofill | YES  |     | NULL    |       |  -- 自动设置了unsigned
+-------+--------------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

-- 插入数据:
mysql> INSERT INTO test_int2(f1, f2, f3) VALUES(1, 123, 123);
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test_int2(f1, f2) VALUES(123456, 123456);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO test_int2(f1, f2, f3) VALUES(123456, 123456, 123456);
Query OK, 1 row affected (0.00 sec)


mysql> SELECT * FROM test_int2;
+--------+--------+--------+
| f1     | f2     | f3     |
+--------+--------+--------+
|      1 |    123 |  00123 |  -- 使用0填充
| 123456 | 123456 |   NULL |
| 123456 | 123456 | 123456 |
+--------+--------+--------+
3 rows in set (0.00 sec)

2.3 无符号

UNSIGNED属性: 在MySQL中用于定义非负整数类型.
当为整数类型加上UNSIGNED修饰符时, 它意味着该字段只能存储非负的整数值, 其最小取值为0.
这样的设置特别适用于那些应用场景中, 我们知道某个字段的数值永远不会是负数, 例如年龄, 库存数量等.

对于INT类型, 如果没有明确指定显示宽度, 其默认显示宽度为INT(11).
而当INT类型被设置为UNSIGNED时, 由于非负整数的范围是正数加上0, 其最大值会比有符号的INT类型小, 因此默认显示宽度会变为INT(10).
这是因为无符号整数的最大值'4294967295'需要10位数字来完整显示, 
而有符号的INT类型最大值'2147483647'需要10位数字, 最小值'-2147483648'需要11位数字,
因此默认宽度设置为11位以确保所有值都能被完整显示.

在定义数据库表结构时, 通过合理地使用UNSIGNED属性, 可以确保数据的完整性和准确性, 同时优化存储和查询性能.
当然, 在选择是否使用UNSIGNED属性时, 还需要根据具体的应用场景和需求来权衡.
-- MySQL 5.7 中.
-- 创建表格:
mysql> CREATE TABLE test_int3(
    f1 INT UNSIGNED
);

-- 查看表结构:
mysql> desc test_int3;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| f1    | int(10) unsigned | YES  |     | NULL    |       |
+-------+------------------+------+-----+---------+-------+
1 row in set (0.00 sec)

2.4 填充

ZEROFILL  MySQL 中的一个属性, 它主要用于整型列, 并自动为这些列添加 UNSIGNED 属性.
使用 ZEROFILL 的主要目的是在显示数值时, 如果数值的位数少于定义的列宽度( int(M) 中的 M),
则在该数值的左侧用零填充至定义的宽度.

这里有几个关键点需要注意:
* 1. int(M), 必须和UNSIGNED ZEROFILL一起使用才有意义.

* 2. 自动添加 UNSIGNED: 如果指定了列为 ZEROFILL, MySQL 会自动为这一列添加 UNSIGNED 属性, 这意味着该列只能存储非负整数.

* 3. 左侧零填充: 如果数值的位数少于定义的列宽度 M, MySQL 会在显示时用零填充至 M .
     例如, 如果列定义为 INT(5) ZEROFILL, 而插入的值为 42, 则在查询结果中该值会显示为 00042.

* 4. 存储空间和 M 的关系:  int(M) , M 并不表示存储空间的大小.
      不论 M 的值是多少(比如 int(3), int(4)  int(8)), INT 类型在磁盘上始终占用 4 个字节( 32 )的存储空间.
      因此, M 仅仅是一个显示宽度, 用于控制数值的显示格式.

* 5. 超过 M 位的处理: 如果整数值的位数超过定义的 M , MySQL 会按照实际位数存储该值, 并且在显示时也不会再用零填充至 M .
     例如, 对于 INT(5) ZEROFILL, 如果插入的值为 123456, 它会被完整存储, 并且在查询时显示为 123456, 
     而不是 00123456 (因为实际位数已经超过了 5 ).

* 6. ZEROFILL 的使用场景: 在某些特定的应用场景下, 比如需要固定宽度的数字字符串输出(如某些财务或编号系统),
     ZEROFILL 可以用来确保输出格式的一致性.
     但在大多数情况下, 可能并不需要用到这个属性, 因为它主要影响的是显示格式, 而非数据的实际存储或计算.


总结来说, ZEROFILL  MySQL 中主要用于整型列的显示格式化, 通过在数值左侧填充零来确保输出的固定宽度.
但它并不改变数据的存储方式或大小, 也不参与计算.
在使用时, 应明确其用途和限制, 以避免不必要的混淆或误解.

2.5 使用场景

以下是各个整型字段在实际应用中的适用场景解释:
* 1. TINYINT: 适用于那些取值范围很小且固定的枚举数据.
     例如, 性别字段(通常使用0表示女性, 1表示男性), 或者一些状态标志(如是否激活, 是否删除等).
     由于其取值范围有限, 它非常适用于这种简单且固定的枚举情况.
     
* 2. SMALLINT: 适用于较小范围的统计数据或者其他场景, 其中不需要非常大的数值范围.
     例如, 一个小型工厂的库存数量, 一个学校的学生数量等.
     SMALLINT 提供了足够的范围来处理这些相对较小的数值, 同时不会占用太多的存储空间.
     
* 3. MEDIUMINT: 适用于需要较大整数范围但又不至于使用 INT  BIGINT 的情况.
     例如, 一个中等规模车站的每日客流量, 一家小型公司的员工数量等.
     MEDIUMINT 在保证存储空间相对紧凑的同时, 提供了足够的范围来满足这些需求.
     
* 4. INT  INTEGER: 是最常用的整型字段, 其取值范围足够大, 可以满足大多数应用的需求.
     例如, 商品编号, 订单编号, 用户ID等, 由于其广泛的应用和适中的存储空间占用, INT 是很多开发者的首选.

* 5. BIGINT: 用于处理特别巨大的整数, 通常是在数据规模非常大或者需要处理高并发交易等场景下使用.
     例如, 大型电商平台的双十一交易量, 大型门户网站的点击量, 金融机构的衍生产品持仓量等.
     BIGINT 提供了非常大的取值范围, 可以确保这些大量级的数据能够被准确存储和处理.

2.6 选择类型

在选择整数类型时, 我们需要仔细权衡存储空间和可靠性之间的平衡.
一方面, 使用占用字节数较少的整数类型可以有效节省存储空间, 这有助于降低存储成本并提高性能;
另一方面, 若所选的整数类型取值范围太小, 可能无法满足实际应用的需求, 一旦数据超出范围, 就可能导致系统错误, 严重影响可靠性.

以商品编号为例, 由于客户门店中的商品种类繁多, 且新旧商品不断迭代, 如果采用 SMALLINT 类型来存储编号,
虽然节省了存储空间, 但由于其最大取值范围仅为65535, 很难保证不会超出范围.
相反, 使用 INT 类型虽然会占用更多的存储空间, 但它能确保足够的取值范围, 从而避免数据超出范围导致系统可靠性问题.

在实际工作中, 系统故障带来的成本往往远超过增加几个字段存储空间所产生的成本.
因此, 在选择整数类型时, 我们应首先确保数据不会超过取值范围, 确保系统的稳定性和可靠性.
在此基础上, 我们再去考虑如何通过合理的类型选择来优化存储空间的使用.
总的来说, 我们应优先考虑可靠性, 再寻求存储空间的优化.

2.7 使用示例

如果尝试在MySQL中插入一个超出整型字段范围的值, 会遇到错误.
MySQL会拒绝这个插入操作, 并返回一个错误消息, 告诉你插入的值超出了列的数据类型所允许的范围.
例如, 如果你有一个TINYINT字段, 它只允许的范围是从-128127(有符号)或从0255(无符号),
而你尝试插入一个值为256或更大的数(对于有符号的情况, 是尝试插入-129或更小的数), MySQL会返回类似以下的错误:
ERROR 1264 (22003): Out of range value for column 'your_column' at row 1.
-- 创建表格:
mysql> CREATE TABLE test_int4(f1 TINYINT);
Query OK, 0 rows affected (0.02 sec)

-- 插入范围内的值:
mysql> INSERT INTO test_int4 VALUES(-128), (127);
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 查看数据:
mysql> SELECT * FROM test_int4;
+------+
| f1   |
+------+
| -128 |
|  127 |
+------+
2 rows in set (0.00 sec)

-- 插入超出范围的值:
mysql> INSERT INTO test_int4 VALUES(-129);
ERROR 1264 (22003): Out of range value for column 'f1' at row 1

3. 浮点类型


3.1 存储范围

在MySQL中, 浮点型数据主要用于存储带有小数部分的数值.
* 浮点型数据不支持无符号(unsigned)属性的.
浮点型数据描述范围
FLOAT单精度浮点数占用4个字节, 取值范围大约为: -3.4E38 到 3.4E38.
有效数字位数大约为6~7位.
DOUBLE双精度浮点数占用8个字节, 取值范围大约为: -1.8E308 到 1.8E308.
有效数字位数大约为15位.
DECIMAL, DEC, NUMERIC精确浮点数取值范围由用户定义的精度和小数位数决定.
提供精确的数值表示, 适用于需要高精度计算的场景, 如财务计算.
科学计数法中, 字母"e""E"通常用来表示10的幂次.
: 数字"-3.4E38", "E38"表示的是1038次方 = -3.4 × (10^38) = -3400000000000000000000000000000000000000000000.
需要注意的是, FLOAT和DOUBLE类型的范围是指数的范围, 实际的数值范围会受到尾数的影响.
另外, 由于FLOAT和DOUBLE是近似值表示, 它们可能会存在舍入误差, 
因此在需要精确计算的场合(如金融计算), 通常推荐使用DECIMAL类型.

DECIMAL类型允许用户指定数值的精度和小数位数, 从而确保数值的精确性.
用户可以根据具体需求来定义DECIMAL类型的精度和小数位数, 以满足不同的精度要求.

3.2 REAL类型

在MySQL中, REAL类型的确是一个特殊的类型, 它的行为取决于当前的SQL模式设置.

在默认情况下, REAL被解释为DOUBLE类型, 即双精度浮点数.
这意味着当你定义一个字段为REAL类型时, MySQL实际上会按照DOUBLE类型来存储数据, 使用8个字节(64)的存储空间.

然而, 当你启用了SQL模式中的'REAL_AS_FLOAT'选项时, MySQL会将REAL类型解释为FLOAT类型, 即单精度浮点数.
这样, 定义为REAL类型的字段将按照FLOAT类型来存储数据, 使用4个字节(32)的存储空间.

这种灵活性使得MySQL能够适应不同的使用场景和性能需求.
通过启用或禁用'REAL_AS_FLOAT'选项, 你可以根据需要选择使用单精度还是双精度浮点数来存储数据.

需要注意的是, 启用或禁用'REAL_AS_FLOAT'选项可能会影响到跨数据库迁移时的数据一致性和兼容性.
因此, 在进行数据库迁移或与其他数据库系统进行交互时, 应该特别注意SQL模式的设置, 并确保在不同系统中使用相同的类型解释.
* 没必要去修改!!!

3.3 精度说明

MySQL的浮点型精度问题主要源于浮点数的存储和计算方式.
浮点数在计算机中是以二进制形式表示的, 由于二进制无法精确表示所有的十进制小数, 因此在进行浮点数的运算时, 可能会产生精度误差.

MySQL提供了FLOAT, DOUBLE和DECIMAL等数据类型用于存储小数.
其中, FLOAT和DOUBLE是浮点型数据, 而DECIMAL是定点数类型.
浮点型数据在存储时可能会产生精度损失, 而DECIMAL类型则可以指定精度和小数位数, 从而避免精度问题.
MySQL支持使用 FLOAT(M,D)  DOUBLE(M,D) 这样的非标准语法来定义浮点列. 
这里的 M  D 分别代表精度和标度.
然而, 重要的是要理解这种语法并不会严格地限制存储在列中的值的范围或精度.
它更多的是一个建议, 告诉 MySQL 如何显示和近似数值.
* 1. M (精度): 这代表总共的位数, 包括整数部分和小数部分.
* 2. D (标度): 这代表小数点后的位数.
例如, FLOAT(7,4)可以存储最大为 999.9999 的值, 因为它允许总共 7 位数字, 其中 4 位在小数点之后.

由于 FLOAT(M,D)  DOUBLE(M,D) 的这种语法并不是 SQL 标准的一部分, 因此它可能在其他数据库系统中不被支持.
如果你计划在不同的数据库系统之间迁移数据, 最好避免使用这种语法, 以确保兼容性和一致性.
MySQL 在处理 FLOAT  DOUBLE 类型时, 会基于是否显式设置了精度 (M,D) 来执行相应的检查.

以下是具体的处理方案:
* 1. 整数部分超出范围: 如果尝试插入的数值的整数部分超出了列定义的范围, 无论是否显式设置了(M,D), MySQL都会报错并拒绝插入.
     这是因为整数部分的范围是由数据类型本身决定的, 与是否设置精度无关.

* 2. 小数部分超出范围: 如果尝试插入的数值的小数部分超出了列定义的D值, MySQL 会尝试进行四舍五入.

* 3. 四舍五入后整数部分未超出范围:
     在这种情况下, MySQL 会发出一个警告(通常是 Data truncated for column 'column_name' at row 1),
     但会成功执行插入操作, 并保存四舍五入后的值.
     例如, 对于 FLOAT(5,2) , 插入 999.009 会被四舍五入为 999.01 并成功保存.

* 4. 四舍五入后整数部分超出范围: 如果四舍五入后整数部分仍然超出范围, MySQL 会报错并拒绝处理该插入操作.
     这是因为即使经过四舍五入, 数值仍然不符合列的定义.
     例如, 对于 FLOAT(5,2) , 插入 999.995 会导致整数部分变为 1000, 这超出了 5 位的限制, 因此会报错.
-- 假设我们有一个 FLOAT(7,4) 类型的列:

-- 新建一个表格:
mysql> CREATE TABLE test2_table( f1 FLOAT(7,4) );
Query OK, 0 rows affected, 1 warning (0.02 sec)

-- 合适的值:
mysql> INSERT INTO test2_table VALUES (123.4567);
Query OK, 1 row affected (0.01 sec)
-- 在这个例子中, 值 123.4567 会被四舍五入到 123.4567(或者可能是 123.4568, 取决于四舍五入的具体实现和精度).

-- 超过小数位数限制的值(四舍五入):
mysql> INSERT INTO test2_table VALUES (123.456789);
Query OK, 1 row affected (0.01 sec)
-- 这个值 123.456789 会被四舍五入到 f1 可以表示的最接近的值, 可能是 123.4568.

-- 四舍五入超过整数范围:
mysql> INSERT INTO test2_table VALUES (999.99999);
ERROR 1264 (22003): Out of range value for column 'f1' at row 1

-- 超过整数部分限制的值(报错):
mysql> INSERT INTO test2_table VALUES (12345.6789);
ERROR 1264 (22003): Out of range value for column 'f1' at row 1
-- 虽然整数部分 12345 超出了 FLOAT(7,4) 中 3 位整数的限制, 因此插入操作会失败,并可能产生一个错误.

-- 整数部分超出范围:
mysql> INSERT INTO test2_table VALUES (9999999999.9999);
ERROR 1264 (22003): Out of range value for column 'f1' at row 1
-- 这个值远远超出了 FLOAT 类型能够表示的范围, 因此插入操作会失败, 并可能产生一个错误.
当为FLOAT或DOUBLE列不指定(M,D), 它们会按照实际的精度来存储和显示数值, 这个实际的精度是由底层的硬件和操作系统决定的.
FLOAT类型默认保留3位小数, DOUBLE类型默认保留15.
在某些情况下, 如果数值非常接近一个整数, MySQL 可能会选择将其舍入为那个整数, 这是浮点数的特性之一.
-- 创建表格
mysql> CREATE TABLE test3_table(
    f1 FLOAT,
    f2 DOUBLE
);
Query OK, 0 rows affected (0.02 sec)

-- 插入数据:
mysql> INSERT INTO test3_table VALUES
(123.123456789, 123.11111222223333344444),
(123.654324, 123.99999999999999999999999),
(123.999999, 123.88888888888888888888888);
Query OK, 1 row affected (0.01 sec)

-- 查看数据
mysql> SELECT * FROM test3_table;
+---------+--------------------+
| f1      | f2                 |
+---------+--------------------+
| 123.123 | 123.11111222223333 |
| 123.654 |                124 | -- 无限接近整数直接进1.
|     124 | 123.88888888888889 |
+---------+--------------------+
3 rows in set (0.00 sec)
从MySQL 8.0开始, 虽然仍然支持FLOAT(M,D)和DOUBLE(M,D)语法用于显示目的, 
但已经标记为弃用因为这种语法并不会影响存储的精度或范围.
在实际应用中, 应该更多地关注数据类型本身(FLOAT或DOUBLE)以及实际存储和计算的需求.

3.4 精度误差说明

在实际开发中, FLOAT和DOUBLE类型的使用需要谨慎考虑, 因为浮点数运算存在精度问题.

比如, 我们设计一个表, 有f1这个字段, 插入值分别为0.47, 0.44, 0.19, 
我们期待的运行结果是: 0.47 + 0.44 + 0.19 = 1.1.
-- DOUBLE类型:
-- 创建表格:
mysql> CREATE TABLE test4_table(
    f1 DOUBLE
);
Query OK, 0 rows affected (0.01 sec)

-- 插入数据:
mysql> INSERT INTO test4_table
    VALUES(0.47), (0.44), (0.19);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- 累积:
mysql> SELECT SUM(f1) FROM test4_table;
+--------------------+
| SUM(f1)            |
+--------------------+
| 1.0999999999999999 |  -- 查询结果是: 1.0999999999999999. 虽然误差很小, 但确实有误差.
+--------------------+
1 row in set (0.01 sec)

-- 累积比较:
mysql>  SELECT SUM(f1) = 1.1, 1.1 = 1.1 FROM test4_table;
+---------------+-----------+
| SUM(f1) = 1.1 | 1.1 = 1.1 |  
+---------------+-----------+
|             0 |         1 |  -- f1的累积结果不等于1.1
+---------------+-----------+
1 row in set (0.00 sec)
-- FLOAT类型:
CREATE TABLE test5_table(
    f1 FLOAT
);
Query OK, 0 rows affected (0.02 sec)

INSERT INTO test5_table
    VALUES(0.47), (0.44), (0.19);
    
mysql> SELECT SUM(f1) FROM test5_table;
+--------------------+
| SUM(f1)            |
+--------------------+
| 1.0999999940395355 |  -- 误差更加大
+--------------------+
1 row in set (0.00 sec)
浮点数在二进制表示中无法精确表示所有的十进制小数, 特别是那些尾数不是05的数.
这是因为二进制系统是基于2的幂次来构建的, 而十进制系统是基于10的幂次.
所以, 当我们将十进制小数转换为二进制浮点数时, 可能会遇到无法精确表示的情况.

9.625为例, 它可以精确地表示为二进制小数1001.101, 这对应于二进制科学记数法中的1.001101乘以23次方.
然而, 对于像9.624这样的数, 我们无法找到一个有限的二进制小数来精确表示它.
因此, 计算机内部会进行近似处理, 通常在允许的精度范围内进行四舍五入.

这种精度限制是浮点数存储和计算的固有特性, 不仅仅是在MySQL中, 几乎所有的编程语言和系统都存在这个问题.
因此, 在涉及需要高精度的计算时(如金融计算, 科学计算等), 通常会使用定点数类型(如MySQL中的DECIMAL类型)来避免浮点数的精度问题.

3.5 定点数类型

定点数类型在计算机科学和数据处理中是一种重要的数值表示方法.
定点数, 顾名思义, 指的是小数点位置固定的数.
在计算机运算过程中, 定点数的小数点位置始终保持不变.
这种特性使得定点数在某些特定的计算场景中具有独特的优势.

定点数有两种主要表示形式: 小数和整数.
定点小数将小数点固定在数据数值部分的左边, 符号位的右边; 而定点整数则将小数点固定在数据数值部分的右边.
这种固定的小数点位置使得定点数在处理某些类型的数据时更加高效和准确.
在MySQL中, 定点数类型以DECIMAL(M,D)表示, 其中M代表精度, 即数据的总位数; D代表标度, 即数据的小数部分所占的位数.
DECIMAL类型在MySQL内部是以字符串的形式进行储存的, 因此其精度比浮点数更高, 更适用于存储需要高精度的数据, 如金额等.

DECIMAL(M,D)的最大取值范围与DOUBLE类型一样, 但是有效的数据范围是由M和D决定的.
DECIMAL的存储空间并不是固定的, 由精度值M决定, 总共占用的存储空间为M+2个字节.
也就是说, 在一些对精度要求不高的场景下, 比起占用同样字节长度的定点数, 浮点数表达的数值范围可以更大一些.
当DECIMAL类型不指定精度和标度时, 其默认为DECIMAL(10,0).
当数据的精度超出了定点数类型的精度范围时, 则MySQL同样会进行四舍五入处理.
浮点数 vs 定点数:
* 1. 浮点数相对于定点数的优点是在长度一定的情况下, 浮点类型取值范围大, 但是不精准, 
     适用于需要取值范围大又可以容忍微小误差的科学计算场景(比如计算化学, 分子建模, 流体动力学等).
* 2. 定点数类型取值范围相对小, 但是精准, 没有误差, 适合于对精度要求极高的场景(比如涉及金额计算的场景).
-- 创建表格:
mysql> CREATE TABLE test6_table(
    f1 DECIMAL,
    f2 DECIMAL(5,2)
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test6_table;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| f1    | decimal(10,0) | YES  |     | NULL    |       |
| f2    | decimal(5,2)  | YES  |     | NULL    |       |
+-------+---------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

-- 插入正常范围内的数据:
mysql> INSERT INTO test6_table VALUES(123.123, 123.456);
Query OK, 1 row affected, 2 warnings (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test6_table;
+------+--------+
| f1   | f2     |
+------+--------+
|  123 | 123.46 |
+------+--------+
1 row in set (0.00 sec)

-- 插入超过整数范围的数据:
mysql> INSERT INTO test_decimal1(f2) VALUES(1234.34);
ERROR 1146 (42S02): Table 'db0.test_decimal1' doesn't exist
-- 计算:
mysql> CREATE TABLE test7_table(
    f1 DECIMAL(5, 2)
);
Query OK, 0 rows affected (0.02 sec)

-- 插入数据:
mysql> INSERT INTO test7_table
    VALUES(0.47), (0.44), (0.19);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- 累积:
mysql> SELECT SUM(f1) FROM test7_table;
+---------+
| SUM(f1) |
+---------+
|    1.10 |
+---------+
1 row in set (0.00 sec)

-- 累积比较:
mysql> SELECT SUM(f1) = 1.1 FROM test7_table;
+---------------+
| SUM(f1) = 1.1 |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)

4. 位类型


4.1 存储范围

在MySQL中, 位类型主要指的是BIT类型, 它用于存储位字段值, 它指定了存储的位数, 可以存储从1位到64位的值.
例如, BIT(1)可以存储01, BIT(8)可以存储0255之间的任何值(因为8位可以表示256个不同的值, 但通常我们是从0开始计数的).

位类型在某些特定场景中非常有用, 
比如需要存储一组开关状态(/, /, /否等), 或者当你需要紧凑地存储大量这样的状态时.

在MySQL中操作位类型时, 可以使用位操作符(如AND, OR, NOT等)来进行位运算.
这些操作在处理大量开关状态或进行性能优化的场景中可能会非常有用.
二进制字符串类型长度范围占用空间
BIT(M)1 <= M <= 64约为(M + 7)/8个字节
说明:
* 1. BIT(M)中的M表示位的数量, 可以是从164的任意整数, 不写的话默认1.
* 2. 占用空间是根据位的数量计算得出的, 大致为(M + 7)/8个字节.
     这是因为计算机存储通常是以字节为单位的, 而一个字节包含8.
     (M + 7)/8这个公式确保了即使只有很少的位, 也会至少占用一个字节的空间, 并且向上取整到最近的字节边界.
     例如, BIT(1)会占用1个字节的空间, BIT(8)也会占用1个字节的空间, 而BIT(9)则会占用2个字节的空间.

4.2 使用示例

BIT列期待你插入一个由01组成的字符串, 这个字符串的长度必须与你定义的BIT长度相匹配.
数字插入格式:
NSERT INTO table_name (column) VALUES (1);
二进制字符串插入格式:
NSERT INTO table_name (column) VALUES (b'1');

查询BIT类型字段时, 返回的值会被转换为十六进制或其他格式, 以便于阅读和处理.
-- 存储数字:
mysql> CREATE TABLE test_bit1(
    f1 BIT,
    f2 BIT(4)  -- 4位对应的最大数字是15, 二进制则是1111.
);
Query OK, 0 rows affected (0.03 sec)

-- 查看表结构:
mysql> DESC test_bit1;
+-------+--------+------+-----+---------+-------+
| Field | Type   | Null | Key | Default | Extra |
+-------+--------+------+-----+---------+-------+
| f1    | bit(1) | YES  |     | NULL    |       |  -- 默认为1为.
| f2    | bit(4) | YES  |     | NULL    |       |
+-------+--------+------+-----+---------+-------+
2 rows in set (0.00 sec)

-- 插入范围内的数字:
mysql> INSERT INTO test_bit1(f1, f2) VALUES(1, b'1111');
Query OK, 1 row affected (0.01 sec)

-- 插入超出范围的数字:
mysql> INSERT INTO test_bit1(f1) VALUES(2);
ERROR 1406 (22001): Data too long for column 'f1' at row 1

-- 插入超出范围内的二进制数据:
mysql> INSERT INTO test_bit1(f2) VALUES(b'10000');  -- 对应数字16
ERROR 1406 (22001): Data too long for column 'f2' at row 1

-- 查看数据(十六进制):
mysql> SELECT * FROM test_bit1;
+------------+------------+
| f1         | f2         |
+------------+------------+
| 0x01       | 0x0F       |
+------------+------------+
1 row in set (0.00 sec)

-- 可以用CONV()函数转换:
mysql> SELECT CONV(f1, 16, 10), CONV(f2, 16, 10) FROM test_bit1;
+------------------+------------------+
| CONV(f1, 16, 10) | CONV(f2, 16, 10) |
+------------------+------------------+
| 1                | 15               |
+------------------+------------------+
1 row in set (0.00 sec)


-- 使用b+0查询数据时, 可以直接查询出存储的十进制数据的值(不能使用*+0):
-- 使用+0会触发隐式类型转换, 将BIT字段的值转换为整数. 
-- 这样做可以方便地获取BIT字段的十进制表示而无需显式地使用转换函数.
mysql> SELECT f1+0, f2+0 FROM test_bit1;
+------+------+
| f1+0 | f2+0 |
+------+------+
|    1 |   15 |
+------+------+
1 row in set (0.00 sec)

5. 字符串类型


5.1 存储范围

在MySQL中, 字符型数据用于存储文本和字符串.
字符串类型描述范围
CHAR定长字符串长度为0到255个字符. 存储时, 如果字符串长度小于定义的长度, 会使用空格填充到指定长度.
VARCHAR可变长度字符串长度为0到65,535个字节(具体取决于最大行大小和其他列).
存储时, 只使用必要的长度来存储字符串, 不会添加额外的空格.
文本字符串描述范围
TINYTEXT非常小的文本字符串最大长度为255个字符.
TEXT文本字符串最大长度为65,535个字符.
MEDIUMTEXT中等大小的文本字符串最大长度为16,777,215个字符.
LONGTEXT大文本字符串最大长度为4,294,967,295个字符.
二进制字符串类型描述范围
BINARY定长二进制字符串类似于CHAR, 但存储二进制数据. 长度为0到255个字节.
VARBINARY可变长度二进制字符串类似于VARCHAR, 但存储二进制数据. 长度为0到65,535个字节.
TINYBLOB非常小的二进制大对象最大长度为255个字节.
BLOB二进制大对象最大长度为65,535个字节.
MEDIUMBLOB中等大小的二进制大对象最大长度为16,777,215个字节.
LONGBLOB大二进制大对象最大长度为4,294,967,295个字节.

5.2 字符串类型

类型特点长度长度范围占用的存储空间
CHAR(M)定长: 固定长度M0 <= M <= 255M个字节
VARCHAR(M)不定长: 长度可变不固定M0 <= M <= 65535(实际长度 + 1)个字节
说明:
* 1. CHAR(M) 类型: 存储固定长度的字符串.
     无论实际字符串的长度如何, 都会占用M个字节的存储空间.
     如果存储的字符串长度小于M, 那么数据库会使用空格进行填充.
     
* 2. VARCHAR(M) 类型: 存储可变长度的字符串.
     根据实际字符串的长度来分配存储空间, 并在字符串的末尾添加一个或两个字节来记录字符串的长度.
     因此, VARCHAR类型占用的存储空间是实际长度加上12个字节.
     
需要注意的是, VARCHAR类型在实际应用中更加灵活, 因为它不会浪费存储空间来填充固定长度的字符串.
但是, 由于需要额外存储长度信息, 所以在某些情况下, 如果字符串长度接近固定长度, 使用CHAR类型可能会更加高效.
5.2.1 CHAR类型
CHAR(M)类型使用注意事项:
* 1. CHAR(M) 类型一般需要预先定义字符串长度.
     如果不指定(M), 则表示长度默认是1个字符.
     定义CHAR类型字段时, 声明的字段长度即为CHAR类型字段所占的存储空间的字节数.

* 2. 如果保存时, 数据的实际长度比CHAR类型声明的长度小, 则会在右侧填充空格以达到指定的长度.
     当MySQL检索CHAR类型的数据时, CHAR类型的字段会去除尾部的空格.
     手动在CHAR字段的尾部插入了空格, 检索时这些尾部的空格也会被自动去除.
示例: 当你定义一个字段为CHAR(5)类型时, 这意味着该字段将始终占用5个字符的空间.
如果插入的值少于5个字符, 数据库会自动在其后面填充空格以达到5个字符的长度.

当你使用SELECT语句从数据库中检索CHAR类型字段的值时, 很多数据库客户端和工具默认会去除尾部的空格.
这是因为在SQL标准中, CHAR和VARCHAR在比较时通常被视为等价的(至少当CHAR字段的值没有被尾部填充时), 
所以许多工具选择简化显示, 不显示尾部的空格.
-- 创建表格:
mysql> CREATE TABLE test_char1(
    c1 CHAR,
    c2 CHAR(5)
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test_char1;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| c1    | char(1) | YES  |     | NULL    |       |
| c2    | char(5) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)

-- 插入正常范围内数据:
mysql> INSERT INTO test_char1
VALUES('a', 'Tom');
Query OK, 1 row affected (0.02 sec)

-- 插入数据:
INSERT INTO test_char1(c2)
VALUES('a ');

-- 查看数据:
mysql> select * from test_char1;
+------+------+
| c1   | c2   |
+------+------+
| a    | Tom  |
| NULL | a    |
+------+------+
2 rows in set (0.00 sec)

-- 查看长度:
mysql> SELECT CHAR_LENGTH(c2)
FROM test_char1;
+-----------------+
| CHAR_LENGTH(c2) |
+-----------------+
|               3 |
|               1 |  -- 检索时会自动去尾部除空格
+-----------------+
3 rows in set (0.00 sec)

-- 插入超出范围的值报错:
mysql> INSERT INTO test_char1
VALUES('123456', '123456');
ERROR 1406 (22001): Data too long for column 'c1' at row 1
5.2.2 VARCHAR类型
VARCHAR(M)类型使用注意事项: 
* 1. VARCHAR(M) 定义时, 必须指定长度M, 否则报错.
* 2. MySQL4.0版本以下, varchar(20): 指的是20字节, 如果存放UTF8汉字时, 只能存6(每个汉字3字节);
     MySQL5.0版本以上, varchar(20): 指的是20字符.
* 3. 检索VARCHAR类型的字段数据时, 会保留数据尾部的空格.
* 4. VARCHAR类型的字段所占用的存储空间为字符串实际长度加12个字节.
使用限制解释:
在MySQL中, VARCHAR(20) 表示的是一个可变长度的字符字段, 其最大长度是20个字符.
这里的'字符'是指你存储的文本内容中的单个字符, 而不是字节.
但是, 实际能够存储的字符数量可能受到字符集和数据库配置的影响
* 1. 字符集的影响: 不同的字符集对字符的编码方式不同, 一个字符可能占用多个字节.
     例如, 在utf8mb4字符集中, 一个字符可能占用最多4个字节.
     可存的字符串则为: 65535 / 4 = 16383.75 (舍去小数).

* 2. 行的最大字节数限制: 每行数据的长度是有限制的, 这个限制是由存储引擎决定的.
     , InnoDB存储引擎, 其默认的行大小限制是65,535字节.
-- VARCHAR定义时必须指定长度M, 否则报错.
mysql> CREATE TABLE test_varchar1(
    NAME VARCHAR  
);
ERROR 1064 (42000): You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 3
-- 默认使用utf8mb4编码, 取值范围则为65535 ÷ 4 = 16383.75 舍去小数部分:
mysql> CREATE TABLE test_varchar2(
    NAME VARCHAR(65535)  
);
ERROR 1074 (42000): Column length too big for column 'NAME' (max = 16383); use BLOB or TEXT instead

-- 修改字符编码为gbk, 取值范围则为: 65535÷2=32767.5 舍去小数部分:
mysql> CREATE TABLE test_varchar3(
    NAME VARCHAR(3333)
) CHARSET=gbk;
Query OK, 0 rows affected (0.02 sec)
-- 创建表格:
mysql> CREATE TABLE test_varchar4(
    NAME VARCHAR(5)
);
Query OK, 0 rows affected (0.02 sec)

-- 插入5个或以内的字符串:
mysql>  INSERT INTO test_varchar4
VALUES('122'), ('12345');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 在末尾插入空格:
mysql> INSERT INTO test_varchar4 VALUES('123  ') ;
Query OK, 1 row affected (0.00 sec)

-- 查看数据:
mysql> SELECT * FROM test_varchar4;
+-------+
| NAME  |
+-------+
| 122   |
| 12345 |
| 123   |
+-------+
3 rows in set (0.00 se

-- 查看字符串长度:
mysql> SELECT CHAR_LENGTH(NAME) FROM test_varchar3;
+-------------------+
| CHAR_LENGTH(NAME) |
+-------------------+
|                 3 |
|                 5 |
|                 5 |  -- 看这里!
+-------------------+
3 rows in set (0.00 sec)

-- 插入超出范围的字符串:
mysql> INSERT INTO test_varchar4 VALUES('123456');
ERROR 1406 (22001): Data too long for column 'NAME' at row 1
5.2.3 对比
类型特点空间上时间上适用场景
CHAR定长浪费存储空间(如果数据长度小于M)效率高存储长度固定.
例: 如性别, 状态码等; 速度要求高的场景
VARCHAR可变长节省存储空间(根据实际数据长度分配)效率相对较低(因为需要存储长度信息)存储长度可变的数据.
例如名字、描述等;非CHAR的情况
说明:
* 1. 空间上: CHAR(M)总是分配固定长度的存储空间, 无论实际数据长度是多少.
     如果数据长度小于M, 则剩余的空间会被浪费.
     而VARCHAR(M)根据实际数据的长度来分配存储空间, 因此可以节省空间.
     
* 2. 时间上: 由于CHAR(M)是固定长度的, 所以在查找和比较时效率较高.
     而VARCHAR(M)因为长度可变, 所以在处理时需要额外的步骤来读取长度信息, 因此效率相对较低.
     
* 3. 适用场景: CHAR(M)适用于存储长度固定的数据, 例如性别(通常是'M''F'), 状态码等.
     它也适用于速度要求高的场景, 因为固定长度的处理通常更快.
     而VARCHAR(M)适用于存储长度可变的数据, 例如名字, 地址, 描述等.
     当数据的长度不是固定的, 或者预计会有大量的空间浪费时, 使用VARCHAR(M)更为合适.
哪些情况使用 CHAR  VARCHAR 更好?
* 1. 情况1: 存储很短的信息.
     比如门牌号码101, 201... 这样很短的信息应该用char, 因为varchar还要占个byte用于存储信息长度
     本来打算节约存储的, 结果得不偿失.

* 2. 情况2: 固定长度的.
     比如使用uuid作为主键, 那用char应该更合适.
     因为他固定长度, varchar动态根据长度的特性就消失了, 而且还要占个长度信息.
     
* 3. 情况3: 十分频繁改变的column.
     因为varchar每次存储都要有额外的计算, 得到长度等工作, 
     如果一个非常频繁改变的, 那就要有很多的精力用于计算, 而这些对于char来说是不需要的.
     
* 4. 情况4: 具体存储引擎中的情况:
     1. MyISAM 数据存储引擎和数据列: MyISAM数据表, 最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列.
        这样使得整个表静态化, 从而使数据检索更快, 用空间换时间.
        
     2. MEMORY 存储引擎和数据列: MEMORY数据表目前都使用固定长度的数据行存储, 
        因此无论使用CHAR或VARCHAR列都没有关系, 两者都是作为CHAR类型处理的.
        
     3. InnoDB 存储引擎, 建议使用VARCHAR类型. 这主要基于以下几个原因:
        首先, InnoDB存储引擎内部的行存储格式并没有区分固定长度和可变长度列.
        所有数据行都使用指向数据列值的头指针, 这意味着无论是CHAR还是VARCHAR类型, 它们在InnoDB中的存储方式并没有本质的区别.

        其次, 主要影响InnoDB性能的因素是数据行使用的存储总量. 
        由于CHAR类型会预分配固定的空间, 无论实际数据长度如何, 都会占用相同的存储空间.
        而VARCHAR类型则根据实际数据的长度来分配存储空间, 因此可以更有效地利用空间.
        
        如果表中存在大量的CHAR字段, 并且这些字段的实际数据长度远小于预分配的空间, 那么将会造成大量的空间浪费.
        这不仅会增加数据存储的总量, 还可能影响磁盘I/O的性能(
        当数据表中存在大量未使用的空间可能导致磁盘碎片化.
        碎片化意味着数据在磁盘上不是连续存储的, 这可能导致磁盘头在读取或写入数据时需要进行更多的移动, 进一步降低I/O性能).

        因此, 对于InnoDB数据表, 除了那些简短并且固定长度的数据(如性别, 状态码等), 其他情况下通常建议使用VARCHAR类型.

5.3 文本字符串类型

在MySQL中, TEXT类型被用来存储文本类型的字符串数据, 它提供了四种不同的子类型, 以满足不同长度的文本存储需求.
这些子类型分别是: TINYTEXT, TEXT, MEDIUMTEXT  LONGTEXT.
每种类型都有其特定的最大字符长度限制,  TINYTEXT 255个字符到 LONGTEXT 4GB字符, 提供了广泛的灵活性.

当向 TEXT 类型的字段保存数据时, MySQL会自动根据文本的实际长度来分配存储空间, 无需在创建表时预先定义字段的长度.
同样地, 在查询这些字段时, 系统也会根据存储的文本长度来检索数据.
这种动态处理的方式使得 TEXT 类型在存储可变长度的文本数据时非常便捷.
文本字符串类型特点长度范围占用的存储空间
TINYTEXT小文本, 可变长度0 <= L <= 255L + 2 个字节
TEXT文本, 可变长度0 <= L <= 65535L + 2 个字节
MEDIUMTEXT中等文本, 可变长度0 <= L <= 16777215L + 3 个字节
LONGTEXT大文本, 可变长度0 <= L <= 4294967295(相当于4GB)L + 4 个字节
这里的'L'代表实际存储的文本长度, '+ 2 个字节', '+ 3 个字节''+ 4 个字节'则是用于记录文本长度的额外空间.
这些额外字节的数量是根据文本长度的最大值来确定的, 以确保能够准确地存储和检索文本数据.
TEXT类型字会保留字符串中的尾部空格.
当你向TEXT字段插入数据时, 包括尾部的空格在内的所有字符都会被完整地存储.
同样地, 当你从TEXT字段检索数据时, 包括尾部的空格在内的完整字符串也会被返回.
-- 创建表格:
mysql> CREATE TABLE test_text(
    tx TEXT
);
Query OK, 0 rows affected (0.01 sec)

-- 插入数据:
mysql> INSERT INTO test_text
VALUES('123456    ');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test_text;
+------------+
| tx         |
+------------+
| 123456     |
+------------+
1 row in set (0.00 sec)

-- 查看长度:
mysql> SELECT CHAR_LENGTH(tx) FROM test_text;
+-----------------+
| CHAR_LENGTH(tx) |
+-----------------+
|              10 |
+-----------------+
1 row in set (0.00 sec)

5.4 二进制字符串类型

在MySQL中, 二进制字符串类型用于存储二进制数据.
MySQL提供了几种二进制字符串类型,  BINARY  VARBINARY,
以及用于存储大量二进制数据的BLOB类型( Binary Large Object).
BLOB类型有几个变种, 包括TINYBLOB, BLOB, MEDIUMBLOB和LONGBLOB, 它们分别可以存储不同大小的数据.

尽管BLOB类型可以存储二进制数据, 但通常建议将大型文件(如图片, 音频和视频)存储在文件系统中, 
并在数据库中仅保存文件的路径或引用.
这样做有几个好处:
* 1. 性能: 数据库专注于快速处理结构化数据, 而文件系统则更适合存储和检索大型文件.
* 2. 备份和迁移: 文件可以单独备份和迁移, 而不需要与数据库一起.
* 3. 扩展性: 文件系统通常提供了更好的扩展性, 可以轻松地添加更多的存储空间.
5.4.1 BINARY类型与VARBINARY类型
BINARY和VARBINARY数据类型与CHAR和VARCHAR在功能上是相似的, 但它们的用途是存储二进制字符串.     
数据类型特点值的长度占用空间
BINARY(M)固定长度M (0 <= M <= 255)M个字节
VARBINARY(M)可变长度M(0 <= M <= 65535)M+1或M+2个字节
说明:
* 1. BINARY(M)类型的字段是固定长度的, 无论实际存储的二进制数据有多长, 都会占用M个字节的空间.
     如果存储的数据长度小于M, 那么剩余的空间会用'\0'(空字符)填充.
     M的取值范围是从0255如果在定义时没有明确指定(M), 那么默认只能存储1个字节的数据.
     BINARY 类型在检索时本身不会自动去除末尾的0.
     
* 2. VARBINARY(M)类型的字段是可变长度的, 它会根据实际存储的二进制数据长度来分配空间.
     除了存储数据本身外, 还需要额外的12个字节来记录数据的实际长度.
     因此, VARBINARY字段占用的空间是数据长度加上这些额外的字节.
     具体使用1个字节还是2个字节来记录长度, 取决于实际数据的大小.
     当数据长度可以使用一个字节表示时, 就使用一个字节; 否则, 使用两个字节.
     在定义VARBINARY字段时, 必须指定(M), 否则数据库会报错.
     虽然VARBINARY(M)的最大长度可以达到65535字节, 但实际上受到数据库行大小限制和存储引擎的限制, (参考VARCHAR).
-- 创建表格:
mysql> CREATE TABLE test_binary1(
    f1 BINARY,
    f2 BINARY(3),
    f3 VARBINARY(10)
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql>  DESC test_binary1;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| f1    | binary(1)     | YES  |     | NULL    |       |
| f2    | binary(3)     | YES  |     | NULL    |       |
| f3    | varbinary(10) | YES  |     | NULL    |       |
+-------+---------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

-- 插入一个字节的数据到 f1  
mysql> INSERT INTO test_binary1(f1) VALUES(X'41'); -- 插入十六进制表示的 'A'  
  
-- 插入三个字节的数据到 f2  
mysql> INSERT INTO test_binary1(f2) VALUES(X'E4BDA0'); -- 插入十六进制表示的汉字 '你' 的 UTF-8 编码


-- 插入可变长度的二进制数据到 f3  
mysql> INSERT INTO test_binary1(f3) VALUES(X'48656C6C6F'); -- 插入 'Hello' 的 ASCII 编码  
  
-- 插入超过10个字节长度的数据到 f3 (报错)  
INSERT INTO test_binary1(f3) VALUES(X'4D7953514C536572766572'); 
ERROR 1406 (22001): Data too long for column 'f3' at row 1

-- 查询数据(将返回原始字节序列)  
mysql> SELECT * FROM test_binary1;
+------------+------------+--------------+
| f1         | f2         | f3           |
+------------+------------+--------------+
| 0x41       | NULL       | NULL         |
| NULL       | 0xE4BDA0   | NULL         |
| NULL       | NULL       | 0x48656C6C6F |
+------------+------------+--------------+
3 rows in set (0.00 sec)
5.4.2 Navicat编码问题
为二进制字符串插入汉字它会自动转化为字符串序列.
-- 在终端执行(国内终端使用gbk, 执行时会将'你'按照gbk的字符串编码转化为"0xC4E3"): 
mysql> INSERT INTO test_binary1(f2) VALUES('你');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> select * from test_binary1;
+------------+------------+----------+
| f1         | f2        | f3        |
+------------+------------+----------+
| NULL       | 0xE4BDA0 | NULL       |  -- 前面插入的
| NULL       | 0xC4E300 | NULL       |  -- 一共三个字节, gbk使用两个自己, 剩下一个字节被0填充.
+------------+------------+----------+
2 rows in set (0.00 sec)
使用Navicat执行查询语句, 返回的是gbk的解码,  0xE4BDA0 被分成两部分 0xE4BD  0xA0, 0xE4BD被解析为浣, 0xA0无法解析则成了?.

2024-03-25_182726

这里就很有意思了, 在Navicat的结果处复制'浣?', 粘贴到其他位置就得到了'你'.

2024-03-25_184127

设置成utf8后, 也没有用.

2024-03-25_191142

修改这个能够解决, 但是不要去设置了, 这个系统设置可能会导致其他软件编码出现问题.
以后搞清楚了在来补充...

image-20240325211945626

5.4.3 二进制大对象
BLOB (Binary Large Object): 是一个用于存储二进制大对象的数据类型, 它允许存储可变长度的数据.
MySQL中, BLOB类型有几个变种, 包括: TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB.
二进制大对象值的长度(L)长度范围占用空间
TINYBLOBL0 <= L <= 255L + 1 个字节
BLOBL0 <= L <= 65,535 (相当于64KB)L + 2 个字节
MEDIUMBLOBL0 <= L <= 16,777,215(相当于16MB)L + 3 个字节
LONGBLOBL0 <= L <= 4,294,967,295 (相当于4GB)L + 4 个字节
5.4.4 使用建议
对于大量的文本数据, 虽然TEXT类型提供了存储能力, 但也需要考虑其带来的性能影响和管理复杂性.
以下是使用建议:
* 1. TEXT累相比于CHAR和VARCHAR类型, TEXT字段的搜索速度通常会更慢一些, 尤其是在执行全表扫描或未正确使用索引的情况下.
     这是因为TEXT类型的字段通常不会存储在表的主数据区域, 而是存储在单独的页或区域中, 这增加了数据检索的复杂性.
     如果文本内容不是特别大, 使用CHAR或VARCHAR类型是更合适的.
     这两种类型存储在表的主数据区域, 访问速度更快, 且对于固定长度的数据(如代码, 编号等), CHAR类型更为合适.
     
* 2. TEXT类型字段通常不应该设置默认值, 因为即使设置了, 它也不会像CHAR或VARCHAR那样在插入时自动填充默认值.

* 3. 当TEXT或BLOB类型的数据被删除时, 可能会在数据库中留下'空洞', 未使用的空间.
     这些空洞可能导致文件碎片增多, 影响数据库的性能和存储效率.
     为了避免这种情况, 可以定期优化数据库表, 使用如OPTIMIZE TABLE命令来整理碎片并回收未使用的空间.
     
* 4. 如果一个表频繁使用且包含大量的TEXT或BLOB字段, 建议将这些字段拆分到单独的表中.
     这样, 主表可以保持较小的体积和更高的访问速度, 而文本或二进制数据可以存储在单独的表中, 并通过关联键进行连接.

* 5. 避免不必要的模糊查询: 虽然MySQL提供了前缀索引来支持对大型文本字段的模糊查询,
     但这样的查询通常效率较低, 特别是当数据量很大时.
     因此, 应尽量避免不必要的模糊查询, 特别是针对BLOB或TEXT列.
     当需要进行模糊查询时, 最好确保WHERE子句能够精确地定位到所需的数据行, 以减少不必要的数据传输和处理.

* 6. TEXT类型由于其设计初衷和存储方式的限制, 不能作为主键使用.
     主键作为表中每一行的唯一标识, 必须是一个固定长度的值, 这样数据库系统才能高效地检索和管理数据.
     由于TEXT类型的长度不确定性, 它无法满足主键对固定长度的要求.

6. 枚举类型


6.1 存储范围

在MySQL中, ENUM类型是一个特定的数据类型, 用于限制字段的取值范围.
在定义ENUM字段时, 必须明确指定其可以接受的成员值列表.
这意味着, 当为具有ENUM类型的字段设置值时, 只能从预先定义的成员中选择单个值(不能同时选择多个值).

此外, ENUM类型所需的存储空间并不是固定的, 而是根据定义时指定的成员数量而定的.
通常, ENUM类型采用一种高效的存储机制, 以最小化空间占用, 同时确保数据的完整性和准确性.
枚举类型描述长度范围
ENUM(L)允许在列中插入预定义的值列表中的一个值.1 <= L <= 65535: 最多可以有65535个不同的值.
示例: ENUM('男', '女'); 只能存储'男'或'女'.占用1或2个字节, 如果值列表在255个以内, 使用一个字节存储;
实际上不存储字符串本身, 而是存储值在列表中的位置(整数).如果超过255但小于65535, 使用两个字节存储.
枚举类型的特点和限制:
* 1. 枚举值在物理存储时实际上是按照定义的顺序存储的整数索引, 而不是实际的字符串值.
* 2. 枚举字段在查询时返回的是字符串值, 而不是整数索引.
* 3. 如果尝试插入不在值列表中的值, MySQL会报错(除非该列允许NULL值, 并且插入了NULL).
* 4. 枚举类型的值列表中的值必须是唯一的, 不能重复.
* 5. 修改枚举类型的值列表(例如添加, 删除或重新排序值)可能需要使用'ALTER TABLE'语句, 这可能会影响到已有的数据和应用逻辑.
* 6. 由于枚举类型的这些特点, 它最适合用于那些值的集合是固定的, 不会经常变化的字段, 例如性别, 星期几等.
     对于可能经常变化的字段, 使用其他数据类型(如VARCHAR)可能更为合适.

6.2 使用示例

-- 创建表格:
mysql> CREATE TABLE test_enum(
    season ENUM('春', '夏', '秋', '冬', 'unknow')
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test_enum;
+--------+------------------------------------+------+-----+---------+-------+
| Field  | Type                               | Null | Key | Default | Extra |
+--------+------------------------------------+------+-----+---------+-------+
| season | enum('春','夏','秋','冬','unknow') | YES  |     | NULL    |       |
+--------+------------------------------------+------+-----+---------+-------+
1 row in set (0.02 sec)

-- 插入数据:
mysql> INSERT INTO test_enum
VALUES('春'), ('秋');
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 忽略大小写:
mysql> INSERT INTO test_enum
VALUES('UNKNOW');
Query OK, 1 row affected (0.01 sec)


--  允许按照角标的方式获取指定索引位置的枚举值:
mysql> INSERT INTO test_enum
VALUES('1'), (3);  -- 对应春秋, 字符串'1'被隐式转换为1
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 当ENUM类型的字段没有声明为NOT NULL时, 插入NULL也是有效的:
mysql> INSERT INTO test_enum
VALUES(NULL);
Query OK, 1 row affected (0.01 sec)

-- 查看表数据:
mysql> SELECT * FROM test_enum;
+--------+
| season |
+--------+
||
||
| unknow |
||
||
| NULL   |
+--------+
6 rows in set (0.00 sec)

-- 插入未定义的成员会报错:
mysql> INSERT INTO test_enum
VALUES('ab');
ERROR 1265 (01000): Data truncated for column 'season' at row 1

7. 集合类型


7.1 存储范围

在MySQL中, 集合(SET)类型是一种字符串对象, 用于存储一个预定义的字符串集合中的零个或多个值.
集合类型允许在单个列中存储多个值, 每个值都是集合定义时指定的一个元素.
集合类型描述范围
SET允许在单个列中存储多个预定义的字符串值(定义时指定).存储时, 每个集合元素使用多个位来表示,
每个集合元素之间用逗号分隔, 集合成员本身不能包含逗号.因此存储需求取决于集合的大小和元素数量.
使用集合类型可以方便地在一个单独的列中存储多个相关的值.
这意味着, 你可以在一个字段中存储多个选项或属性, 而不是为每个选项或属性都创建一个单独的列.

此外, 与集合类型相关的MySQL函数(: FIND_IN_SET())可以用于查询和操作集合数据, 提供了灵活的集合运算能力.
成员个数范围(L表示实际成员个数)占用的存储空间
1 <= L <= 81个字节
9 <= L <= 162个字节
17 <= L <= 243个字节
25 <= L <= 324个字节
33 <= L <= 648个字节
SET类型所占用的存储空间并不是固定的, 而是根据其包含的成员个数而有所不同.
当SET类型所包含的成员数量不同时, 所需的存储空间也会相应变化.
这种设计使得SET类型能够高效地使用存储空间, 同时满足各种实际应用的需求.

SET类型表示一个字符串对象, 它具有独特的特性, 即允许包含0个或多个成员, 但成员的总数不得超过上限64.
在设置字段值时, SET类型允许从预定义的成员集合中选择0个或多个值, 为用户提供了灵活的选择范围.
SET类型允许用户在选取成员时一次选择多个成员, 这与ENUM类型形成了鲜明的对比.
ENUM类型只允许从成员中选取单个值, 而SET类型则提供了更大的灵活性, 允许用户根据需要选择多个成员(包括单个).

7.2 使用示例

-- 创建表格:
mysql> CREATE TABLE test_set(
    s SET ('A', 'B', 'C')
);
Query OK, 0 rows affected (0.03 sec)

-- 查看表结构:
mysql> desc test_set;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| s     | set('A','B','C') | YES  |     | NULL    |       |
+-------+------------------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 向表中插入数据(一次可以插入单个或多个, 插入多个时使用逗号分隔, 并且不能出现多余的空格):
mysql> INSERT INTO test_set (s) VALUES ('A'), ('A,B');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 插入重复的SET类型成员时, MySQL会自动删除重复的成员:
mysql> INSERT INTO test_set (s) VALUES ('A,B,C,A');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test_set;
+-------+
| s     |
+-------+
| A     |
| A,B   |
| A,B,C |
+-------+
3 rows in set (0.00 sec)

-- 插入未定义的成员会报错:
mysql> INSERT INTO test_set (s) VALUES ('A,B,C,D');
ERROR 1265 (01000): Data truncated for column 's' at row 1

8. 日期与时间类型


8.1 存储范围

日期与时间信息在数据处理中占据举足轻重的地位, 它们为数据提供了关键的上下文信息.
在我们的系统中, 不论是用户活动记录, 交易数据还是其他任何类型的信息, 几乎每一个数据表都需要包含日期与时间的标签.
这是因为客户需要这些时间标签来精确地查询, 统计和处理数据, 从而了解数据背后的故事, 做出基于时间的决策.

MySQL数据库管理系统提供了多种数据类型来存储日期和时间信息, 不同的MySQL版本可能有些许差异.
在MySQL 8.0版本中, 主要的日期和时间类型包括: YEAR类型, TIME类型, DATE类型, DATETIME类型和TIMESTAMP类型.
详细介绍如下:
* 1. YEAR类型专门用于存储年份信息, 适用于那些只需要记录年份的场景.
* 2. DATE类型则用于存储具体的日期, 包括年, , , 它适用于大多数需要记录完整日期的应用.
* 3. TIME类型专注于时间部分, 包括小时, 分钟和秒, 它对于记录一天中的具体时间点非常有用.
* 4. DATETIME类型则结合了DATE和TIME, 可以完整地表示年, , , , , , 适用于那些需要精确到秒级时间戳的应用.
* 5. TIMESTAMP类型则更为复杂, 它不仅包含了DATETIME的所有信息, 还加入了时区支持, 
     使得在不同地理位置和时间设置下, 数据的时间表示都能保持一致.
通过合理选择和使用这些日期与时间类型, 我们可以确保系统中的数据既准确又易于处理, 从而为客户提供更好的数据服务.
类型名称字节日期格式最小值最大值
YEAR1YYYY或YY19012155
TIME时间3HH:MM:SS-838:59:59838:59:59
DATE日期3YYYY-MM-DD1000-01-019999-12-31
DATETIME日期时间8YYYY-MM-DD HH:MM:SS1000-01-01 00:00:009999-12-31 23:59:59
TIMESTAMP日期时间带时区4YYYY-MM-DD HH:MM:SS1970-01-01 00:00:00 UTC2038-01-19 03:14:07 UTC
在MySQL中, 插入时间, 日期时间, 时间戳类型的数据, 应该使用单引号来包裹这些值.
因为这些类型在数据库中都有特定的格式要求, 使用单引号可以确保MySQL正确地将这些值解析为相应的时间或日期时间类型,
而不是将它们当作其他类型的数据处理.

8.2 YEAR类型

YEAR类型在日期时间类型中占据最小的存储空间, 仅需1个字节即可.
YEAR类型具有灵活的存储格式, 以适应不同的使用场景, 常用格式:

* 1. 以四位数的'字符串''数字'格式来表示, 其格式为YYYY, 这种格式的范围从1901年至2155. 
     这种表示方式清晰明了, 适用于大多数需要记录完整年份的场景.
     
* 2. 以两位数的'字符串''数字'格式来表示年份, 范围从0099.
     然而, 这种表示方式在实际应用中可能存在一定的歧义.
     例如, 当取值为0169, 它实际上表示的是2001年到2069;
     而当取值为7099, 则表示的是1970年到1999.
     此外, 当取值为整数0或字符串'00', 它代表的是0000, 这在实际应用中可能是不准确的.
     因此, 为了避免混淆和错误, 从MySQL 5.5.27版本开始, 已经不建议使用这种两位数的YEAR表示方式.

在MySQL中, YEAR类型的默认格式就是'YYYY', 因此在实际使用时, 我们无需特意指定YEAR(4)这样的格式.
而且, 从MySQL 8.0.19版本开始, 为了保持数据的一致性和准确性, 已经不推荐使用指定显示宽度的YEAR(4)数据类型.
-- 创建表格:
mysql> CREATE TABLE test_year(
    f1 YEAR,
    f2 YEAR(4)
);
Query OK, 0 rows affected, 1 warning (0.02 sec)

-- 查看表结构:
mysql> desc test_year;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| f1    | year | YES  |     | NULL    |       |  -- 5.7 版本显示year(4)
| f2    | year | YES  |     | NULL    |       |  -- 5.7 版本显示year(4)
+-------+------+------+-----+---------+-------+
2 rows in set (0.00 sec)
-- 四位数格式插入数据:
mysql> INSERT INTO test_year
VALUES(2020, '2021');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test_year;
+------+------+
| f1   | f2   |
+------+------+
| 2020 | 2021 |
+------+------+
1 row in set (0.00 sec)

-- 两位数格式插入数据(以70(1970年, 计算机元年)为界):
mysql> INSERT INTO test_year
VALUES(45, '71');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test_year;
+------+------+
| f1   | f2   |
+------+------+
| 2020 | 2021 |
| 2045 | 1971 |
+------+------+
2 rows in set (0.00 sec)

8.2 DATE类型

DATE类型专门用于表示日期, 不包含时间部分, 其标准格式为YYYY-MM-DD, 
其中YYYY代表四位数的年份, MM代表两位数的月份, DD代表两位数的日期.
该类型数据在存储时仅需3个字节的空间, 在插入数据到DATE类型的字段时, 必须确保数据符合指定的格式要求.

日期数据格式(也可以使用数字): 
* 1. 标准的YYYY-MM-DD格式或更为紧凑的YYYYMMDD格式.
     无论采用哪种格式, 其有效范围都是固定的, 即最小日期为1000-01-01, 最大日期为9999-12-31.
     值得注意的是, 如果提供的是YYYYMMDD格式, 系统会自动将其转换为标准的YYYY-MM-DD格式.

* 2. 简化的日期格式YY-MM-DD或YYMMDD, 其中YY代表两位数的年份.
     对于这种格式, 年份的处理有特定的规则:
     如果年份在0069之间, 系统会自动将其转换为2000年到2069;
     如果年份在7099之间, 则会被转换为1970年到1999.
     这种规则有助于处理20世纪和21世纪早期的年份, 避免了因年份缩写而产生的歧义.

在需要插入当前系统日期时, 可以使用SQL的内置函数.
具体来说, 可以使用CURRENT_DATE()函数来获取当前日期(不包含时间), 并将其插入到DATE类型的字段中.
另外, NOW()函数也可以实现类似的功能, 但它返回的是当前日期和时间, 因此在只需要日期的情况下, 使用CURRENT_DATE()更为合适.
这些函数使得在插入日期数据时能够方便地获取当前的日期信息.
-- 创建表格:
mysql> CREATE TABLE test_date1(
    f1 DATE
);
Query OK, 0 rows affected (0.01 sec)

-- 查看表结构:
mysql> DESC test_date1;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| f1    | date | YES  |     | NULL    |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 插入数据:
mysql> INSERT INTO test_date1
VALUES ('2020-10-01'), ('20201001'), (20201001);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- 插入更多数据:
mysql> INSERT INTO test_date1 VALUES
('00-01-01'), ('000101'),
('69-10-01'), ('691001'), 
('70-01-01'), ('700101'),
('99-01-01'), ('990101');
Query OK, 8 rows affected (0.00 sec)
Records: 8  Duplicates: 0  Warnings: 0

-- 数字类型:
INSERT INTO test_date1
VALUES (000301), (690301), (700301), (990301);
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

-- 使用函数获取时间:
INSERT INTO test_date1
VALUES (CURRENT_DATE()), (NOW());

-- 查看数据:
mysql> SELECT * FROM test_date1;
+------------+
| f1         |
+------------+
| 2020-10-01 |
| 2020-10-01 |
| 2020-10-01 |
| 2000-01-01 |
| 2000-01-01 |
| 2069-10-01 |
| 2069-10-01 |
| 1970-01-01 |
| 1970-01-01 |
| 1999-01-01 |
| 1999-01-01 |
| 2000-03-01 |
| 2069-03-01 |
| 1970-03-01 |
| 1999-03-01 |
+------------+
15 rows in set (0.00 sec)

8.3 TIME类型

TIME类型专门用于表示时间, 不包含日期信息.
在MySQL数据库中, TIME类型的数据需要3个字节的存储空间.
该类型的时间数据通常采用'HH:MM:SS'的格式进行表示, 其中HH代表小时, MM代表分钟, SS代表秒.

在MySQL中, 向TIME类型的字段插入数据时, 可以灵活使用多种格式:
* 1. 带有冒号的字符串格式, 'D HH:MM:SS', 'HH:MM:SS', 'HH:MM', 'D HH:MM', 'D HH''SS'.
     其中, D代表天数, 其取值范围是从034.
     当使用包含D的字符串格式插入数据时, D将被转换为小时, 转换的规则是D乘以24再加上HH.
     若只使用带有冒号且不包含D的字符串表示时间, 则默认为当天的时间, 例如'12:10'表示的是'12:10:00', 而不是'00:12:10'.

* 2. 不带有冒号的字符串或数字格式, 'HHMMSS'或HHMMSS.
     MySQL在解析这种格式时会自动进行适配, 将最右边的两位解析为秒.
     例如, 对于字符'1210', MySQL会将其解析为'00:12:10', 而不是'12:10:00'.
     如果插入的字符串或数字不符合TIME类型的格式要求, MySQL会将其自动转换为'00:00:00'进行存储.

可以使用MySQL的内置函数CURRENT_TIME()来获取当前的时间信息, 并将其插入到TIME类型的字段中.
-- 创建表格:
mysql> CREATE TABLE test_time1(
    f1 TIME
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test_time1;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| f1    | time | YES  |     | NULL    |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 插入数据(带:双数不要秒, 单数则为秒):
mysql> INSERT INTO test_time1
VALUES('2 12:30:29'), ('12:35:29'), ('12:40'), ('2 12:40'),
('1 05'), ('45'), ('123520'), (124011), (1210);
Query OK, 9 rows affected (0.01 sec)
Records: 9  Duplicates: 0  Warnings: 0

-- 使用函数获取:
mysql> INSERT INTO test_time1
VALUES (CURRENT_TIME()), (NOW());
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 查看数据:
mysql> SELECT * FROM test_time1;
+----------+
| f1       |
+----------+
| 60:30:29 |
| 12:35:29 |
| 12:40:00 |
| 60:40:00 |
| 29:00:00 |
| 00:00:45 |
| 12:35:20 |
| 12:40:11 |
| 00:12:10 |
| 14:19:56 |
| 14:19:56 |
+----------+
11 rows in set (0.00 sec)

8.4 DATETIME类型

DATETIME类型在MySQL中用于同时表示日期和时间, 其存储空间需求为8个字节, 是所有日期时间类型中占用空间最大的.
其格式结合了DATE和TIME类型的特点, 可以表示为'YYYY-MM-DD HH:MM:SS', 
其中YYYY代表四位数的年份, MM代表两位数的月份, DD代表两位数的日期, HH代表小时, MM代表分钟, SS代表秒.

在插入数据到DATETIME类型的字段时, 必须确保数据满足特定的格式要求.
* 1. 四位数的年份'YYYY-MM-DD HH:MM:SS'格式或'YYYYMMDDHHMMSS'格式的字符串都可以被正确识别并插入.
     该类型的取值范围是从'1000-01-01 00:00:00''9999-12-31 23:59:59'.
     如果以'YYYYMMDDHHMMSS'格式的数字进行插入, MySQL会自动将其转换为'YYYY-MM-DD HH:MM:SS'格式.

* 2. 两位数的年份, 'YY-MM-DD HH:MM:SS''YYMMDDHHMMSS'格式, MySQL会遵循YEAR类型的规则进行解析.
     具体来说, 当年份取值为0069, 它会被视为2000年到2069; 当年份取值为7099, 它会被视为1970年到1999.

MySQL提供了内置函数CURRENT_TIMESTAMP()和NOW()返回当前的日期和时间, 并可以直接用于向DATETIME类型的字段插入数据.
-- 创建表格
mysql> CREATE TABLE test_datetime1(
    dt DATETIME
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test_datetime1;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| dt    | datetime | YES  |     | NULL    |       |
+-------+----------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 插入数据(正常格式与紧凑格式):
mysql> INSERT INTO test_datetime1
VALUES ('2021-01-01 06:50:30'), ('20210101065030');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 两位数格式:
mysql> INSERT INTO test_datetime1
VALUES ('99-01-01 00:00:00'), ('990101000000'), 
('20-01-01 00:00:00'), ('200101000000');
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

-- 数字格式:
mysql> INSERT INTO test_datetime1
VALUES (20200101000000), (200101000000), 
(19990101000000), (990101000000);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

-- 使用函数获取当前时间:
mysql> INSERT INTO test_datetime1
VALUES (CURRENT_TIMESTAMP()), (NOW());
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 查看数据:
mysql> SELECT * FROM test_datetime1;
+---------------------+
| dt                  |
+---------------------+
| 2021-01-01 06:50:30 |
| 2021-01-01 06:50:30 |
| 1999-01-01 00:00:00 |
| 1999-01-01 00:00:00 |
| 2020-01-01 00:00:00 |
| 2020-01-01 00:00:00 |
| 2020-01-01 00:00:00 |
| 2020-01-01 00:00:00 |
| 1999-01-01 00:00:00 |
| 1999-01-01 00:00:00 |
| 2024-03-26 14:37:03 |
| 2024-03-26 14:37:03 |
+---------------------+
12 rows in set (0.00 sec)

8.5 TIMESTAMP类型

TIMESTAMP类型在数据库中用于表示日期和时间, 其展示格式与DATETIME类型一致, 都是采用YYYY-MM-DD HH:MM:SS的形式.
尽管在显示上它们看起来相同, 但在存储空间需求和时间范围上, 两者存在显著差异.
TIMESTAMP类型需要4个字节的存储空间, 但所能表示的时间范围较为有限, 
仅限于'1970-01-01 00:00:01 UTC''2038-01-19 03:14:07 UTC'之间.

这里的UTC指的是协调世界时, 也称为世界标准时间, 是国际上统一使用的标准时间.
由于TIMESTAMP类型存储的是UTC时间, 因此在存储数据时, 需要将当前时间从本地时区转换为UTC时区;
而在查询数据时, 又需要将UTC时间转换回本地时区.
这一特性导致, 使用TIMESTAMP存储的同一时间值, 在不同时区的查询结果中会显示为不同的本地时间.

当向TIMESTAMP类型的字段插入数据时, 如果数据的格式满足YY-MM-DD HH:MM:SS或YYMMDDHHMMSS,
其中两位数值的年份同样需遵循YEAR类型的规则条件, 不过其时间范围远小于YEAR类型.
如果尝试插入超出TIMESTAMP类型时间范围的数据, MySQL将返回错误信息, 阻止无效数据的存储.
举个例子来说明时间转换这一特性:
假设有一个位于美国纽约(东部标准时间, EST)的数据库服务器, 并且在该服务器上有一个TIMESTAMP类型的字段.
现在, 有一个用户位于英国伦敦(格林尼治标准时间, GMT), 他尝试向这个字段插入当前时间.

存储数据: 当英国伦敦的用户尝试插入当前时间(例如: 2023-04-01 12:00:00 GMT), 
         数据库服务器会接收这个时间并将其转换为UTC时间.
         由于伦敦比纽约快5小时, 所以纽约当前的UTC时间是2023-04-01 07:00:00 UTC.
         这个UTC时间会被存储到TIMESTAMP字段中.
         
查询数据: 当纽约的用户查询这个字段时, 数据库会返回存储的UTC时间(2023-04-01 07:00:00 UTC),.
         但是, 为了显示给用户一个有意义的时间, 数据库会把这个UTC时间转换回纽约的本地时间, 
         即EST时间(2023-04-01 07:00:00 EST).
         
         相反, 如果伦敦的用户查询这个字段, 数据库同样会返回UTC时间(2023-04-01 07:00:00 UTC), 
         但这次会将其转换回伦敦的本地时间, 即GMT时间(2023-04-01 12:00:00 GMT).

尽管数据库中存储的是相同的UTC时间, 但由于用户的时区不同, 他们查询到的本地时间也会有所不同.
-- 创建表格:
mysql> CREATE TABLE test_timestamp1(
    ts TIMESTAMP
);
Query OK, 0 rows affected (0.02 sec)

-- 查看表结构:
mysql> DESC test_timestamp1;
+-------+-----------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+-------+
| ts    | timestamp | YES  |     | NULL    |       |
+-------+-----------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 插入数据:
mysql> INSERT INTO test_timestamp1
VALUES ('1999-01-01 03:04:50'), ('19990101030405'),
('99-01-01 03:04:05'), ('990101030405');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings:

-- 自定义间隔符号:
mysql> INSERT INTO test_timestamp1
VALUES ('2020@01@01@00@00@00'), ('20@01@01@00@00@00');
Query OK, 2 rows affected, 2 warnings (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 2

-- 使用函数获取时间:
mysql> INSERT INTO test_timestamp1
VALUES (CURRENT_TIMESTAMP()), (NOW());
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- 查看表格数据:
mysql> SELECT * FROM test_timestamp1;
+---------------------+
| ts                  |
+---------------------+
| 1999-01-01 03:04:50 |
| 1999-01-01 03:04:05 |
| 1999-01-01 03:04:05 |
| 1999-01-01 03:04:05 |
| 2020-01-01 00:00:00 |
| 2020-01-01 00:00:00 |
| 2024-03-26 14:43:14 |
| 2024-03-26 14:43:14 |
+---------------------+
8 rows in set (0.00 sec)

-- 超出最大值 2038-01-19 报错:
mysql> INSERT INTO test_timestamp1
VALUES ('2038-01-20 03:14:07');
ERROR 1292 (22007): Incorrect datetime value: '2038-01-20 03:14:07' for column 'ts' at row 1

image-20240326153946119

-- 时间差示例:
-- 创建表格:
mysql> CREATE TABLE temp_time(
    d1 TIMESTAMP
);
Query OK, 0 rows affected (0.02 sec)

-- 插入当前时间:
mysql> INSERT INTO temp_time VALUES (NOW());
Query OK, 1 row affected (0.00 sec)

-- 查看表格数据:
mysql> SELECT * FROM temp_time;
+---------------------+
| d1                  |
+---------------------+
| 2024-03-26 15:34:38 |
+---------------------+
1 row in set (0.00 sec)

-- 修改时区(将时区向后调整了4个小时):
mysql> SET time_zone = '+4:00';
Query OK, 0 rows affected (0.00 sec)

-- 查看表格数据:
mysql> SELECT * FROM temp_time;
+---------------------+
| d1                  |
+---------------------+
| 2024-03-26 11:34:38 |
+---------------------+
1 row in set (0.00 sec)

8.6 使用建议

在实际的项目开发中, DATETIME 类型的日期时间数据被广泛应用, 这得益于它包含完整的日期和时间信息, 以及广泛的取值范围.
相较于 YEAR, TIME, DATE  TIMESTAMP 等其他类型, DATETIME 提供了更为丰富的数据展示, 满足了大多数应用场景的需求.
同时, 其直观性也使得使用者在记录和理解数据时更为便捷.

值得注意的是, 尽管 DATETIME 在日常应用中表现优秀, 但在某些特定场景下,
比如存储注册时间, 商品发布时间等, 使用时间戳(UNIX TIMESTAMP)可能更为合适.
这是因为时间戳是以从某个固定时间点(通常是197011 00:00:00 UTC)开始的秒数来表示的, 
它便于进行时间的计算和比较尤其在需要进行时间差计算或排序等操作时.
此外, 时间戳通常占用更少的存储空间, 这在处理大量数据时尤为重要.

然而, 这并不意味着 DATETIME 就应该被完全弃用.
在选择日期时间类型时, 我们应根据具体的应用场景和需求来决定.
对于需要展示完整日期和时间信息的情况, DATETIME 无疑是更好的选择;
而对于需要频繁进行时间计算和比较的场景, 时间戳则可能更为合适.
在实际应用中, 我们需要根据项目的具体需求来灵活选择和使用这些日期时间类型.

9. JSON类型


9.1 类型介绍

JSON(JavaScript Object Notation): 是一种轻量级的数据交换格式, 其简洁且清晰的层次结构使其成为理想的数据交换语言.
它不仅易于人类阅读和编写, 同时也易于机器解析和生成, 从而有效地提升了网络传输效率.
JSON能够将JavaScript对象中的一组数据转换为字符串, 使得这些字符串可以在网络或程序之间轻松传递,
并在需要时还原为各编程语言所支持的数据格式.

从MySQL 5.7开始, 已经支持JSON数据类型, 这为在数据库中存储结构化数据提供了更大的灵活性.
到了MySQL 8.x版本, JSON类型的支持得到了进一步增强.
不仅提供了可以自动验证的JSON文档, 还优化了存储结构, 使得在MySQL中存储和读取JSON类型的数据变得更为方便和高效.

JSON类型并没有一个固定的字节大小, MySQL内部是作为二进制字符串来存储的, 具体占用的字节数取决于存储的JSON数据的大小.
JSON类型描述说明
JSON用于存储JSON格式的数据.可以存储任何有效的JSON数据, 包括对象, 数组, 字符串, 数字, 布尔值和NULL.
JSON数据在MySQL中以二进制字符串的形式存储, 但可以使用专门的JSON函数进行查询和操作.
JSON类型列可以接受空值(NULL).
JSON_OBJECT返回一个JSON对象.在查询时用于构建JSON对象.
可以使用列值或常量作为对象的键和值.
返回的对象是一个JSON字符串.
JSON_ARRAY返回一个JSON数组.在查询时用于构建JSON数组.
可以使用列值或常量作为数组的元素.
返回的数组是一个JSON字符串.
虽然上述表格中列出了JSON_OBJECT和JSON_ARRAY, 但它们实际上是用于构建JSON数据的函数, 而不是数据类型本身.
在创建表时, 会使用JSON数据类型来定义列, 然后使用这些函数在查询时构建和操作JSON数据.

关于范围, JSON类型在MySQL中没有固定的长度限制, 它取决于存储的JSON数据的大小.
然而, 需要注意的是, 过大的JSON数据可能会对性能产生影响, 因此在设计数据库和查询时需要谨慎考虑.

MySQL提供了一系列专门的JSON函数, 用于在查询时验证, 提取和修改JSON数据.
这些函数使得在MySQL中处理JSON数据变得相对简单和高效.
可以使用这些函数来查询JSON对象中的特定字段, 修改JSON数据, 数组操作等.

MySQL的JSON类型提供了一种灵活的方式来存储和查询JSON格式的数据, 使得在数据库中直接处理结构化数据变得更加方便.
-- 创建表格:
mysql> CREATE TABLE test_json(
    js json
);
Query OK, 0 rows affected (0.01 sec)

-- 查看表结构:
mysql> DESC test_json;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| js    | json | YES  |     | NULL    |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.00 sec)

-- 插入json格式数据:
mysql> INSERT INTO test_json (js)
VALUES ('{"name":"kid", "age":18, "address":{"province":"beijing",
"city":"beijing"}}');
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT * FROM test_json;
+-----------------------------------------------------------------------------------+
| js                                                                                |
+-----------------------------------------------------------------------------------+
| {"age": 18, "name": "kid", "address": {"city": "beijing", "province": "beijing"}} |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

9.2 检索数据

在MySQL中, 当需要检索JSON类型字段中数据的某个具体值时, 可以使用'->''->>'这两个JSON路径运算符.

->运算符用于从JSON列中提取值, 并返回JSON格式的数据; 如果指定的路径不存在, 则返回NULL.
->>运算符与->类似, 但它返回的是非JSON格式的字符串; 如果路径不存在, 它同样返回NULL.

在MySQL的JSON函数中, '$'是一个特殊的路径表示法, 用于指向JSON对象的根.
在JSON路径中使用'$', 表示从JSON对象的顶部开始导航.

: 
->运算符来检索name字段的值:  SELECT js -> '$.name' FROM test_json; 
->>运算符检索上面的name字段: SELECT js ->>'$.name' FROM test_json;
运算符前面写上检索的字段名称, '$.name'表示从JSON对象的根($)开始, 然后导航到name字段.

如果你想获取address字段下的city字段的值, 可以嵌套使用, 
: SELECT js->'$.address.city' FROM test_json;
$.address.city表示从根开始, 先导航到address字段, 然后再导航到city字段.
-- 返回js格式数据:
mysql> SELECT 
js -> '$.name' AS `name`,
js -> '$.age' AS `age`,
js -> '$.address.province'AS `province`,
js -> '$.address.city' AS `city`
FROM test_json;
+-------+------+-----------+-----------+
| name  | age  | province  | city      |
+-------+------+-----------+-----------+
| "kid" | 18   | "beijing" | "beijing" |
+-------+------+-----------+-----------+
1 row in set (0.01 sec)

-- 返回非js格式数据:
mysql> SELECT 
js ->> '$.name' AS `name`,
js ->> '$.age' AS `age`,
js ->> '$.address.province'AS `province`,
js ->> '$.address.city' AS `city`
FROM test_json;
+------+------+----------+---------+
| name | age  | province | city    |
+------+------+----------+---------+
| kid  | 18   | beijing  | beijing |
+------+------+----------+---------+
1 row in set (0.01 sec)

9.3 JSON_OBJECT函数

JSON_OBJECT()函数: 用于快速创建JSON对象.
它的使用方式是将键值对作为参数传入, 其中奇数位置的参数被视为键(key), 偶数位置的参数被视为值 (value).
语法: JSON_OBJECT(key1, val1, key2, val2, ...);
示例: 假设我们有一个名为 user 的表, 其中有一个JSON类型的列 info,
我们想要向其中插入一个包含用户姓名和年龄的JSON对象.
sql: INSERT INTO user (name, info)  
VALUES ('kid', JSON_OBJECT('gender', 'male', 'age', 18));
这条SQL语句将在 user 表中插入一条记录, 其中 name 列的值为 'John',
info 列的值为一个包含 'gender'  'age' 键值对的JSON对象.
-- 创建表格:
mysql> CREATE TABLE user(
    name VARCHAR(20),
    info JSON
);
Query OK, 0 rows affected (0.02 sec)

-- 插入数据:
mysql> INSERT INTO user (name, info)  
VALUES ('kid', JSON_OBJECT('gender', 'male', 'age', 18));
Query OK, 1 row affected (0.01 sec
                          
-- 查看数据:
mysql> SELECT name,
info ->> '$.gender' AS `gender`,
info ->> '$.age'AS `age`
FROM user;
+------+--------+------+
| name | gender | age  |
+------+--------+------+
| kid  | male   | 18   |
+------+--------+------+
1 row in set (0.00 sec)

9.4 JSON_ARRAY函数

JSON_ARRAY()函数: 用于快速创建JSON数组, 它将所有传入的参数作为数组的元素,
语法: JSON_ARRAY(val1, val2, ...);
示例: 同样以 user 表为例, 假设我们想要向 info 列插入一个包含用户爱好的JSON数组.

sql: INSERT INTO user (name, info)  
VALUES ('kid', JSON_ARRAY('reading', 'swimming', 'painting'));
这条SQL语句将在 user 表中插入一条记录, 其中 name 列的值为 'kid', info 列的值为一个包含三个爱好的JSON数组.

此外, JSON_ARRAY 也可以用在 SELECT 语句中, 与其他列的数据一起返回, 或者用在 UPDATE 语句中, 更新表中的JSON数据.
-- 插入数据:
mysql> INSERT INTO user (name, info)
VALUES ('kid', JSON_ARRAY('reading', 'swimming', 'painting'));
Query OK, 1 row affected (0.01 sec)

-- 查看数据:
mysql> SELECT name,
info ->> '$' AS `info`
FROM user;
+------+-------------------------------------+
| name | info                                |
+------+-------------------------------------+
| kid  | {"age": 18, "gender": "male"}       |
| kid  | ["reading", "swimming", "painting"] |
+------+-------------------------------------+
2 rows in set (0.00 sec)

10. 空间数据类型


MySQL的空间数据类型为地理特征的生成, 存储和分析提供了强大的支持.
这些地理特征可以是世界上的任何具有位置的事物, 
无论是一个具体的实体, 如山峰, 还是一个空间范围, 如办公楼, 甚至是可定义的位置点, 如十字路口.

在MySQL中, Geometry类型是所有空间数据类型的基类, 它代表了一个点或点的集合, 用于描述世界上任何具有位置的事物.
而基于Geometry, MySQL还提供了其他更具体的空间数据类型,  POINT, LINESTRING, POLYGON ,
以及它们的集合类型, : MULTIPOINT, MULTILINESTRING, MULTIPOLYGON  GEOMETRYCOLLECTION.

POINT类型用于表示一个具体的点, 它有一个坐标值, 通常包含经度和纬度两个字段.
例如, POINT(121.213342 31.234532) 表示了一个经度为 121.213342, 纬度为 31.234532 的点.

LINESTRING类型用于表示由一系列点连接而成的线.
这些线可以是简单的(没有交叉), 也可以是封闭的(起点和终点重叠).
例如, LINESTRING(30 10, 10 30, 40 40) 表示了一条由三个点连接而成的线.

POLYGON类型用于表示多边形.
多边形可以是一个实心的平面形状, 也可以包含内部空洞.
最简单的多边形只有一个外边界, 例如: POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)) 表示了一个矩形.

MultiPoint, MultiLineString, MultiPolygon, GeometryCollection这4种类型都是集合类,
是多个Point, LineString或Polygon组合而成.
空间数据类型(单值)描述范围
GEOMETRY所有空间数据类型的基类.表示任意的几何对象, 可以是点, 线, 多边形等.
POINT表示二维空间中的一个点.包含一个坐标值, 如(x, y).
LINESTRING表示一系列有序的点, 形成一条线.由至少两个点组成, 表示一条连续的线段.
POLYGON表示一个多边形.由一系列有序的点组成, 第一个和最后一个点必须相同, 表示闭合的多边形.
空间数据类型(多值)描述范围
MULTIPOINT表示多个点的集合.包含多个POINT对象.
MULTILINESTRING表示多条线的集合.包含多个LINESTRING对象.
MULTIPOLYGON表示多个多边形的集合.包含多个POLYGON对象.
GEOMETRYCOLLECTION表示几何对象的集合, 可以是点, 线, 多边形等的任意组合.包含上述任意类型的几何对象.

202403261803321

11. 总结


阿里巴巴<<Java开发手册>>之MySQL数据库:
* 0. [个人补充]: 在定义数据类型时, 如果确定是整数, 就用INT; 如果是日期与时间, 就用DATETIME.
* 1. 任何字段如果为非负数, 必须是 UNSIGNED
* 2. [强制] 小数类型为 DECIMAL, 禁止使用 FLOAT  DOUBLE.
     说明: 在存储的时候, FLOAT  DOUBLE 都存在精度损失的问题, 很可能在比较值的时候, 得到不正确的结果.
     如果存储的数据范围超过 DECIMAL 的范围, 建议将数据拆成整数和小数并分开存储.
* 3. [强制] 如果存储的字符串长度几乎相等, 使用 CHAR 定长字符串类型.
* 4. [强制] VARCHAR 是可变长字符串, 不预先分配存储空间, 长度不要超过 5000.
     如果存储长度大于此值, 定义字段类型为 TEXT, 独立出来一张表, 用主键来对应, 避免影响其它字段索引效率.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值