目录
通过前几篇文章,我们已经完整学习了 MySQL 数据库、数据表的 DDL 操作:从 create database 建库、备份恢复库,到 create table 建表、alter 修改表结构、drop 删表,我们清楚了数据库是存储容器、数据表是结构化文件载体。但一张数据表由若干字段组成,每个字段要存什么格式的数据、占用多大存储空间、取值范围如何约束,就由数据类型来定义,下面我们就来学习 MySQL 的数据类型。
一、数据类型
数据类型分类

在 MySQL 中,数据类型整体划分为四大类目:
- 数值类型:用来存放数字数据,细分整数、浮点小数、定点数,包含 BIT、TINYINT、INT、BIGINT、FLOAT、DOUBLE、DECIMAL 等,适配年龄、金额、编号这类数字业务;
- 文本 & 二进制类型:负责存储字符、文件二进制数据,CHAR 固定字符串、VARCHAR 可变字符串是日常最常用,TEXT 存超大文本、BLOB 用来存放图片 / 文件二进制流;
- 时间日期类型:专门管理时间格式,DATE、DATETIME、TIMESTAMP 是主流,分别适配纯日期、完整时分秒时间、时间戳场景;
- 枚举字符串类型:ENUM 单选枚举、SET 多选集合,限定字段只能从预设值里录入数据,适合性别、爱好这类固定选项业务。
简单总结:建表时给字段指定数据类型,本质就是提前约束字段的数据格式,既能节省磁盘存储空间,又能从底层规避非法数据入库,是数据表设计的基础规范。
整数类型
这几个类型都属于整数类型,但是它们所占的字节数和表示的范围不同
tinyint类型
在 MySQL 整数体系里,TINYINT 是占用存储空间最小的整型,仅 1 字节,分为带符号、无符号两种模式,默认创建字段为带符号类型。带符号 TINYINT 取值区间是 -128 ~ 127,无符号加上 unsigned 修饰后范围变更为 0 ~ 255,这个取值边界是后续插入数据校验的核心规则。
举例:
我们先建一个库,执行 create database test_db; 创建 test_db 测试库,语句执行成功后,使用 use test_db; 切换到该库,后续所有数据表都在这个库内创建。
下面我们就先建表 t1,表内只定义一个 num 字段,字段数据类型指定为 tinyint,建表语句:create table if not exists t1(num tinyint); 。因为当前表默认使用 InnoDB 存储引擎,在 Linux 系统对应的数据库目录中,会生成 .frm 表结构文件与 .ibd 数据索引文件两个磁盘文件。
建表完成后,通过三条常用语句核对表结构信息:desc t1; 可以直观查看 num 字段为 tinyint 类型;show tables; 用来确认当前库内存在 t1 数据表;show create table t1\G 能够查看完整建表语句、字符集、存储引擎等全量配置。
下面我们看一下 tinyint:
接下来进行合法数据插入验证,在带符号默认规则下,依次向 t1 插入区间 -128~127 以内的数据:insert into t1 values(-128);、insert into t1 values(127);、insert into t1 values(0);、insert into t1 values(-1);、insert into t1 values(1); 五条 SQL 全部正常执行无报错。执行 select * from t1; 查询全表数据,能够看到五条数据完整存入数据表,印证在取值范围内的数据可以正常入库。
最后做越界数据测试,尝试插入超出 -128~127 边界的数值,分别执行 insert into t1 values(-129);、insert into t1 values(128);、insert into t1 values(130); 三条语句全部抛出 Out of range value 字段越界报错。这就体现出数据类型的约束作用,TINYINT 从底层拦截非法超范围数据。
在了解了默认带符号的 TINYINT 之后,我们继续学习添加 unsigned 无符号的 tinyint :
添加 unsighed 关键字后,字段不再支持负数存储,原本存放负数的存储空间全部用来扩充正数范围,取值区间变为 0~255。
我们首先创建测试数据表 t2,在建表语句中将字段定义为 num tinyint unsigned,建表完成后使用 desc t2 查看表结构,能够在 Type 字段上清晰看到 tinyint (3) unsigned 标识,确认无符号属性生效。随后我们在合法区间 0 到 255 中选取 0、255、100 三组数值依次插入数据表,三条插入语句全部正常执行没有报错,有效数据可以顺利存入表中。之后我们尝试插入边界之外的数据,分别填入负数 - 1 和超出上限的 256,两条 SQL 都触发 Out of range value 越界错误,MySQL 凭借数据类型的约束直接拦截非法数据写入。
通过两组对照实验我们可以总结,带符号 TINYINT 适用存在正负值的业务场景,无符号 TINYINT 只保存非负数字,像年龄、状态标识这类永远不会为负的字段,优先选用无符号类型,既节约存储空间,也能在底层限制错误数据。

