扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

DBA | MySQL 数据库五种数据类型介绍实践指南

2025/09/25
1604
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 ,若此文对你有帮助,一定记得倒点个关注⭐与小红星❤️,收藏学习不迷路  。

MySQL 数据类型

描述:在前面章节中,我们实践了如何创建一个表,并简单定义表中的字段名称,以及简单的类型。此小节将深入讲解 MySQL 8.x 支持的数据类型。

原文链接:https://articles.zsxq.com/id_wj25n3h3ejx7.html

什么是数据类型?为什么要出现数据类型?

定义数据类型的本质是在定义列,因为不同的数据类型可以存储并且高效处理各类数据,并且进行数据处理的方式算法也是有所不同的,这也是关系型数据库的一个特点。

数据分类的意义是什么?

1.分配合适的存储空间,减少磁盘占用,不同的数据类型其存储空间是不一致的。 2.提高查询效率,不同的数据类型其处理方式也是有所差异的。 3.提高数据的安全性,例如:不允许用户输入负数,可以使用无符号类型。 4.提高数据的一致性,例如:不允许用户输入非数字类型的数据。

MySQL 支持的数据类型大致可以分为以下几类:数字类型字符串 (字符和字节)类型日期和时间类型空间类型和 JSON 数据类型

参考来源: https://dev.mysql.com/doc/refman/8.4/en/data-types.html

1.数字类型

描述:MySQL 支持多种数字类型,每种类型的存储空间和取值范围不同, 其取值范围越大需要的空间也就越多, 此外,特别注意有无符号(UNSIGNED)数据取值范围。

数字类型分类:

    位值类型 - BIT : M 表示每个值的位数,据指定的位数(n)来分配存储空间,其取值范围为1 ~ 64,以二进制形式存储,占用空间极小(如BIT(8)仅需1字节),缺省为 1 表示 0 与 1,无符号类型。布尔类型 - BOOL, BOOLEAN : 是 TINYINT(1) 的同义词占用1字节,使用二进制形式存储,0 表示 FALSE,非零值表示 true:整数类型(精确值) - INTEGER、INT、SMALLINT、TINYINT、 MEDIUMINT,BIGINT浮点类型(近似值) - FLOAT、DOUBLE定点类型(精确值) - DECIMAL、NUMERIC
类型 类型名称 存储大小 有符号范围(SIGNED) 无符号范围(UNSIGNED) 备注
位值 BIT(M) M 位 - - M 其取值范围为1 ~ 64,使用二进制形式存储
布尔 BOOL 1 字节 - 0,255 0 表示FALSE,非零值表示true
整数 TINYINT 1 字节 -128 ~ 127 0 ~ 255 很小的整数
整数 SMALLINT 2 字节 -32768 ~ 32767 0 ~ 65535 小的整数
整数 MEDIUMINT 3 字节 -8388608 ~ 8388607 0 ~ 16777215 中等大小的整数
整数 INT 或 INTEGHR 4 字节 -2147483648 ~ 2147483647 0 ~ 4294967295 普通大小的整数
整数 BIGINT 8 字节 -9223372036854775808 ~ 9223372036854775807 0〜18446744073709551615 大整数
小数 FLOAT(M,D) 4 字节 -3.402823466 E+38 ~ 3.402823466 E+38 0 和 (1.175494351 E-38~3.402823466 E+38) 单精度浮点数,显示宽度和小数位数
小数 DOUBLE(M,D) 8 字节 -1.7976931348623157 E+308 ~ 1.7976931348623157 E+308 0 和 (2.2250738585072014 E-308~1.7976931348623157 E+308) 双精度浮点数,显示宽度和小数位数
小数 DECIMAL(M,D) M+2 字节或更大 取决于精度和范围 取决于精度和范围 精确定点数,M为精度(总位数), D为标度(小数位数),缺省 (10,0)
小数 NUMERIC(M,D) M+2 字节或更大 取决于精度和范围 取决于精度和范围 精确定点数,M为精度(总位数), D为标度(小数位数)

由上表可知,每种类型的取值范围是不同的,开发或者DBA通常需要根据业务需求进行选择字段对应类型。

BIT(M) : 适合于用户性别、状态标记、用户权限、配置参数选项字段,例如 BIT(1) 可用 0 表示关闭、1表示启用。
BOOL, BOOLEAN : 适合于用户状态,例如 false 表示关闭、 true 表示启用。
TINYINT : 适合于存储较小的整数值,例如用户年龄、用户等级等。
SMALLINT : 适合于存储较小的整数值,例如等级、评分等。
MEDIUMINT : 适合于存储中等大小的整数值,例如用户ID、订单号等。
INT, INTEGER : 适合于存储较大的整数值,例如用户ID、订单号等。
BIGINT : 适合于存储非常大的整数值,例如人口信息、订单号、物流号等。
FLOAT : 适合于存储单精度浮点数,例如价格、评分等。
DOUBLE(M,D) : 适合于存储双精度浮点数,例如地理坐标、科学实验数据等,例如 DOUBLE(5,2) 表示最多存储 5 位数字,其中小数点后保留两位。
DECIMAL(M,D) : 适合于存储精确的小数,例如金钱、财务等,例如 DECIMAL(5,2) 表示最多存储 5 位数字,其中小数点后保留两位,取值范围 -999.99 到 999.99。

知识扩展:DOUBLE(M,D) 和 DECIMAL(M,D) 的主要差异体现在存储方式、精度控制和适用场景三个方面。

DOUBLE‌:基于 IEEE 754 标准的双精度浮点数,使用 8 字节存储,以二进制近似存储数值,可能存在舍入误差;其 (M,D) 参数仅影响显示宽度,不限制实际存储范围;计算速度更快,适合科学计算等对性能敏感,但对精度要求不高的场景。

