乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

SET 类型

导学

ENUM 只能选一个,但现实中很多场景需要"多选"——比如用户的兴趣标签、商品的属性特征、权限的叠加组合。SET 类型让你在一个字段中存储多个预定义选项的组合,内部用位运算存储,查询时支持精确匹配和包含判断。

定义

SET:集合类型,用于存储从一个预定义字符串列表中选择的零个或多个值的组合。内部以位图(bitmask)存储,每个成员对应一个比特位。

核心语法

CREATE TABLE 表名 (
    列名 SET('值1', '值2', '值3', ...) [NOT NULL] [DEFAULT '']
);
特性说明
最大成员数64 个
存储空间1~8 字节(成员数决定,按位存储)
成员对应位第 1 个成员 = 第 0 位(值为 1),第 2 个成员 = 第 1 位(值为 2),以此类推
空值'' 表示空集合(所有位为 0)
重复值自动去重('A,A,B' 存储为 'A,B')

演示数据准备

DROP TABLE IF EXISTS employees;

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2),
    skills SET('Java', 'Python', 'SQL', 'Linux') DEFAULT ''
);

INSERT INTO employees (emp_name, dept, score, skills) VALUES
('大翔', '技术部', 100, 'Java,Python,SQL,Linux'),
('白歌', '技术部', NULL, 'Python,SQL');

当前 employees 表中的完整数据如下:

emp_idemp_namedeptscoreskills
1大翔技术部100Java,Python,SQL,Linux
2白歌技术部NULLPython,SQL

SQL 示例

场景一:插入 SET 值(多选组合)

当前数据状态:见上文 employees 表完整数据。

执行语句:

INSERT INTO employees (emp_name, dept, score, skills) VALUES ('孔蓝', '产品部', 88, 'SQL,Linux');

操作后结果:

SELECT * FROM employees;
emp_idemp_namedeptscoreskills
1大翔技术部100Java,Python,SQL,Linux
2白歌技术部NULLPython,SQL
3孔蓝产品部88SQL,Linux

结果解读:'SQL,Linux' 是 SET 定义中的合法组合,插入成功。SET 值用逗号分隔多个成员,顺序不影响存储结果(内部按定义顺序排序显示)。

场景二:插入非法成员的陷阱

当前数据状态:见上文 employees 表完整数据。

执行语句:

-- 严格模式下报错
INSERT INTO employees (emp_name, dept, score, skills) VALUES ('赵鸣', '运营部', 76, 'Java,Go');

操作后结果:

在 STRICT_TRANS_TABLES 模式下:

ERROR 1265 (01000): Data truncated for column 'skills'

在非严格模式下:

SELECT emp_name, skills FROM employees WHERE emp_name = '赵鸣';
emp_nameskills
赵鸣Java

结果解读:'Go' 不在 SET('Java', 'Python', 'SQL', 'Linux') 列表中。严格模式下报错;非严格模式下会静默截断非法成员,只保留合法成员 'Java'。这是 SET 的常见陷阱——数据被部分截断而不报错。

场景三:SET 的精确匹配查询(=)

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT emp_name, skills FROM employees WHERE skills = 'Python,SQL';

操作后结果:

emp_nameskills
白歌Python,SQL

结果解读:skills = 'Python,SQL' 是精确匹配,要求 skills 的值恰好等于 'Python,SQL'。大翔的 skills = 'Java,Python,SQL,Linux' 虽然包含 'Python,SQL',但不等于它,因此不匹配。

注意:SET 的精确匹配要求成员顺序与定义顺序一致(内部存储时会自动按定义顺序排序),但查询时写 'SQL,Python' 和 'Python,SQL' 是等价的,因为内部存储顺序固定。

场景四:SET 的包含查询(FIND_IN_SET)

当前数据状态:见上文 employees 表完整数据。

执行语句:

-- 查询包含 Python 技能的人
SELECT emp_name, skills FROM employees WHERE FIND_IN_SET('Python', skills) > 0;

操作后结果:

emp_nameskills
大翔Java,Python,SQL,Linux
白歌Python,SQL

结果解读:FIND_IN_SET('Python', skills) 返回 'Python' 在 skills 集合中的位置(从 1 开始),如果不包含则返回 0。因此 > 0 表示"包含"。这是 SET 类型最常用的查询方式——判断某个成员是否在集合中。

再看一个用位运算的等价写法:

-- 使用位运算判断(更底层,性能更好)
SELECT emp_name, skills FROM employees WHERE skills & (1 << 1) > 0;

'Python' 是 SET 定义中的第 2 个成员,对应第 1 位(1 << 1 = 2)。skills & 2 > 0 表示该位被置 1,即包含 'Python'。

场景五:SET 的位运算查询(多条件组合)

当前数据状态:见上文 employees 表完整数据。