在完整吃透 TINYINT 的使用规则之后,我们顺势梳理 MySQL 里剩余四类常用整型:SMALLINT、MEDIUMINT、INT、BIGINT,这四类整型和 TINYINT 的设计逻辑完全一致,全部区分带符号与无符号两种存储模式,唯一区别在于占用存储空间、数值取值范围各不相同。其中 SMALLINT 占用 2 字节存储空间,带符号取值区间为 - 32768~32767,无符号则从 0 延伸至 65535;MEDIUMINT 占用 3 字节,带符号范围 - 8388608~8388607,无符号上限为 16777215;INT 是日常开发最常用的整型,占用 4 字节,带符号区间 - 2147483648~2147483647,无符号最大值可达 4294967295;BIGINT 占用 8 字节,是整型里存储空间最大的类型,适配超大数字存储场景,带符号和无符号的取值上下限也是所有整型里跨度最大的。这四类整型无需逐个落地测试,只要掌握 TINYINT 正负号切换、越界报错的规律,就可以同理推导剩余整型的使用逻辑,添加 unsigned 关键字就会剔除负数存储范围、扩充正数上限,超出取值边界的数据无法正常写入。

基于前面所有整型的实操结果,我们可以提炼出 MySQL 数据类型的核心本质:数据类型本身是数据库自带的原生约束。当我们向字段内写入超出数据类型取值范围的非法数据时,MySQL 会直接抛出报错并拦截本次插入操作,从底层杜绝脏数据入库;反过来讲,只要一条数据成功存入数据表,就代表这条数据在插入时完全符合对应字段的数据类型规范。这套约束机制会倒逼开发人员在编写 SQL、对接业务代码时规范数据录入,从数据库层面兜底,保障库中存储的数据具备可预期性与完整性。
这里也可以和 C、C++ 这类编译型编程语言做横向对比,二者在数值溢出的处理逻辑上有着明显区别,C 语言中数值超出变量存储范围时,大多会发生数据截断、隐式类型转换,程序不会直接终止报错,很容易在不知情的情况下存入错误数值;但 MySQL 依靠数据类型约束,直接拦截越界数据,从存储源头规避非法数值,进一步体现出数据库类型约束的实用价值。
最后补充一个建表书写细节,也是 MySQL 和主流编程语言语法的一处关键区别:在 MySQL 建表定义字段时,书写顺序是字段名称在前,数据类型在后,就像我们之前写的 num tinyint;而 C、C++、Java 等编程语言在定义变量时,都是先声明数据类型,再填写变量名称,例如 int num,这个书写顺序的差异在建表编写字段时需要多加留意,避免书写语法错误。
bit类型
在介绍完各类整型之后,我们继续学习 MySQL 里的 BIT 位类型:
BIT (M) 是专门用来存储二进制位数据的类型,括号内的 M 代表自定义存储位数,取值限定在 1~64 之间,不手动指定 M 时默认位数为 1,超出 64 位在建表阶段就会直接报错。
我们接着创建表 t3,用来实操验证 bit 类型:
建表语句里设置 id 字段为 int 类型,online 字段定义为 bit (1),用这个字段标记用户在线状态,1 代表在线、0 代表离线。建表完成后通过 desc t3 查看表结构,可以清晰看到 online 字段的数据类型标注为 bit (1),字段配置生效。
接下来我们进行合法数据的插入操作,先后向表中插入两条合规数据,一条是 (123,0) 代表 id123 的用户离线,另一条是 (124,1) 代表 id124 的用户在线,两条插入语句都正常执行没有报错。随后我们尝试向 bit (1) 字段写入 2、3、5 这类超出 1 位二进制能表达范围的数字,全部触发数据超长报错,这是因为 1 位 bit 仅能存放 0 和 1 两个数值,超出范围的数据会被数据库拦截。
直接执行 select * from t3 查询全表时,bit 类型的数据不会直观展示数字,会以 ASCII 不可见字符形式呈现,表格里对应位置显示空白。想要查看真实存储的数值,需要借助 hex() 函数做进制转换,执行 select id,hex(online) from t3; 之后,原本的 0 和 1 就会以十六进制数字正常展示出来。
我们继续补充测试,向 online 字段插入字符 'a' 以及数字 97,两条语句都可以正常入库,本质是数据库会自动读取对应字符的 ASCII 码值,再转为二进制存入 bit 字段,查询时依旧以对应 ASCII 字符的形式展示,这也是 bit 字段特殊的存储规则。
最后我们验证 BIT 类型的位数上限规则,尝试新建数据表 t4,把 online 字段定义为 bit (65),因为 M 的合法最大值是 64,这条建表语句直接抛出参数越界错误,由此确认 BIT 的位数参数必须控制在 1~64 这个区间内。
综合整套实操可以总结,BIT 适合存储开关类、状态类的二进制标识数据,字段受位数约束严格限制存入数值大小,原生查询展示不直观,日常查看数值需要配合 hex 等转换函数处理。
小数浮点类型
在整型与 BIT 位类型学习完毕后,我们开始学习 MySQL 里存储小数的三类数值类型,分别是FLOAT 单精度浮点、DOUBLE 双精度浮点、DECIMAL 定点数,三者都可以通过 (M,D) 格式限定数字总长度与小数位数,也支持添加 unsigned 设置无符号,其中 FLOAT 占用 4 字节存储空间,DOUBLE 占用 8 字节,DECIMAL 是定点存储,不会出现浮点精度丢失,三者适用场景各有区分。
float
FLOAT 的定义格式为 FLOAT(M,D) UNSIGNED,M 代表整体有效数字总长度,D 代表小数点后的小数位数,以示例里的 float(4,2) 为例,数字一共占用 4 位,其中小数固定占 2 位,整数部分只剩 2 位,带符号取值范围默认是 -99.99 ~ 99.99,MySQL 在存入数据时会自动对超出小数位数的数值做四舍五入处理。
继续举例 :
接下来我们创建表 t5 来,建表语句定义 id 为 int 字段,薪资字段 salary 设置为float(4,2),建表完成后执行desc t5;查看表结构,可以在 Type 列清晰看到float(4,2)的字段属性,确认长度与小数精度配置生效。
随后我们插入区间内的合法数据,依次插入 99.99、-99.99、-12.34 三个数值,三条插入语句全部正常执行,查询全表数据后,三条薪资数据完整无误存入表中,符合 float(4,2) 的取值规范。
之后我们做越界数据测试,先后尝试插入 -99.999、-999.99、-100.00 这类超出 -99.99~99.99 区间的数值,全部触发数值越界报错被数据库拦截;而插入 -10.0 时,数值落在合法区间内,语句正常执行入库,数据库自动补全小数位变为 -10.00。这也再次印证了数据类型自带约束能力,超出预设范围的小数无法写入数据表。
我们继续验证 FLOAT 自带的四舍五入存储规则:
先插入 23.45,数据刚好契合两位小数格式,可以直接正常入库;接着分别插入 23.456 与 23.454 ,MySQL 会自动对第三位小数做四舍五入处理,两个数值最终都保存为 23.46 和 23.45,由此能看出在整体位数不超限的前提下,超出设定小数位的部分会自动取舍。
在四舍五入的逻辑之外还存在边界例外情况,我们尝试向 salary 字段插入 99.995、99.997 这类数值,在四舍五入后数值会进位变成 100.00,整数位突破两位、整体数字长度超出 4 位的定义,因此这类数据全部触发越界报错无法插入,这也说明 FLOAT 的取值上限由 (M,D) 括号内的总位数与小数位严格限定,四舍五入后的结果同样不能突破数值范围。
完成带符号 FLOAT 的测试后,我们再创建 t6 表用来演示无符号 FLOAT,建表时将薪资字段定义为 float(4,2) unsigned,无符号修饰会直接剔除负数存储区间,字段合法范围变更为 0 ~ 99.99。
插入测试时,99.99 可以正常存入,而 99.996 进位后超限、所有负数例如 -99.99、-0.01 全部超出定义域,都会被 MySQL 拦截报错,和之前整型 unsigned 的范围裁剪逻辑保持一致。
最后我们补充 FLOAT 的原生缺陷,也就是浮点精度丢失问题。如果定义 FLOAT 时不填写 (M,D) 限定格式,使用默认原生 FLOAT,它仅能保证大约 7 位有效数字的精准度,我们向字段传入超长小数 32424234.2131238123 后,数据库存储的数值出现末尾数值失真、自动舍入,整数与小数部分都会产生精度损耗。这也正是金融金额场景不推荐使用 FLOAT、后续需要选用 DECIMAL 定点类型的关键原因。
double
在掌握了 FLOAT 单精度浮点类型之后,我们接着学习 DOUBLE 双精度浮点类型:

DOUBLE 的语法格式写作 DOUBLE(M,D) UNSIGNED ,整体的语法规范、取值约束逻辑和 FLOAT 一致,同样依靠 M 标识数字总有效位数、D 代表小数点后保留位数,添加 UNSIGNED 关键字就会剔除负数存储范围,只保存非负小数。
从存储与精度层面来看,DOUBLE 固定占用 8 字节存储空间,对比仅 4 字节的 FLOAT,它的有效精度范围更大,FLOAT 大约只能保证 7 位有效数字准确,而 DOUBLE 可以保障 15~16 位左右的有效数字精度,能存储数值范围更大、更长的小数数据,和 C、Java 等编程语言里的 double 底层设计思路相通。
在存储特性上,DOUBLE 依旧属于浮点存储格式,哪怕精度优于 FLOAT,本质还是二进制浮点保存数据,因此同样无法彻底规避浮点精度丢失问题,只是出现误差的概率和误差幅度远小于 FLOAT。日常开发里,普通大批量小数统计可以选用 DOUBLE,但是涉及金额、账务这类要求绝对精准的场景,依然不能使用 DOUBLE 和 FLOAT,后续我们就要学习定点类型 DECIMAL 来解决精准存储的需求。
decimal
我们开始学习定点数 DECIMAL :
decimal 的语法格式 decimal(m,d) unsigned 和浮点用法一致,m 代表总长度、d 代表小数位数,最大的优势是不会出现浮点精度丢失,是金额等精准数值场景的首选类型。
我们继续创建表 t7,同时定义 f1 float(10,8)、f2 decimal(4,2) 两个字段,方便对照浮点与定点的区别,通过 desc 可以确认两个字段的数据类型配置生效。我们依次插入测试数据,99.99、-99.99 在 decimal(4,2) 范围 -99.99~99.99 内可正常写入;-99.999 这类四舍五入后超限的数据直接报错拦截;而 99.994、23.935 会自动四舍五入存储,和 float 的舍入规则保持一致。
随后通过 alter table modify 把 f2 修改为 decimal(10,8),扩充字段存储范围,之后同时给 f1、f2 填入相同数值 23.12345612。查询结果能够直观看出,float 字段存储的数据末尾出现数值偏差、产生精度失真,而 decimal 完整保存原始小数,精准没有误差。
最后小结,float、double 是二进制浮点存储,天然存在精度缺陷,decimal 以十进制定点存储保证数值准确,开发中涉及价钱、账务计算时统一选用 decimal。
字符串类型
char
在数值类型学习完毕后,我们开始进入 MySQL 字符串类型的学习,第一个要讲解的就是固定长度字符串 CHAR(L) :
CHAR 的语法里的 L 代表字符个数,不是存储空间字节数,该类型的最大可设置字符数为 255,属于定长存储,无论实际存入几个字符,都会占用 L 个字符的固定存储空间。
举例 : 我们继续创建表 t8 :
执行建表语句 create table if not exists t8(id int,name char(2));,将 name 字段设定为 char (2),规则约束此字段最多只能存放 2 个字符。建表完成后通过 desc t8; 查看表结构,Type 列清晰显示char(2),字段配置正式生效。
随后我们分英文、中文两组插入数据验证约束规则,英文场景中,依次插入单字符 'a'、'b',双字符 'ab',三条数据字符数量都在 0~2 的合法区间,全部正常插入;当尝试插入三个英文字母 'abc' 时,超出 2 字符上限,MySQL 抛出 Data too long 字段超长报错,直接拦截非法数据。
切换中文数据测试,MySQL 里单个汉字同样算作一个字符,因此单字 '中'、双字 '中国' 满足 char (2) 的长度要求,可以顺利入库,而三个字 '中国人' 超出字符限制,同样触发超长报错。这里需要重点区分容易混淆的知识点,utf8 编码下一个汉字占用 3 字节、gbk 下占用 2 字节,但 char 的 L 统计标准是字符符号,字母、汉字都计为 1 个字符,字段长度限制只看字符数量,和编码占用的存储空间无关,这也是 "中国" 占用 6 字节却能存入 char (2) 的核心原因。
最后我们验证 CHAR 的全局长度上限,尝试在建表时定义 address char(256),语句直接报错提示最大值为 255,由此确定 CHAR 类型的字符长度不能超过 255,想要存储超过 255 个字符的文本就需要更换 VARCHAR 或 TEXT 类型。
varchar
下面我们继续学习可变长字符串 VARCHAR :
VARCHAR(L) 是可变存储的字符串类型,在 MySQL 的版本迭代中,varchar 字段括号内的数值定义规则发生过变更,MySQL4.0 以及更早的旧版本里,varchar(L) 括号中的 L 代表字段能够存储的最大字节数量,字段长度受存储空间字节约束;从 MySQL5.0 及之后的5.5、5.7、8.0 等主流稳定版本开始,varchar(L) 括号内的数值修改为限制字符个数,不再直接约束存储字节,单个字符占用的字节大小由表所选用的字符集决定。
举例 : 我们继续创建表 t9 :
我们上图数据库环境属于 5.0 之后的新版MySQL,建表语句 varchar(6) 代表 name 字段最多存入 6 个字符,当前表使用 utf8 字符集,单个汉字、标点符号都算作一个字符,单个汉字在编码中占用 3 字节。在建表完成后依次插入测试数据,单个汉字 "中"、两个汉字 "中国"、三个汉字 "中国人" 字符数均小于6,都能够顺利写入数据表;后续插入 "中国人," 是4个字符、"中国人,加" 是5个字符、"中国人,加油" 恰好凑齐6个字符,全部符合字符上限规则,正常插入;最后额外增加感叹号形成 "中国人,加油!",整体字符数量变为7个,超出 varchar(6) 规定的 6 字符上限,数据库抛出字段超长的报错。
我们再进行更改:
接着我们尝试用 alter 语句把 t9 的 name 字段改成 varchar(65536),语句直接抛出报错。因为我们当前使用的是 MySQL5.0 之后的版本,varchar 括号里填写的数字代表字符数量而非字节,数据库底层单行非大字段总字节上限固定 65535 字节,varchar 还要预留 1~3 字节存储长度标识,剩余可用数据字节为 65532。在 utf8 编码下单个字符占 3字节,65532÷3≈21844 个字符,65536 这个数值远超换算后的最大字符数 21844,因此修改语句执行失败,报错提示该字段最大仅能填写21845 以内的字符数值。
接着我们继续创建一个表 t10,第一次建表填写 varchar(21845),21845 个字符换算成字节已经超出 65532 可用字节,触发行尺寸超限的报错;把字段长度改成 varchar(21844) 后,字符换算后的总字节符合存储上限,建表顺利执行。
char 和 varchar 的区别,新版 MySQL 中两者括号都代表字符数,char 是定长存储,固定占用定义长度的空间,varchar 为变长存储,只占用实际数据加长度标识的空间,固定数据选 char,长短不定的数据选用varchar。
char和varchar的比较
在MySQL5.0 及以后的版本中,char 与 varchar 字段定义时括号内的参数都代表存储的最大字符数量,但二者在底层存储空间分配、数据填充规则、存取性能与业务选用场景上有着本质区别,char 属于定长字符类型,一旦定义 char(n),无论字段实际存入多少个字符,数据库都会直接分配对应 n 个字符的固定存储空间,若存入内容字符数不足定义长度,MySQL 会在字符串末尾自动补齐空格填满剩余空间,读取数据时再自动剔除末尾填充的空格,这种固定空间分配的特性让 char 存取数据时不需要额外计算数据实际长度,读写速度更快,不过在存储内容远小于定义长度时会造成磁盘空间浪费;而 varchar 是变长字符类型,存储空间由实际存储内容的字节大小外加 1~3 字节的长度标识字节共同组成,长度标识用来记录当前字符串真实占用长度,存入多少内容就占用多少有效数据空间,不会产生多余空格占位浪费存储空间,但正因需要额外记录长度信息、动态分配空间,varchar 在数据读取和写入时的性能略逊色于定长的 char,结合业务选型来看,手机号、身份证号、固定编码这类字符长度永远固定的数据适合使用 char 类型,依托它高效的读写特性提升查询效率,姓名、备注、地址这类内容长短参差不齐、长度浮动较大的数据优先选用 varchar 类型,依靠变长存储的优势节约磁盘存储资源。
日期和时间类型
在字符串类型讲解结束后,我们进入 MySQL 日期时间类型的学习 :
日常开发里最常用 date、datetime、timestamp 三类,三者存储格式、占用字节、取值范围和自动特性各不相同。date 只保存年月日,格式yyyy-mm-dd,固定占用 3 字节;datetime 完整保存年月日时分秒yyyy-mm-dd HH:ii:ss,取值区间 1000~9999 年,占用 8 字节;timestamp 时间戳展示格式和 datetime 一致,但底层从 1970 年起始,仅占用 4 字节,自带自动更新特性。
举例:
我们继续创建表 t11,分别定义 t1 date、t2 datetime、t3 timestamp 三个字段,执行 desc t11 查看表结构可以发现,timestamp 字段默认值为 CURRENT_TIMESTAMP,附带 on update CURRENT_TIMESTAMP 属性,这代表插入或更新本条数据时,该字段会自动填充、刷新为当前系统时间,而 date 和 datetime 没有自动赋值规则,必须手动写入数据。
随后执行插入语句,仅给 date、datetime 字段手动填写 '2000-10-01' 与 '1949-10-01 08:00:00',没有传入 t3 的值,查询结果能够看到 timestamp 自动填入了当前系统时间。
之后执行 update 修改 t1 的日期数据,再次查表,t3 字段同步刷新为最新的系统时间,完整印证 timestamp 自动更新的特点,date 和 datetime 的数据则维持原有数值不变。
时间戳有啥用 : 比如要在一个论坛发表东西,发表时应有发表的时间接着我们新建一个表 t12 模拟论坛发帖场景,表内 content text 存储帖子正文,time timestamp 记录发布时间。插入数据时只填写文章内容,不手动录入发布时间,查询数据表后 time 字段自动生成当前时间,这就是 timestamp 的典型使用场景,用来自动记录数据新增、最后修改时间,省去手动维护时间字段的开发成本。
最后梳理一下三者的使用场景:只需要记录年月日(生日、订单日期)选用 date;需要完整年月日时分秒、跨度远超 1970 年前后的时间(历史档案、长远台账)选用 datetime;需要自动记录创建 / 更新时间、存储节省空间的场景(日志、帖子更新时间)选用 timestamp。
枚举enum和集合set
下面我们接着学习 MySQL 里的枚举 ENUM 与集合 SET:
枚举enum:
ENUM 枚举类型是单选约束,语法格式为 enum('选项1','选项2','选项3',...),字段同一行数据只能从预设列表里选取一个值存入。ENUM 底层并不直接保存字符串文本,而是把各个选项依次映射为数字 1、2、3......,最多支持 65535 个备选项,我们插入数据时既可以填写定义好的字符串内容,也能直接写入对应序号数字,数据库会自动转换成对应的枚举值存储,依靠数字存储来提升查询和存储效率,像性别、用户状态这类只能二选一或单选的业务字段很适合选用 ENUM。
集合set:
SET 集合类型是多选约束,语法 set('选项1','选项2','选项3',...),单个单元格能够同时存放任意多个预设选项,多个内容用英文逗号隔开。SET 底层采用二进制位映射规则,各个选项依次对应 1、2、4、8、16 这类 2 的幂次数值,最多只能定义 64 个选项,通过数值的位运算来组合多选内容,因此我们除了填写字符串组合,也能通过累加对应数字批量写入多个选项,适合用户爱好、附加标签这类可以多选的业务场景。
举例:
我们继续创建一个表 votes,表中一共三个字段,username 使用 varchar (30) 存储用户名,gender 定义为 enum (' 男 ',' 女 ') 枚举单选类型,用来约束性别只能填入男或女,hobby 设置为 set (' 代码 ',' 羽毛球 ',' 乒乓球 ',' 足球 ',' 游泳 ') 集合多选类型,用来存储用户多项爱好。建表完成后通过 desc 查看表结构,可以直观看到两个特殊字段的枚举、集合配置全部生效。
下面我们插入数据:
我们先针对 enum 性别字段开展插入测试,首先正常插入 '张飞','男','代码'、'刘备','女','代码',两个性别值都在 enum 预设范围内,语句顺利执行入库。之后尝试给 gender 填入unknown、好、比特、a、b这类不在枚举列表里的内容,全部触发字段数据截断报错,由此验证 enum 的约束特性:字段只允许写入提前定义好的选项,自定义陌生内容会被数据库直接拦截。
紧接着我们改用数字 1、2 插入性别字段,insert into votes values ('孙权',1,'代码') 和 insert into votes values ('孙权',2,'代码') 都可以正常执行,原因是 enum 底层把第一个选项映射序号 1、第二个映射序号 2,下标从 1 开始,直接填对应序号数字等价于填写对应字符串。
上面我们插入的是枚举类型 下面我们来看集合类型set
完成 enum 验证后,我们切换到 set 爱好字段的测试,先插入单一项合法爱好 '羽毛球' 能够正常入库,而填入不在集合里的篮球、飞盘就会报错拦截,体现 set 同样只认可预先配置的选项。set 支持多选录入,多个合法爱好用英文逗号隔开即可,像 '乒乓球,足球,游泳' 这种组合格式可以顺利插入,查询数据表后也能完整查出拼接后的多个爱好内容。
最后我们总结 set 的三条使用规则:
- 第一,无法插入集合定义之外的陌生选项;
- 第二,支持单次只存入单个选项;
- 第三,支持多个合法选项以英文逗号分隔批量存入。
我们继续基于 votes 表的 hobby 字段(set('代码','羽毛球','乒乓球','足球','游泳')),讲解 SET 依靠位图数字存入数据的规则,SET 和 ENUM 的数字含义完全不同,SET 的数字不是下标,是二进制位图。
首先插入 insert into votes values ('刘表',1,0);,hobby 传入数值 0,二进制全为 0,代表不选中任何爱好,最终 hobby 字段存入 NULL。紧接着传入数字 1,二进制 00001,对应 SET 第一个选项「代码」,数据正常存入,查询后 hobby 的值就是代码。
接下来测试数字 7,7 换算二进制是 00111,低位连续三位为 1,分别对应第 1 项代码、第 2 项羽毛球、第 3 项乒乓球,因此插入 7 之后,数据库自动拼接三个「爱好代码,羽毛球,乒乓球」存入字段,直观体现位图组合多选的特性。
之后传入数值 31,二进制11111,五个二进制位全部置 1,刚好对应 set 里全部 5 个爱好选项,插入之后 hobby 完整保存「代码,羽毛球,乒乓球,足球,游泳」五个内容。这里就能明确 SET 底层逻辑:每一个选项固定占用一个二进制位,取值依次是(2^0=1、2^1=2、2^2=4、2^3=8、2^4=16),想要多选就把对应选项的数值相加,相加结果作为参数写入即可。
最后总结 SET 的两种写入方式,第一种是直接填写字符串,多个爱好用英文逗号分隔;第二种是填写位图数字,通过二进制位组合实现多选。反观 ENUM 的数字是选项下标、从 1 依次递增,SET 数字是二进制位图,这是二者使用数字插入时最核心的区别。
enum和set类型的查找
enum的筛选
我们先来学习 ENUM 枚举字段的查询筛选,以表中 gender enum('男','女') 字段为例,ENUM 既可以用字符串条件查询,也能依靠底层映射的数字做筛选。
第一种写法直接使用字面字符串 where gender='女',可以精准查询出性别为女的全部数据;
第二种利用 ENUM 下标规则,' 女 ' 是第二个选项对应数字 2,where gender=2 的查询结果和用 ' 女' 筛选完全一致,这是因为 ENUM 底层实际存储序号数字,MySQL 支持直接用对应数字匹配数据。
set的筛选
接下来进入 SET 集合字段的筛选,SET 同样支持字符串与位图数字两种查询方式,但数字是二进制位图,逻辑和 ENUM 下标完全不同。
先用字面值where hobby='羽毛球'做精准匹配,这条语句只会筛选出爱好仅有羽毛球的记录,只要字段同时包含其他爱好就无法被命中。
使用数字筛选时遵循二进制位图规则,hobby=1 换算二进制 00001,对应第一个选项「代码」,因此这条查询取出所有爱好恰好只有代码的数据;数值 3 二进制是00011,对应代码 + 羽毛球两个选项,只会匹配爱好刚好是这两项组合的记录;
数值 31 二进制11111,覆盖 SET 全部 5 个爱好,最终精准查出五项爱好全部选中的那条数据。
由此总结查询规律:ENUM 数字是选项序号,等值匹配对应单个枚举值;SET 数字是位图,等值匹配等于该数字对应的爱好组合,想要模糊查询包含某一项爱好不能直接用 =,需要借助位运算,而精确匹配完整组合就可以直接使用对应数字或全拼字符串。
find_in_set()
在掌握 SET 等值精准查询后,我们下面学习一个查找函数 find_in_set(),专门用来检索单个元素是否存在于逗号分隔的集合字符串中,也是筛选 SET 字段包含某一项的核心方法,函数格式为find_in_set(待查找元素,逗号拼接集合)。
我们先通过固定字符串 'a,b,c' 做基础测试,查找单个字符 'a',函数返回下标 1;查找不存在的 'd',返回 0 代表无匹配数据;如果传入 'a,b' 这类多值内容,函数直接返回 0,由此明确函数规则:只能检索单个独立项,不能一次性查找多个拼接内容。
继续查询 'c' 返回 3、'b' 返回 2,能看出返回值是元素在集合里从 1 开始的位置序号。
接着回到 votes 表的 hobby 字段做业务查询对比,where hobby='羽毛球' 是精准全匹配,只会查出爱好只有羽毛球的记录;而 where find_in_set('羽毛球',hobby) 是模糊包含匹配,只要 hobby 字段里含有羽毛球这一项,无论同时附带多少其他爱好都会被查询出来,因此结果数量远多于精准匹配,这也是日常筛选 "包含某标签" 的常用写法。
如果想要筛选同时包含羽毛球、代码两项爱好的数据,不能把两个值写在同一个 find_in_set 参数里,这种写法无法匹配结果,需要用 and 拼接两次函数判断,where find_in_set('羽毛球',hobby) and find_in_set('代码',hobby),最终筛选出同时携带两个爱好的全部数据。
总结
我们整体梳理了 MySQL 的全部数据类型,不管是数值、字符串、日期还是枚举集合,各类数据类型的核心作用统一都是数据库层面的数据约束。整数、浮点、定点类型会限定数据的取值范围与小数格式,超长或越界的数字无法存入;CHAR 与 VARCHAR 约束字符的存储长度,超出上限直接拦截;日期类型约束时间的合法格式,不符合年月日时分秒规范的数据不能入库;ENUM 单选、SET 多选提前固定可选内容,只能填入预设选项,陌生内容会被拒绝。所有类型在建表时就划定了数据的合法标准,插入数据一旦违背字段的类型规则,数据库就直接拦截报错,从根源避免无效脏数据存入表中,以此保证整张数据表内的数据规范、合规。
谢谢大家的观看!