DECIMAL:定点数类型,以字符串形式精确存储数值,每 4 字节存储 9 位数字(如 DECIMAL(18,2) 占 9 字节),完全避免浮点误差;(M,D) 严格限制存储范围(M 为总位数,D 为小数位,且 D ≤ M);计算开销较大,存储空间随精度增长(如 DECIMAL(65,30) 需要更多字节),但保证精确性。

在 MySQL 中,定点数以字符串形式存储,在对精度要求比较高的时候(如货币、科学数据),使用 DECIMAL 的类型比较好,另外两个浮点数进行减法和比较运算时也容易出问题,所以在使用浮点数时需要注意,并尽量避免做浮点数比较。

温馨提示:若为字段配置了自增长属性,则该字段必须是整数无符号(UNSIGNED)类型,即不能为负数。

温馨提示:如果为数字列指定 ZEROFILL(MySQL 8.X 之后将 deprecated )表示长度不够将自动填充 0,并且 MySQL 会自动将 UNSIGNED 属性添加到该列中。

温馨提示:MySQL 8.0 后已弃用 FLOAT/DOUBLE(M,D) 非标准扩展语法,其 (M,D) 参数仅作兼容保留,实际存储仍按浮点数规则处理,并且FLOAT 和 DOUBLE 在不指定精度时,默认会按照实际的精度(由计算机硬件和操作系统决定),DECIMAL 如果不指定精度,默认为(10,0)。

示例演示

例1.创建一个 tinyint_test 指定无符号与有符号的字段, 并插入几条数据查看效果。

CREATE TABLE tinyint_test (
  col1 TINYINT,          -- 默认是有符号
  col2 TINYINTUNSIGNED-- 定义无符号
);

-- ERROR 1264 (22003): Out of range value for column 'col1' at row 1
INSERTINTO tinyint_test VALUES (-129,128); 
-- ERROR 1264 (22003): Out of range value for column 'col2' at row 1
INSERTINTO tinyint_test VALUES (-128,256); 
-- 正确插入
INSERTINTO tinyint_test VALUES (-128,255); 

例2.创建一个 zerofill_test 表,当前缀不够时自动填充0,并插入几条数据查看效果。

CREATE TABLE zerofill_test (
  col1 INT(4) ZEROFILL -- 长度不够4时则填充0
);