执行语句:

-- 查询同时包含 Java 和 Linux 的人
SELECT emp_name, skills FROM employees
WHERE skills = 'Java,Linux'  -- 精确匹配,只包含这两个
   OR (FIND_IN_SET('Java', skills) > 0 AND FIND_IN_SET('Linux', skills) > 0);

操作后结果:

emp_nameskills
大翔Java,Python,SQL,Linux

结果解读:

  • skills = 'Java,Linux' 精确匹配只包含这两个技能的人,结果为空(大翔还包含 Python 和 SQL)
  • FIND_IN_SET('Java', skills) > 0 AND FIND_IN_SET('Linux', skills) > 0 判断同时包含 Java 和 Linux,匹配大翔

这是 SET 查询的核心认知:精确匹配用 =,包含判断用 FIND_IN_SET(),多条件组合用 AND。

场景六:SET 的添加和删除成员

当前数据状态:见上文 employees 表完整数据。

执行语句:

-- 给白歌添加 Linux 技能
UPDATE employees SET skills = skills | (1 << 3) WHERE emp_id = 2;
-- 等价于:UPDATE employees SET skills = skills + 'Linux' WHERE emp_id = 2;

-- 从大翔移除 Java 技能
UPDATE employees SET skills = skills & ~(1 << 0) WHERE emp_id = 1;
-- 等价于:UPDATE employees SET skills = skills - 'Java' WHERE emp_id = 1;

操作后结果:

SELECT emp_name, skills FROM employees;
emp_nameskills
大翔Python,SQL,Linux
白歌Python,SQL,Linux

结果解读:

  • skills | (1 << 3) 用位或运算添加成员('Linux' 是第 4 个成员,对应第 3 位)
  • skills & ~(1 << 0) 用位与运算清除成员('Java' 是第 1 个成员,对应第 0 位)
  • MySQL 也支持更直观的字符串运算:skills + 'Linux' 添加成员,skills - 'Java' 移除成员

常见误区

误区正解
"SET 和 ENUM 一样只能选一个"不是。SET 可以多选,ENUM 只能单选。
"SET 查询用 = 可以匹配包含关系"不是。= 是精确匹配,包含关系必须用 FIND_IN_SET()。
"SET 存储的是逗号分隔的字符串"不是。内部是位图(bitmask),1~8 字节,比 VARCHAR 节省空间。
"SET 成员顺序影响存储"不影响。内部按定义顺序自动排序显示,插入顺序无关。
"SET 可以存储 100 个成员"不能。最多 64 个成员。
"SET 比用关联表存储多对多关系更好"不一定。SET 适合选项少且固定的场景;选项多或动态变化时,用关联表更灵活。

面试考点

Q:SET 和 ENUM 的区别?

ENUM 单选,内部 1~2 字节;SET 多选,内部 1~8 字节按位存储。ENUM 像单选按钮,SET 像复选框。两者都限制在预定义列表中,但 SET 支持零个、一个或多个值的组合。

Q:SET 如何查询"包含某个成员"?

用 FIND_IN_SET('成员', 列名) > 0。不能用 =,因为 = 是精确匹配。也可以用位运算 列名 & (1 << n) > 0,其中 n 是成员在定义中的位置减 1。

Q:SET 最多多少个成员?存储空间怎么算?

最多 64 个成员。存储空间按成员数向上取整到字节:1~8 成员 = 1 字节,9~16 成员 = 2 字节,...,57~64 成员 = 8 字节。每个成员对应一个比特位。

Q:为什么生产环境很少用 SET?

  1. 查询语法特殊(FIND_IN_SET),不如关联表直观;2. 成员列表修改困难(ALTER TABLE);3. 无法存储额外属性(如每个技能的熟练度);4. 超过 64 个成员不支持。现代设计更倾向于用关联表(用户-技能中间表)存储多对多关系。

Q:SET 的位运算怎么理解?

SET('A','B','C','D') 中,A=第0位(1),B=第1位(2),C=第2位(4),D=第3位(8)。skills & 2 > 0 判断是否包含 B;skills | 4 添加 C;skills & ~1 移除 A。

小结

  • SET 用于存储预定义列表中的多个值的组合,内部以位图存储,节省空间
  • 查询包含关系用 FIND_IN_SET(),不能用 =(= 是精确匹配)
  • 支持位运算添加/删除成员,也支持字符串运算 + / -
  • 最多 64 个成员,修改成员列表需要 ALTER TABLE
  • 适合选项少且固定的多选场景(如兴趣标签、权限组合),复杂场景建议用关联表

下一章引子:ENUM 和 SET 都是基于字符串列表的类型,但如果需要存储的是纯二进制标志位——比如 8 个开关状态的组合——BIT 类型是更底层的选择。

上一页
ENUM 类型
下一页
JSON 类型