在 MySQL 整数体系里,TINYINT 是占用存储空间最小的整型,仅 1 字节,分为带符号、无符号两种模式,默认创建字段为带符号类型。带符号 TINYINT 取值区间是 -128 ~ 127,无符号加上 unsigned 修饰后范围变更为 0 ~ 255,这个取值边界是后续插入数据校验的核心规则。





最后做越界数据测试,尝试插入超出 -128~127 边界的数值,分别执行 insert into t1 values(-129);、insert into t1 values(128);、insert into t1 values(130); 三条语句全部抛出 Out of range value 字段越界报错。这就体现出数据类型的约束作用,TINYINT 从底层拦截非法超范围数据。

BIT (M) 是专门用来存储二进制位数据的类型,括号内的 M 代表自定义存储位数,取值限定在 1~64 之间,不手动指定 M 时默认位数为 1,超出 64 位在建表阶段就会直接报错。

接下来我们进行合法数据的插入操作,先后向表中插入两条合规数据,一条是 (123,0) 代表 id123 的用户离线,另一条是 (124,1) 代表 id124 的用户在线,两条插入语句都正常执行没有报错。随后我们尝试向 bit (1) 字段写入 2、3、5 这类超出 1 位二进制能表达范围的数字,全部触发数据超长报错,这是因为 1 位 bit 仅能存放 0 和 1 两个数值,超出范围的数据会被数据库拦截。