INSERTINTO zerofill_test VALUES (1234),(123),(12);
Query OK, 3 rows affected (0.11 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM zerofill_test;
+------+
| col1 |
+------+
| 1234 |
| 0123 |
| 0012 |
+------+

例3.创建一个 float_test 表,并插入几条数据验证浮点数查看效果。

CREATE TABLE float_test (
  col1 FLOAT(3,2),
  col2 DOUBLE(3,2)
); 

INSERTINTO float_test VALUES (6.3,6.3),(6.33,6.33),(6.333,6.333),(6.335,6.335);
Query OK, 4 rows affected (0.15 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM float_test;
+------+------+
| col1 | col2 |
+------+------+
| 6.30 | 6.30 | -- 位数不够采用0补齐
| 6.33 | 6.33 | -- 刚好
| 6.33 | 6.33 | -- 超出了小数范围,只留下两位小数部分
| 6.34 | 6.34 | -- 超出了小数范围,当第三位是5时,则进1变为6.34
+------+------+

-- 此外, 若整数部分超出宽度范围,则插入报错,而小数部分插入时无强预设小数位数限制。
-- ERROR 1264 (22003): Out of range value for column 'col1' at row 1 
INSERTINTO float_test VALUES (66.3,66.3); -- 插入失败,因为小数占两位,总宽度三位,整数部分超出范围多了一位
INSERTINTO float_test VALUES (6.33333,6.33333); -- 插入正常,不受小数位数限制

例4.创建一个 decimal_test 表,并插入几条数据验证定点数查看效果。

CREATE TABLE decimal_test (
  col1 DECIMAL(5,3) ZEROFILL -- 表示 2位整数,3位小数,不足的以 0 填充
);

INSERTINTO decimal_test VALUES (66.55),(66.555),(66.5554),(66.5556);
Query OK, 4 rows affected, 1 warning (0.11 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> SELECT * FROM decimal_test;
+--------+
| col1   |
+--------+
| 66.550 |  -- 位数不足,填充0
| 66.555 |
| 66.555 |  -- 位数超过,只截取指定小数位数
| 66.556 |  -- 位数超过,若小数部分是最后一位大于等于5,则进1变为66.556
+--------+

2.字符串 (字符和字节)类型

描述:MySQL 字符串类型是一个统称它可以包含多种不同的数据类型,大致可以分为两大类:文本字符型二进制字节型,其中字符类型可存储单个字符、多个字符、数字、字母等,字节类型可存储二进制数据,而二进制字节型可存储图片、音频、视频等二进制数据。

字符串类型分类:

    文本字符型 - CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT二进制字节型 - BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB枚举类型 - ENUM、SET
类型名称 存储大小 备注
CHAR(M) M 字节,0 ~ 255 字节 定长字符串,最大长度为255个字符(8.0 版本之前),最大长度65535个字符(8.0 版本之后)
VARCHAR(M) M+1 字节,0 ~ 65535 字节 变长字符串
TINYTEXT L+1 字节,最大长度为 255 字节 小文本
TEXT L+2 字节,最大长度为 65535 字节 大文本
MEDIUMTEXT L+3 字节,最大长度为 16 777 215 字节 中等大小文本
LONGTEXT L+4 字节,最大长度为 4 294 967 295 字节 极大文本
BINARY(M) M 字节 定长二进制字符串
VARBINARY (M) M+1 字节 可变二进制字符串
TINYBLOB L+1 字节,最大长度为 255 字节 非常小的二进制对象
BLOB L+2 字节,最大长度为 65535 字节 小二进制对象
MEDIUMBLOB L+3 字节,最大长度为 16 777 215 字节 中等大小二进制对象
LONGBLOB L+4 字节,最大长度为 4 294 967 295 字节 极大二进制对象
ENUM('value1','value2',...) 1或2个字节,取决于枚举值的数目 (最大值为65535) 枚举类型,只能有一个枚举字符串值
SET('value1','value2',...) 1、2、3、4或8个字节,取决于集合成员的数量(最多64个成员) 集合类型,字符串对象可以有零个或多个SET成员

知识扩展:CHAR 与 VARCHAR 的区别,主要体现在存储空间、性能和适用场景三个方面。

CHAR 和 VARCHAR 类型都可以存储字符串,但它们在内部处理和性能上有所不同,前者性能优于后者。 CHAR 和 VARCHAR 类型存储时占用的空间大小不同,前者固定长度,后者可变长度,所以前置适合存储固定长度的字段场景, 如性别,而后者则适合存储长度可变的字段,如用户名

例如,一个 CHAR(10) 列能保存一个长度为 10 个字符/字节的字符串,无论实际存储少于 10 个字符/字节,都会占用固定的 10 个字节的空间。

例如,一个 VARCHAR(10) 列能保存一个最大长度为 10 个字符/字节的字符串,实际的存储需要字符串的长度 L 加上一个字节以记录字符串的长度,对于字符 “abcd”,L 是 4,而存储要求 5 个字节。

示例演示

例1.创建一个 char_test 表,并插入几条数据探究CAHR 与 VARCHAR 类型异同。

CREATE TABLEIFNOTEXISTS char_varchar_test (
  col1 CHAR(4),     -- 固定长度4个字符,无论实际存储少于4个字符,都会占用固定的 4 个字节的空间。
  col2 VARCHAR(4)   -- 可变长度4个字符,实际的存储需要字符串的长度 L 加上一个字节以记录字符串的长度。
);

INSERTINTO char_varchar_test VALUES ('ab  ','ab  '),('a b  ','a b  '),('abc','abc'),('abcd','abcd'),('全栈','全栈'),('全栈技术','全栈技术');
Query OK, 6 rows affected, 1 warning (0.09 sec)
Records: 6  Duplicates: 0  Warnings: 1

-- 使用 concat 函数拼接字符串,可以发现 CHAR 和 VARCHAR 在拼接时差异。
mysql> SELECTconcat(col1,'@'),concat(col2,'@') FROM char_varchar_test;
+------------------+------------------+
| concat(col1,'@') | concat(col2,'@') |
+------------------+------------------+
| ab@              | ab  @            |  -- CHAR 自动去除尾部空格,而 VARCHAR 会保留尾部空格(但不超过设置的字符长度)。
| a b@             | a b @            |
| abc@             | abc@             |
| abcd@            | abcd@            |
| 全栈@            | 全栈@            |
| 全栈技术@        | 全栈技术@        |
+------------------+------------------+

SELECT col1,LENGTH(col1) AS'col1 长度',col2,LENGTH(col2) AS'col2 长度'FROM char_varchar_test;
+--------------+-------------+--------------+-------------+
| col1         | col1 长度   | col2         | col1 长度   |
+--------------+-------------+--------------+-------------+
| ab           |           2 | ab           |           4 | -- 同样可以发现,CHAR 自动去除尾部空格,而 VARCHAR 会保留尾部空格(但不超过设置的字符长度)。
| a b          |           3 | a b          |           4 |
| abc          |           3 | abc          |           3 |
| abcd         |           4 | abcd         |           4 |
| 全栈         |           6 | 全栈         |           6 | -- 中文一个字占用长度为3。
| 全栈技术     |          12 | 全栈技术     |          12 | 
+--------------+-------------+--------------+-------------+

温馨提示:当插入的数据超过定义的长度时,MySQL 会自动截断数据,但是需要你在执行插入语句前执行SET sql_mode=''指令,将 sql_mode 变量参数置位空。例如,对于一个 VARCHAR(10) 类型的列,如果尝试插入一个长度为 15 个字符的字符串,那么只有前 10 个字符会被存储。

SHOW variables LIKE 'sql_mode';
-- 修改 sql_mode 后即使插入的长度超过定义的长度,也不会报错,只会截断。
SET sql_mode='';
INSERT INTO chardemo VALUES ('ABCDEFG','ABCDEFG'); 

例2.创建 text_test 表,并插入几条数据验证文本类型的差异。

CREATE TABLEIFNOTEXISTS text_test (
  col1 TEXT
);

INSERTINTO text_test VALUES ('weiyigeek'),('WeiyiGeek'),('公众号:全栈工程师修炼指南'),('博客:blog.weiyigeek.top');
Query OK, 4 rows affected (0.18 sec)
Records: 4  Duplicates: 0  Warnings: 0

-- 使用模糊查询的方式,可发现TEXT类型在模糊查询时不区分大小写。
mysql> SELECT * FROM text_test WHERE col1 LIKE'wei%';
+-----------+
| col1      |
+-----------+
| weiyigeek |
| WeiyiGeek |

-- 使用 BINARY 关键字,可区分大小写。
mysql> SELECT * FROM text_test WHERE col1 LIKEBINARY'wei%';
+-----------+
| col1      |
+-----------+
| weiyigeek |

 

枚举类型

枚举类型,其其本质是文本字符串类型的一种延伸类型,所以我将其放入到字符串类型中进行讲解。

ENUM

     是一个字符串对象,值为表创建时列规定中枚举的一列值。其值在内部用整数表示,每个枚举值均有一个索引值;列表值所允许的成员值从 1 开始编号,MySQL 存储的就是这个索引编号,枚举最多可以有 65535 个元素。
# 语法格式
<字段名> ENUM( '值1', '值1', …, '值n' )
    SET 是一个字符串的对象,可以有零或多个值,SET 列最多可以有 64 个成员,值为表创建时规定的一列值。 与 ENUM 类型相同,SET 值在内部用整数表示,列表中每个值都有一个索引编号。当创建表时,SET 成员值的尾部空格将自动删除。
# 语法格式
SET( '值1', '值2', …, '值n' )

示例演示,例如,定义 ENUM 类型的列性别,以及 SET 类型的列专业。

CREATE TABLE special_test (
  sex ENUM('F','M','UN'),  -- 插入的值只能是其中之一,分别表示 Female 女、男Male 和未知
  major SET('Java','Python','C++','Go')  -- 插入的值可以是其中一个,也可以是多个值的联合
);

INSERTINTO special_test VALUES ('F','Java,Python'),('M','C++'),('UN',NULL);
mysql> SELECT * FROM special_test;
+------+-------------+
| sex  | major       |
+------+-------------+
| F    | Java,Python |
| M    | C++         |
| UN   | NULL        |
+------+-------------+
3 rows in set (0.00 sec)


-- 若向 ENUM 类型字段插入不合法的值,则会报错 ERROR 1265 (01000): Data truncated for column 'sex' at row 1
INSERTINTO special_test VALUES ('F,M','Java,Python');

综上所述,SET 类型与 ENUM 类型不同的是,ENUM 类型的字段只能从定义的列值中选择一个值插入,而 SET 类型的列可从定义的列值中选择多个字符的联合,简单来说,ENUM 类型是单选,而 SET 类型是多选。

温馨提示:ENUM 列总有一个默认值。如果将 ENUM 列声明为 NULL,NULL 值则为该列的一个有效值,并且默认值为 NULL。如果 ENUM 列被声明为 NOT NULL,其默认值为允许的值列表的第 1 个元素。

# ENUM 值依照列索引顺序排列,并且空字符串排在非空字符串前,NULL 值排在其他所有枚举值前。 
值  索引
NULL  NULL
''  0
F  1
M  2
UN  3

温馨提示:如果插入 SET 字段中的列值有重复,则 MySQL 自动删除重复的值;插入 SET 字段的值的顺序并不重要,MySQL 会在存入数据库时,按照定义的顺序显示;如果插入了不正确的值,默认情况下,MySQL 将忽视这些值,给出警告。

3.日期和时间类型

描述:MySQL 支持多种日期和时间类型,包括 YEAR、DATE、TIME、DATETIME 和 TIMESTAMP 等,每种类型都有其特定的用途和存储格式,这也是在企业开发中需要考虑的一个重要方面。

类型名称 存储大小 取值范围 格式 备注
YEAR 1 byte 1901 到 2155 或 '00' 到 '99' YY YYYY
DATE 3 bytes '1000-01-01' 到 '9999-12-31' YYYY-MM-DD 日期值,通常使用 NOW(),插入当前系统日期。
TIME 3 bytes '-838:59:59' 到 '838:59:59' HH:MM:SS 时间值或持续时间
TIMESTAMP 4 bytes '1970-01-01 00:00:01' UTC 到 '2038-01-19 03:14:07' UTC YYYY-MM-DD HH:MM:SS 或者 'YYYYMMDDHHMMSS' 字符 时间戳值,受时区影响
DATETIME 8 bytes '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59' YYYY-MM-DD HH:MM:SS 混合日期和时间值

知识扩展: 协调世界时(英:Coordinated Universal Time,法:Temps Universel Coordonné)又称为世界统一时间、世界标准时间、国际协调时间,英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称 UTC。

知识扩展:TIMESTAMP 与 DATETIME 日期类型区别。

首先,是存储字节和支持的范围不同,TIMESTAMP 占用4个字节,而 DATETIME 占用8个字节。 其次,DATETIME 按实际输入的格式存储 ,而 TIMESTAMP 是以 UTC(世界标准时间)格式保存的,存储时对当前时区进行转换,检索时再转换回当前时区,即查询时,根据当前时区的不同,显示的时间值是不同的。

温馨提示:若为一个 DATETIME 或 TIMESTAMP 对象分配一个 DATE 值,结果值的时间部分将被设置为 '00:00:00',因此 DATE 值未包含时间信息。如果为一个 DATE 对象分配一个 DATETIME 或 TIMESTAMP 值,结果值的时间部分被删除,因此DATE 值未包含时间信息。

示例演示

-- 例1.获取当前数据库日期、时间、时间戳函数。

SELECT CURRENT_DATE(), CURRENT_TIME(),CURRENT_TIMESTAMP(); 
+----------------+----------------+---------------------+
| CURRENT_DATE() | CURRENT_TIME() | CURRENT_TIMESTAMP() |
+----------------+----------------+---------------------+
| 2025-09-23     | 09:39:14       | 2025-09-23 09:39:14 |
+----------------+----------------+---------------------+

例2.创建 year_test 表,并插入四位、二位年份(MySQL 8.x以下)查看其差异。

CREATE TABLE year_test(
  col1 YEAR(4),
  col2 YEAR   -- MySQL 8.0 没有 YEAR(2) 默认同上
);
INSERTINTO year_test VALUE (2020,20),(1979,79),(2099,99),(0,0),('0','0'); 
mysql> SELECT * FROM year_test;
+------+------+
| col1 | col2 |
+------+------+
| 2020 | 2020 |
| 1979 | 1979 |
| 2099 | 1999 |  -- 区别点
| 0000 | 0000 |
| 2000 | 2000 |  -- 区别点
+------+------+

例3.创建一个 date_test 表,并插入几条数据验证日期值。

CREATE TABLE date_test(
  col1 DATE
);
-- 注意日期需以引号包含,插入的几种格式都是表示一个时间。
INSERT INTO date_test VALUES ('2020-2-2'),('20-02-02'),('20-2-2'); 
mysql> SELECT * FROM date_test;
+------------+
| col1       |
+------------+
| 2020-02-02 |
| 2020-02-02 |
| 2020-02-02 |
+------------+

例4.创建一个 time_test 表,并插入几条数据验证时间值或持续时间。

CREATE TABLE time_test(
  col1 TIME
);

-- 注意: 单引号中包含空格的区别
INSERTINTO time_test VALUES ('10:59:59'),(1222),('1222'),('2 10:10'),('2 10'),('10'),(10); 
mysql> SELECT * FROM time_test;
+----------+
| col1     |
+----------+
| 10:59:59 |
| 00:12:22 |
| 00:12:22 |
| 58:10:00 |  -- 表示:2 天10小时10分钟
| 58:00:00 |  -- 表示:2 天10小小时
| 00:00:10 |  -- 表示:10秒
| 00:00:10 |  -- 表示:10秒
+----------+

特别注意:若插入的值不能把转换为时间格式,则会报错,例如 INSERT INTO time_test VALUES (6565); 报错信息为:(1292): Incorrect time value: '6565' for column 'col1' at row 4 

例5.创建一个 timestamp_test 表,并插入几条数据验证时间戳值。

CREATE TABLEIFNOTEXISTS timestamp_test(
  col1 TIMESTAMP
);
INSERTINTO timestamp_test VALUES (NOW()); -- 插入当前系统时间戳值。
SELECT * FROM timestamp_test;
mysql> SELECT * FROM timestamp_test;
+---------------------+
| col1                |
+---------------------+
| 2025-09-24 00:27:36 |
+---------------------+

-- 设置时区为东八区:
SETtime_zone='+8:00'
-- 查看当前系统时区信息:
SHOWvariablesLIKE'time_zone';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| time_zone     | +08:00 |
+---------------+--------+
mysql> SELECT * FROM timestamp_test;
+---------------------+
| col1                |
+---------------------+
| 2025-09-24 08:27:36 |  -- 根据时区进行转换。
+---------------------+

特别注意:此类型会根据当前的系统时间自动转换为UTC格式的时间戳值。

例6.创建一个 datetime_test 表,并插入几条数据验证混合日期和时间值。

CREATE TABLE datetime_test(
  col1 DATETIME
);
-- 由于MySQL支持不严格的语句匹配的特性所以输入日期类型值是非常灵活的;
INSERTINTO datetime_test VALUES ('2020-02-2'),('20-2-2 10:00:59'),('10$10$10 23:59:59'); 
Query OK, 3 rows affected, 1 warning (0.13 sec)
Records: 3  Duplicates: 0  Warnings: 1

mysql> SELECT * FROM datetime_test;
+---------------------+
| col1                |
+---------------------+
| 2020-02-02 00:00:00 |
| 2020-02-02 10:00:59 |
| 2010-10-10 23:59:59 |
+---------------------+

温馨提示:在实践开发中,尽量避免使用不严格的语句匹配的特性,例如在插入数据时,尽量使用严格的日期格式。其次,空间占用上,DATETIME 类型比 DATE 和 TIME 类型组合多占2字节,所以开发中建议使用DATE+TIME或者TIMESTAMP代替它。

4.空间数据类型

描述:MySQL 支持空间数据类型,用于存储和操作地理空间数据,这些数据类型遵循 Open Geospatial Consortium (OGC) 标准,允许存储点、线、多边形等几何对象。

功能场景

基于其强大的地理空间数据处理能力,支持标准的几何对象类型,提供丰富的空间函数和操作,支持空间索引优化查询性能,遵循 OGC 标准,与其他 GIS 系统兼容,适用于位置服务、地理信息系统、物流规划等多种应用场景。

主要空间数据类型

类型名称 存储大小 格式 描述 示例
GEOMETRY 可变 二进制 所有空间类型的基类,可存储任何几何对象 通用几何容器
POINT 25字节 (x,y) 二维空间中的单个点 例如 地理位置坐标 POINT(116.3974 39.9093)
LINESTRING 可变 (x1 y1, x2 y2, ...) 由点序列连接而成的线 例如 道路、河流 LINESTRING(0 0, 10 10, 20 25)
POLYGON 可变 ((外环坐标), (内环1), ...) 封闭的多边形区域,可包含孔洞 例如 行政区域、湖泊 POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))
MULTIPOINT 可变 (point1, point2, ...) 多个点的集合 MULTIPOINT((0 0), (10 10), (20 20))
MULTILINESTRING 可变 (linestring1, linestring2, ...) 多条线的集合 MULTILINESTRING((0 0,10 10), (20 20,30 30))
MULTIPOLYGON 可变 (polygon1, polygon2, ...) 多个多边形的集合 MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)), ((20 20,30 20,30 30,20 30,20 20)))
GEOMETRYCOLLECTION 可变 (geometry1, geometry2, ...) 各种几何对象的混合集合 GEOMETRYCOLLECTION(POINT(1 1), LINESTRING(0 0, 2 2))