最后我们验证 BIT 类型的位数上限规则,尝试新建数据表 t4,把 online 字段定义为 bit (65),因为 M 的合法最大值是 64,这条建表语句直接抛出参数越界错误,由此确认 BIT 的位数参数必须控制在 1~64 这个区间内。
















随后我们分英文、中文两组插入数据验证约束规则,英文场景中,依次插入单字符 'a'、'b',双字符 'ab',三条数据字符数量都在 0~2 的合法区间,全部正常插入;当尝试插入三个英文字母 'abc' 时,超出 2 字符上限,MySQL 抛出 Data too long 字段超长报错,直接拦截非法数据。












ENUM 枚举类型是单选约束,语法格式为 enum('选项1','选项2','选项3',...),字段同一行数据只能从预设列表里选取一个值存入。ENUM 底层并不直接保存字符串文本,而是把各个选项依次映射为数字 1、2、3......,最多支持 65535 个备选项,我们插入数据时既可以填写定义好的字符串内容,也能直接写入对应序号数字,数据库会自动转换成对应的枚举值存储,依靠数字存储来提升查询和存储效率,像性别、用户状态这类只能二选一或单选的业务字段很适合选用 ENUM。
SET 集合类型是多选约束,语法 set('选项1','选项2','选项3',...),单个单元格能够同时存放任意多个预设选项,多个内容用英文逗号隔开。SET 底层采用二进制位映射规则,各个选项依次对应 1、2、4、8、16 这类 2 的幂次数值,最多只能定义 64 个选项,通过数值的位运算来组合多选内容,因此我们除了填写字符串组合,也能通过累加对应数字批量写入多个选项,适合用户爱好、附加标签这类可以多选的业务场景。