温馨提示:上表中的存储大小说明,固定大小类型 POINT 固定为 25 字节,可变大小类型是根据坐标点数量和复杂度而变化,其计算公式为:4 + (8 × 坐标点数) + 其他开销字节

知识扩展:SRID (空间参考系统标识符),用于指定坐标系,例如 SRID 0:未知坐标系(默认)SRID 4326:WGS84 经纬度坐标系(常用,前者为纬度,后则为经度)SRID 3857:Web Mercator 投影坐标系

知识扩展:在插入空间类型数据时,需要确保开启了空间支持。例如:SET have_geometry='YES'。 若未开启,则在创建包含空间类型字段的表时会报错。

-- # 查看是否支持空间数据
mysql> SHOW VARIABLES LIKE 'have_geometry';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| have_geometry | YES   |
+---------------+-------+

-- # 若未启用,则需启用空间数据支持
SET have_geometry='YES'

示例演示

例1.创建一个包含空间数据类型的表,并插入几条数据。

-- 创建包含空间数据的表
CREATETABLE spatial_examples (
idINT AUTO_INCREMENT PRIMARY KEY, -- 主键自增ID
nameVARCHAR(100),
  location POINT, 
  area POLYGON,   
  route LINESTRING,
  created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP
);

-- 1.插入点数据
-- 使用 WKT (Well-Known Text) 格式
INSERTINTO spatial_examples (name, location) 
VALUES ('公司总部', ST_GeomFromText('POINT(89.3974 39.9093)', 4326));
-- 纬度 Latitude [-90.000000, 90.000000]
-- 经度 Longitude (-180.000000, 180.000000]

-- 使用 WKB (Well-Known Binary) 格式
-- 使用 ST_GeomFromWKB 函数
INSERTINTO spatial_examples (name, location) 
VALUES ('公司总部', ST_GeomFromWKB(X'0101000000000000000000F03F000000000000F03F', 4326));

-- 2.插入多边形数据
INSERTINTO spatial_examples (name, area) 
VALUES ('北京区域', ST_GeomFromText('POLYGON((116.0 39.5, 117.0 39.5, 117.0 40.5, 116.0 40.5, 116.0 39.5))'));
-- 使用 ST_PolygonFromText 函数
INSERTINTO spatial_examples (name, area) 
VALUES ('测试区域', ST_PolygonFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'));

-- 3.插入线数据
INSERTINTO spatial_examples (name, route) 
VALUES ('通勤路线', ST_GeomFromText('LINESTRING(116.3 39.9, 116.4 39.8, 116.5 39.7)'));

查询插入的空间数据:

-- 查询所有空间数据
mysql> SELECTid, name, ST_AsText(location) as location_wkt FROM spatial_examples;
+----+--------------+-------------------------+
| id | name         | location_wkt            |
+----+--------------+-------------------------+
|  1 | 公司总部     | POINT(89.3974 39.9093)  |
|  2 | 公司总部     | POINT(89.3974 119.9093) |
|  3 | 公司总部     | POINT(1 1)              |
|  4 | 北京区域     | NULL                    |
|  5 | 测试区域     | NULL                    |
|  6 | 通勤路线     | NULL                    |
+----+--------------+-------------------------+

-- 查询特定类型的几何对象
mysql> SELECTname, ST_GeometryType(area) as geometry_type FROM spatial_examples WHERE area ISNOTNULL;
+--------------+---------------+
| name         | geometry_type |
+--------------+---------------+
| 北京区域     | POLYGON       |
| 测试区域     | POLYGON       |
+--------------+---------------+

-- 查找距离某点10公里范围内的地点
SELECTname, ST_AsText(location)
FROM spatial_examples
WHERE ST_Distance_Sphere(location, ST_GeomFromText('POINT(89.3974 39.9093)',4326)) <= 10000;
+--------------+------------------------+
| name         | ST_AsText(location)    |
+--------------+------------------------+
| 公司总部     | POINT(89.3974 39.9093) |
+--------------+------------------------+
1 row in set (0.00 sec)


-- 计算面积(适用于多边形)
mysql> SELECTname, ST_Area(area) as area_size
    -> FROM spatial_examples
    -> WHERE area ISNOTNULL;
+--------------+-----------+
| name         | area_size |
+--------------+-----------+
| 北京区域     |         1 |
| 测试区域     |       100 |
+--------------+-----------+
2 rows in set (0.00 sec)

-- 计算长度(适用于线)
mysql> SELECTname, ST_Length(route) as route_length FROM spatial_examples WHERE route ISNOTNULL;
+--------------+-------------------+
| name         | route_length      |
+--------------+-------------------+
| 通勤路线     | 0.282842712474618 |
+--------------+-------------------+

-- 计算两点间距离(计算精度纬度比较实用)
SELECT
  ST_Distance(
      Point(116.3974, 39.9093),
      Point(121.4737, 31.2304)
  ) as distanc
+--------------------+
| distanc            |
+--------------------+
| 10.054458060979712 |
+--------------------+

-- 获取几何对象的边界
mysql> SELECTname, ST_AsText(ST_Envelope(area)) as bounding_box FROM spatial_examples WHERE area ISNOTNULL;
+--------------+---------------------------------------------------------+
| name         | bounding_box                                            |
+--------------+---------------------------------------------------------+
| 北京区域     | POLYGON((116 39.5,117 39.5,117 40.5,116 40.5,116 39.5)) |
| 测试区域     | POLYGON((0 0,10 0,10 10,0 10,0 0))                      |
+--------------+---------------------------------------------------------+

-- 获取几何对象的中心点
mysql> SELECTname, ST_AsText(ST_Centroid(area)) as centroid FROM spatial_examples WHERE area ISNOTNULL;
+--------------+-----------------+
| name         | centroid        |
+--------------+-----------------+
| 北京区域     | POINT(116.5 40) |
| 测试区域     | POINT(5 5)      |
+--------------+-----------------+

-- 缓冲区分析
SELECTname, ST_AsText(ST_Buffer(location, 0.1)) as buffer_zone
FROM spatial_examples
WHERE location ISNOTNULL;

例子2.商店位置管理与客户服务范围。

CREATE TABLE stores (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    location POINT SRID 0,
    service_area POLYGON SRID 0
);

-- 插入示例数据
INSERT INTO stores (name, location, service_area) VALUES
('王府井店', Point(116.4170, 39.9090), 
 ST_GeomFromText('POLYGON((116.4 39.9, 116.45 39.9, 116.45 39.92, 116.4 39.92, 116.4 39.9))')),
('中关村店', Point(116.3160, 39.9830),
 ST_GeomFromText('POLYGON((116.3 39.98, 116.33 39.98, 116.33 39.99, 116.3 39.99, 116.3 39.98))'));

-- 查找距离用户最近的商店
SET @user_location = Point(116.4000, 39.9100);
SELECT name, 
       ST_Distance_Sphere(location, @user_location) as distance_meters
FROM stores
ORDER BY distance_meters ASC
LIMIT 3;
+--------------+--------------------+
| name         | distance_meters    |
+--------------+--------------------+
| 王府井店     | 1454.2359317927449 |
| 中关村店     | 10824.284659737623 |
+--------------+--------------------+

-- 查找服务范围包含用户位置的商店
SET @user_location = Point(116.3,39.99);
SELECT name
FROM stores
WHERE ST_Within(@user_location, service_area) = 1;

温馨提示: SQL 中 create_at 字段可在插入记录时自动记录时间戳,只需要在创建表时将其默认值设置为 CURRENT_TIMESTAMP 即可。

温馨提示:在使用空间数据类型时,为保证性能,有如下几个建议,一是为空间列创建空间索引,二是使用边界框预过滤减少计算量,三是避免在 WHERE 子句中对几何对象进行复杂计算。

-- 创建空间索引
CREATE SPATIAL INDEX idx_location ON spatial_examples(location);
CREATE SPATIAL INDEX idx_area ON spatial_examples(area);
CREATE SPATIAL INDEX idx_route ON spatial_examples(route);

-- 查看索引信息
SHOWINDEXFROM spatial_examples;

-- 空间查询使用索引(自动优化)
EXPLAINSELECTnameFROM spatial_examples 
WHERE ST_Within(location, @beijing_polygon) = 1;

5.JSON 数据类型

描述:MySQL 从 5.7.8 版本开始支持 RFC 8259 定义的原生 JSON 数据类型,可以直接存储和查询 JSON 格式数据,其与字符串列中存储 JSON 格式的字符串相比,其优势如下。

    原生存储:JSON 数据以优化的二进制格式存储自动验证:插入时会自动验证 JSON 格式的正确性高效访问:支持快速读取和修改 JSON 文档中的特定部分索引支持:可以在 JSON 列上创建函数索引

示例演示

-- 创建包含 JSON 列的表,例如 爱好
CREATETABLE json_test (
    idINT PRIMARY KEY AUTO_INCREMENT,
    nameVARCHAR(50),
    profile JSON,
    created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP
);

-- 插入 JSON 对象
INSERTINTO json_test (name, profile) VALUES
('admin', '{"age": 25, "city": "北京", "hobbies": ["读书", "游泳"], "contact": {"email": "admin@weiyigeek.top", "phone": "13800138000"}}'),
('master', '{"age": 30, "city": "上海", "hobbies": ["音乐", "旅行"], "contact": {"email": "master@weiyigeek.top", "phone": "13900139000"}}');

-- 使用 JSON_OBJECT 函数
INSERTINTO json_test (name, profile) VALUES
('唯一极客', JSON_OBJECT(
'age', 28,
'city', '重庆',
'hobbies', JSON_ARRAY('技术', '美食'),
'contact', JSON_OBJECT('email', 'wygk@weiyigeek.top', 'phone', '13600136000'),
'blog', 'https://blog.weiyigeek.top',
'wechat', '全栈工程师修炼指南'
));

查询插入的 JSON 数据

-- 提取 JSON 字段值
mysql> SELECTname, profile->'$.city'as city FROM json_test;
| name         | city     |
+--------------+----------+
| admin        | "北京"   |
| master       | "上海"   |
| 唯一极客     | "重庆"   |

-- 使用 JSON_EXTRACT 函数(等价于 -> 操作符)
SELECTname, JSON_EXTRACT(profile, '$.age') as age FROM json_test;
| name         | age  |
+--------------+------+
| admin        | 25   |
| master       | 30   |
| 唯一极客     | 28   |

-- 提取嵌套对象
mysql> SELECTname, profile->'$.contact.phone'as mobile_phone,profile->'$.contact.email'as email FROM json_test;
| name         | mobile_phone  | email                  |
+--------------+---------------+------------------------+
| admin        | "13800138000" | "admin@weiyigeek.top"  |
| master       | "13900139000" | "master@weiyigeek.top" |
| 唯一极客     | "13600136000" | "wygk@weiyigeek.top"     |

-- 提取数组元素
mysql> SELECTname, profile->'$.hobbies[0]'as first_hobby FROM json_test;
| name         | first_hobby |
+--------------+-------------+
| admin        | "读书"      |
| master       | "音乐"      |
| 唯一极客     | "技术"      |

-- 查询年龄大于 25 的用户
SELECTname, profile->'$.age'as age 
FROM json_test 
WHERE JSON_EXTRACT(profile, '$.age') > 25;

-- 查询在北京的用户
SELECTname, profile->'$.city'as city 
FROM json_test 
WHERE profile->'$.city' = '"北京"';

更新 JSON 数据:

-- 更新整个 JSON 文档
UPDATE json_test SET profile = '{"age": 26, "city": "深圳"}'WHEREid = 1;

-- 使用 JSON_SET 更新特定字段
UPDATE json_test SET profile = JSON_SET(profile, '$.age', 31) WHEREname = 'master';

-- 使用 JSON_REPLACE 替换字段值
UPDATE json_test SET profile = JSON_REPLACE(profile, '$.city', '杭州') WHEREname = 'master';

-- 使用 JSON_INSERT 插入新字段
UPDATE json_test SET profile = JSON_INSERT(profile, '$.company', '重庆极客科技有限责任公司') WHEREid = 1;

-- 使用 JSON_REMOVE 删除字段
UPDATE json_test SET profile = JSON_REMOVE(profile, '$.company') WHEREid = 1;

JSON 函数示例:

-- JSON_LENGTH 获取 JSON 文档长度
mysql> SELECTname, JSON_LENGTH(profile->'$.hobbies') as hobby_count FROM json_test;
| name         | hobby_count |
+--------------+-------------+
| admin        |           2 |
| master       |           2 |
| 唯一极客     |           2 |

-- JSON_KEYS 获取所有键
mysql> SELECTname, JSON_KEYS(profile) as profile_keys FROM json_test;
| name         | profile_keys                                            |
+--------------+---------------------------------------------------------+
| admin        | ["age", "city", "contact", "hobbies"]                   |
| master       | ["age", "city", "contact", "hobbies"]                   |
| 唯一极客     | ["age", "blog", "city", "wechat", "contact", "hobbies"] |


-- 使用 JSON_CONTAINS 查询包含特定爱好的用户
SELECTname, profile->'$.hobbies'as hobbies
FROM json_test 
WHERE JSON_CONTAINS(profile->'$.hobbies', '"游泳"');
| name  | hobbies              |
+-------+----------------------+
| admin | ["读书", "游泳"]     |

-- JSON_OBJECT 创建 JSON 对象
SELECT JSON_OBJECT('name', '测试', 'value', 100);

-- JSON_ARRAY 创建 JSON 数组
SELECT JSON_ARRAY('苹果', '香蕉', '橙子');

-- JSON_MERGE_PATCH 合并 JSON 文档(覆盖式)
SELECT JSON_MERGE_PATCH('{"a":1, "b":2}', '{"b":3, "c":4}');
-- 结果: {"a": 1, "b": 3, "c": 4}

总结:类型选择与使用

根据不同的使用场景(需求),定义列的数据类型,数据类型决定数据的特性(精度),存储空间与查询效率等多个方面来考虑,另外数据类型在不同的存储引擎(InnoDB、MyISAM)表现与处理方式是不同的。

1.字符串类型的选择

根据不同的场景选择不同的字符串类型,例如:CHAR 类型存储经常变更的短字符串效率高,而 VARCHAR 类型存储不定长的长字符串,虽然根据字符串长度消耗不定的存储空间,但导致磁盘碎片增多磁盘IO效率变低,可使用 optimize table 命令定期在数据库层面的碎片整理;

根据不同的数据引擎选择不同的字符串类型,例如:MYISAM 引擎下读多写少的场景下使用 CHAR 类型比VARCHAR 类型性能更好,而在 InnoDB 中两种性能相差不大但是 VARCHAR 类型更节省空间,在 MEMORY 引擎下由于其存放在内存中查询效率高,为节省空间往往也会选择 VARCHAR 类型 。

根据是否需要存储二进制数据选择不同的字符串类型,例如:BINARY 类型用于存储二进制数据,VARBINARY 类型用于存储不定长的二进制数据,针对与大文本数据选择 TEXT 类型,针对与二进制大对象选择 BLOB 类型(不过最好的方式还是采用静态服务器进行存放二进制文件效率更高)。

2.浮点数类型的选择

根据不同的场景选择不同的浮点数类型,数量少的整数选择 TINYINT、SMALLINT、MEDIUMINT 类型,数量多的选择 INT、BIGINT 类型,并且无符号时选择 UNSIGNED,有精度要求选择 DECIMAL 类型,无精度要求的浮点数选择 FLOAT 或 DOUBLE 类型。 例如:DECIMAL 类型适合存储精确的小数(如金钱、财务数据),FLOAT 和 DOUBLE 类型适合存储非精确的大范围小数(地理位置信息)。

3.日期类型的选择

建议使用 DATA 和 TIME 类型,而不是 DATETIME 类型,因为前两者占用空间更小,并且与时区无关。 建议使用 TIMESTAMP 类型,而不是 DATETIME 类型,因为前者占用空间更小,并且可根据时区进行调整,通常在跨国业务场景中使用。

实践案例

请根据需求创建合适的表结构,例如:设置一张员工信息表:

-- 字段定义:
编号:INT 类型,主键
工号: varchar(10) 类型
姓名:varchar(10) 类型
性别: ENUM('男', '女') 类型
年龄:TINYINT UNSIGNED 类型
手机号:CHAR(11) 类型
技能: JSON 类型
身份证号: CHAR(18) 类型 ,唯一键,索引
入职时间:DATE 类

-- 建表语句示例:
CREATETABLEIFNOTEXISTS employee (
idINT(10) UNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'编号',
  uid VARCHAR(10) COMMENT'工号',
nameVARCHAR(10) NOTNULLCOMMENT'姓名',
  gender ENUM('男', '女', '未知') NOTNULLCOMMENT'性别'DEFAULT'未知',
  age TINYINTUNSIGNEDNOTNULLDEFAULT0COMMENT'年龄'CHECK (age > 0AND age < 120),
  phone_number CHAR(11) NOTNULLCOMMENT'手机号'CHECK (phone_number REGEXP '^1[3-9]d{9}$'),
  skills JSONCOMMENT'技能',
  id_card CHAR(18) NOTNULLUNIQUEKEYCOMMENT'身份证号'CHECK (id_card REGEXP '^d{17}[0-9Xx]$'),
  entry_date DATECOMMENT'入职时间',
  PRIMARY KEY (id),
INDEX  idx_id_card (id_card)
) ENGINE=InnoDB AUTO_INCREMENT=1COMMENT='员工信息表';

-- 插入数据示例:
INSERTINTO employee (uid, name, gender, age, phone_number, skills, id_card, entry_date) VALUES ('001', '张三', '男', 30, '13800138000', '{"编程": "高级", "设计": "中级"}', '500102199001010001', '2020-01-01');

-- 查询数据示例:
mysql> SELECT * FROM employee;
+----+------+--------+--------+-----+--------------+------------------------------------------+--------------------+------------+
| id | uid  | name   | gender | age | phone_number | skills                                   | id_card            | entry_date |
+----+------+--------+--------+-----+--------------+------------------------------------------+--------------------+------------+
|  1 | 001  | 张三   | 男     |  30 | 13800138000  | {"编程": "高级", "设计": "中级"}         | 500102199001010001 | 2020-01-01 |
+----+------+--------+--------+-----+--------------+------------------------------------------+--------------------+------------+

至此,我们已经了解了 MySQL 五种数据类型,以及如何在 MySQL 中选择合适的数据类型,希望这些信息对你有所帮助!

加入:作者【全栈工程师修炼指南】知识星球

『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。

相关推荐