1. 什么是内连接、外连接、交叉连接、笛卡尔积?
答:
这几个概念本质上都和“多表查询”有关,区别主要在于:连接后保留哪些记录。
1)内连接(INNER JOIN)
内连接只返回两张表中满足连接条件的记录。 如果两张表之间某条记录无法匹配上,就不会出现在结果中。
例如:
SELECT *FROM user uINNER JOIN orders o ON u.id = o.user_id;这条 SQL 的意思是:只有用户表 user 中的用户,且在订单表 orders 中存在对应订单时,这条数据才会被查出来。
也就是说,只保留交集部分。
2)外连接(OUTER JOIN)
外连接是在内连接的基础上,除了返回匹配成功的记录,还会保留某一边没有匹配上的记录。
外连接分为:
- 左外连接(LEFT JOIN)
- 右外连接(RIGHT JOIN)
- 全外连接(FULL OUTER JOIN,MySQL 不直接支持)
3)左连接(LEFT JOIN)
返回左表的全部记录,以及右表中匹配成功的记录。
如果右表中没有匹配,则右表对应字段填 NULL。
SELECT *FROM user uLEFT JOIN orders o ON u.id = o.user_id;含义:所有用户都要返回,即使这个用户没有订单,也会显示出来,只不过订单字段是 NULL。
4)右连接(RIGHT JOIN)
返回右表的全部记录,以及左表中匹配成功的记录。
如果左表中没有匹配,则左表对应字段填 NULL。
SELECT *FROM user uRIGHT JOIN orders o ON u.id = o.user_id;含义:所有订单都要返回,即使某个订单在用户表里找不到对应用户。
5)交叉连接(CROSS JOIN)
交叉连接返回两张表所有记录的组合,也就是 SQL 中的笛卡尔积实现。
如果:
- A 表有
m行 - B 表有
n行
那么交叉连接结果就是 m * n 行。
SELECT *FROM ACROSS JOIN B;它不需要连接条件,会把 A 表每一行都和 B 表每一行组合一次。
6)笛卡尔积
笛卡尔积本来是数学中的概念。
如果集合 A = {a,b},集合 B = {1,2,3},那么 A × B 的结果就是:
(a,1), (a,2), (a,3), (b,1), (b,2), (b,3)在数据库中,如果多表查询没有写连接条件,往往就会产生笛卡尔积,这是非常危险的,容易导致结果数量暴增,SQL 性能极差。
总结
- INNER JOIN:只取匹配上的
- LEFT JOIN:左表全保留
- RIGHT JOIN:右表全保留
- CROSS JOIN:所有组合
- 笛卡尔积:交叉连接的结果,本质是数学概念
2. MySQL 的内连接、左连接、右连接有什么区别?
答:
MySQL 中最常见的连接查询就是:
INNER JOINLEFT JOINRIGHT JOIN
它们最大的区别是:结果集保留哪边的数据。
1)INNER JOIN
只保留两张表中连接条件匹配成功的记录。
例如用户表和订单表:
SELECT *FROM user uINNER JOIN orders o ON u.id = o.user_id;只有用户存在并且订单也存在时,数据才会出现在结果里。
如果某个用户没有订单,则这个用户不会被查出来。 如果某个订单对应的用户不存在,也不会被查出来。
所以可以理解为:取交集。
2)LEFT JOIN
左连接会把左表所有行都返回,右表中如果找不到匹配值,就补 NULL。
SELECT *FROM user uLEFT JOIN orders o ON u.id = o.user_id;意思是:
- 用户表中的所有用户都要显示
- 如果用户没有订单,也要显示出来
- 只是订单字段会是
NULL
所以左连接经常用于:查“主表全部 + 附表匹配信息”
3)RIGHT JOIN
右连接和左连接相反,会保留右表全部记录。
SELECT *FROM user uRIGHT JOIN orders o ON u.id = o.user_id;意思是:
- 订单表中的所有订单都要显示
- 即使某些订单找不到对应用户,也要保留
- 用户表字段用
NULL补齐
实际开发中,RIGHT JOIN 用得相对少,因为通过调整表顺序,用 LEFT JOIN 往往也能实现相同效果。
举例说明
假设有两张表:
用户表 user:
| id | name |
|---|---|
| 1 | 张三 |
| 2 | 李四 |
| 3 | 王五 |
订单表 orders:
| id | user_id | amount |
|---|---|---|
| 101 | 1 | 100 |
| 102 | 1 | 200 |
| 103 | 4 | 300 |
INNER JOIN 结果:
SELECT *FROM user uINNER JOIN orders o ON u.id = o.user_id;结果:
| user.id | name | order.id | user_id | amount |
|---|---|---|---|---|
| 1 | 张三 | 101 | 1 | 100 |
| 1 | 张三 | 102 | 1 | 200 |
因为:
user.id=1和订单匹配成功user.id=2、3没订单,不显示orders.user_id=4找不到用户,不显示
LEFT JOIN 结果:
SELECT *FROM user uLEFT JOIN orders o ON u.id = o.user_id;结果:
| user.id | name | order.id | user_id | amount |
|---|---|---|---|---|
| 1 | 张三 | 101 | 1 | 100 |
| 1 | 张三 | 102 | 1 | 200 |
| 2 | 李四 | NULL | NULL | NULL |
| 3 | 王五 | NULL | NULL | NULL |
因为左表 user 全保留。
RIGHT JOIN 结果:
SELECT *FROM user uRIGHT JOIN orders o ON u.id = o.user_id;结果:
| user.id | name | order.id | user_id | amount |
|---|---|---|---|---|
| 1 | 张三 | 101 | 1 | 100 |
| 1 | 张三 | 102 | 1 | 200 |
| NULL | NULL | 103 | 4 | 300 |
因为右表 orders 全保留。
总结
INNER JOIN:只返回两表匹配上的数据LEFT JOIN:左表全返回,右表匹配不上补 NULLRIGHT JOIN:右表全返回,左表匹配不上补 NULL
一般情况下,实际开发中最常用的是:
INNER JOINLEFT JOIN
3. 说一下数据库的三大范式?
答:
数据库三大范式是设计关系型数据库表结构时的规范,目的主要是:
- 减少数据冗余
- 避免插入、更新、删除异常
- 保证数据一致性
- 让表结构更清晰合理
三大范式分别是:
- 第一范式(1NF)
- 第二范式(2NF)
- 第三范式(3NF)
1)第一范式(1NF)
第一范式要求:数据库表中的每个字段都必须是原子性的,不可再分。
也就是说,一个字段里不能再嵌套多个值。
不符合 1NF 的例子
用户表:
| id | name | address |
|---|---|---|
| 1 | 张三 | 广东省-深圳市-南山区 |
这里 address 可以再拆分成:
- 省
- 市
- 区
说明这个字段不是最小不可分单位,不符合第一范式。
改进后
| id | name | province | city | district |
|---|---|---|---|---|
| 1 | 张三 | 广东省 | 深圳市 | 南山区 |
这就符合第一范式。
2)第二范式(2NF)
第二范式要求:在满足第一范式的基础上,非主键字段必须完全依赖主键,而不能只依赖主键的一部分。
这个要求主要针对联合主键。
举例
订单明细表:
| order_id | product_id | product_name | price | count |
|---|
假设主键是 (order_id, product_id)。
这里:
count依赖于order_id + product_id- 但是
product_name、price只依赖于product_id
这就叫部分依赖,不符合第二范式。
改进方式
拆成两张表:
商品表:
| product_id | product_name | price |
|---|
订单明细表:
| order_id | product_id | count |
|---|
这样非主键字段都完全依赖主键,就符合第二范式。
3)第三范式(3NF)
第三范式要求:在满足第二范式的基础上,非主键字段不能依赖其他非主键字段。
也就是说,不能存在传递依赖。
举例
订单表:
| order_id | user_id | user_name | user_address |
|---|
主键是 order_id。
这里:
user_id依赖order_iduser_name、user_address又依赖user_id
所以 user_name 和 user_address 是通过 user_id 间接依赖 order_id,这就是传递依赖,不符合第三范式。
改进方式
拆成:
订单表:
| order_id | user_id |
|---|
用户表:
| user_id | user_name | user_address |
|---|
这样就符合第三范式。
三大范式的核心理解
- 1NF:字段不能再拆
- 2NF:非主键字段必须完全依赖主键
- 3NF:非主键字段不能依赖其他非主键字段
三大范式的优点
- 减少数据冗余
- 更新时不容易出现不一致
- 插入删除更安全
- 表结构更规范
互联网场景为什么常常反范式?
虽然范式设计规范,但现实业务里,为了性能,很多系统会适当做反范式设计。
比如订单表里会冗余:
- 用户名
- 商品名称
- 商品价格
为什么? 因为如果完全按三范式设计,查询订单详情时就得频繁 join 用户表、商品表,性能较差。 尤其在高并发场景下,适当冗余数据可以减少联表操作,提高查询效率。
所以实际设计里往往是:
- 核心表结构遵循范式思想
- 在高性能场景下适度反范式
这本质上是:空间换时间。
4. varchar 和 char 的区别?
答:
char 和 varchar 都是 MySQL 中用来存储字符串的类型,但它们的存储方式和适用场景不同。
1)char
char 是定长字符串,长度固定。
例如:
name CHAR(10)无论你实际存储的是:
'a''abc''hello'
都会按固定的 10 个字符长度存储,不足的部分会补空格。
特点:
- 长度固定
- 存储速度相对快
- 会浪费空间
- 最大长度为 255 个字符
适合场景:
适合长度基本固定的数据,比如:
- 手机号
- 身份证号
- 性别
- MD5 值
- 固定长度状态码
2)varchar
varchar 是变长字符串,存储时只占用实际内容长度加少量额外字节。
例如:
name VARCHAR(10)如果插入 'abc',就只存 'abc' 实际内容,不会补满 10 个字符。
特点
- 长度可变
- 节省存储空间
- 读取和写入时处理稍复杂
- 最大长度理论上可到 65535 字节,但会受到行大小限制
适合场景
适合长度不固定的数据,比如:
- 用户名
- 标题
- 邮箱
- 地址
- 评论内容(较短时)
3)核心区别总结
| 对比项 | char | varchar |
|---|---|---|
| 长度 | 固定 | 可变 |
| 存储方式 | 不足补空格 | 按实际长度存储 |
| 空间使用 | 更浪费 | 更节省 |
| 存取速度 | 理论更快 | 理论稍慢 |
| 适合场景 | 固定长度字段 | 可变长度字段 |
4)实际开发如何选?
一般原则是:
- 固定长度字段:优先
char - 长度不确定字段:优先
varchar
比如:
gender CHAR(1)phone CHAR(11)(如果统一存 11 位)username VARCHAR(50)email VARCHAR(100)
5)为什么实际中 varchar 更常见?
因为大多数业务字段长度都不固定。
如果都用 char,会造成大量空间浪费。
尤其当表数据量很大时,这种浪费会非常明显。
所以在实际开发中,除非字段长度真的固定,否则通常首选 varchar。
5. blob 和 text 有什么区别?
答:
BLOB 和 TEXT 都用于存储大字段,但两者的核心区别在于:
BLOB用于存储二进制数据TEXT用于存储文本数据
1)BLOB
BLOB 全称是 Binary Large Object,即二进制大对象。
它适合存放:
- 图片
- 音频
- 视频片段
- 文件内容
- 加密后的二进制流
特点
- 存储的是原始字节数据
- 不涉及字符集和排序规则
- 比较和排序按字节进行
2)TEXT
TEXT 用于存储大文本字符串,适合:
- 文章正文
- 评论内容
- 日志文本
- 富文本内容
特点
- 有字符集
- 有校对规则(collation)
- 可以按字符语义比较和排序
3)主要区别
| 对比项 | BLOB | TEXT |
|---|---|---|
| 存储内容 | 二进制数据 | 文本数据 |
| 字符集 | 无 | 有 |
| 排序规则 | 无 | 有 |
| 适用场景 | 图片、文件流 | 文章、评论、日志 |
4)实际开发建议
- 如果存的是文件、图片二进制内容,用
BLOB - 如果存的是纯文本内容,用
TEXT
不过需要注意,现代系统里通常不建议把大文件直接存数据库,而是:
- 文件存对象存储(如 OSS、S3、MinIO)
- 数据库存文件路径或 URL
这样性能和维护性更好。
6. DATETIME 和 TIMESTAMP 的异同?
答:
DATETIME 和 TIMESTAMP 都可以表示日期和时间,在 MySQL 中非常常见。
它们看起来格式相似,但在存储范围、空间占用、时区处理上有明显不同。
1)相同点
- 都能表示日期和时间
- 显示格式都类似:
YYYY-MM-DD HH:MM:SS- 都支持到秒,MySQL 高版本也支持微秒
2)不同点
(1)存储范围不同
DATETIME范围更大:
1000-01-01 00:00:00 到 9999-12-31 23:59:59TIMESTAMP范围较小:
1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC所以如果你存的是特别早或特别晚的时间,应该选 DATETIME。
(2)占用空间不同
DATETIME:通常 8 字节TIMESTAMP:通常 4 字节
TIMESTAMP 更省空间。
(3)是否受时区影响
DATETIME:不受时区影响TIMESTAMP:受时区影响
TIMESTAMP 实际保存的是 UTC 时间,查询显示时会根据当前时区转换。
而 DATETIME 保存的就是字面值,不会自动转换。
举例,如果你插入:
'2025-01-01 12:00:00'DATETIME永远就是这个值TIMESTAMP可能在不同服务器时区下显示不同结果
(4)默认值行为不同
很多场景下:
TIMESTAMP常和CURRENT_TIMESTAMP配合使用- 用于
create_time、update_time很方便
例如:
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP3)怎么选?
选 DATETIME 的场景
- 业务时间,不希望受时区影响
- 时间范围可能很大
- 例如预约时间、出生日期、活动时间
选 TIMESTAMP 的场景
- 记录系统操作时间
- 需要自动更新时间
- 例如创建时间、更新时间、日志时间
4)总结
| 对比项 | DATETIME | TIMESTAMP |
|---|---|---|
| 存储范围 | 大 | 小 |
| 存储空间 | 8 字节 | 4 字节 |
| 时区影响 | 无 | 有 |
| 常用场景 | 业务时间 | 系统时间 |
实际开发中:
- 业务含义时间常用
DATETIME - 记录系统创建/更新时间常用
TIMESTAMP
7. MySQL 中 in 和 exists 的区别?
答:
IN 和 EXISTS 都常用于子查询场景,用来判断一条记录是否满足某个集合或关联条件。
它们在逻辑上有时能实现相同效果,但执行方式、适用场景、性能特点有所不同。
1)IN 的含义
IN 用于判断某个值是否在某个集合中。
例如:
SELECT *FROM userWHERE id IN (SELECT user_id FROM orders);意思是:查询那些出现在订单表中的用户。
也可以理解成:
先把子查询结果查出来,形成一个集合,然后判断外层记录的 id 是否在这个集合里。
2)EXISTS 的含义
EXISTS 用于判断子查询是否有结果返回,只要子查询能查出一条记录,就返回 true。
例如:
SELECT *FROM user uWHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id);意思是:查询那些存在订单记录的用户。
这里重点不是子查询具体返回什么,而是是否存在符合条件的记录。
3)执行思路区别
IN
通常更偏向于:
- 先执行子查询
- 得到一个结果集
- 再拿外层字段去匹配这个结果集
EXISTS
通常更偏向于:
- 先遍历外层表
- 对每一行去执行子查询
- 只要找到匹配记录就停止
当然,现代 MySQL 优化器会重写 SQL,实际执行可能不像字面理解那么机械,但面试时通常这样回答没问题。
4)适用场景
适合用 IN
当子查询结果集比较小时,IN 比较合适。
例如:
SELECT * FROM user WHERE dept_id IN (1,2,3);或者子查询返回很少记录时:
SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE status = 1);适合用 EXISTS
当外层表较小,子查询表较大,并且是做“是否存在”判断时,EXISTS 更合适。
例如:
SELECT * FROM user uWHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id);这类“存在性”判断通常用 EXISTS 语义更清晰。
5)NOT IN 和 NOT EXISTS 的区别
这是面试里很容易考的点。
NOT IN 的坑
如果子查询结果中存在 NULL,那么 NOT IN 可能返回空结果。
例如:
SELECT * FROM userWHERE id NOT IN (SELECT user_id FROM orders);如果 orders.user_id 中有 NULL,那么比较会出现三值逻辑问题,结果可能不符合预期。
NOT EXISTS
通常没有这个问题,更安全。
SELECT * FROM user uWHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id);所以实践中,排除型子查询优先考虑 NOT EXISTS。
6)性能怎么选?
经典经验:
- 子查询结果集小,用
IN - 子查询结果集大,用
EXISTS NOT EXISTS通常优于NOT IN
但要注意,现代 MySQL 优化器已经比较智能,很多时候具体性能还是要结合:
- 表大小
- 索引情况
- 执行计划
EXPLAIN
不能机械死记。
7)总结
IN:判断某值是否在某集合中EXISTS:判断是否存在满足条件的记录IN更适合小结果集EXISTS更适合关联存在性判断NOT EXISTS通常比NOT IN更安全
8. MySQL 里记录货币用什么字段类型比较好?
答:
在 MySQL 中,记录金额、余额、价格、工资这类货币数据时,最推荐的类型是:
DECIMAL而不推荐使用:
FLOATDOUBLE
1)为什么不用 FLOAT / DOUBLE?
FLOAT 和 DOUBLE 属于浮点数类型,底层使用二进制近似表示。
二进制浮点数在表示十进制小数时,很多值无法做到完全精确,因此会出现精度误差。
例如:
- 0.1
- 0.2
- 0.3
在二进制中都可能无法被精确表示。
这会带来什么问题?
例如金额计算:
0.1 + 0.2 != 0.3在某些情况下就会发生。 对于货币系统来说,这种误差是绝对不能接受的。
2)为什么推荐 DECIMAL?
DECIMAL 是定点数类型,会按照精确的十进制方式存储,适合需要高精度的数值。
例如:
salary DECIMAL(10,2)含义:
- 总共 10 位数字
- 其中 2 位是小数
- 可表示范围大致是:
-99999999.99 ~ 99999999.99
它可以精确表示金额,不会产生浮点误差。
3)DECIMAL(M,D) 中的含义
假设:
DECIMAL(9,2)那么:
9表示总位数(precision)2表示小数位数(scale)
所以整数部分最多 7 位,小数部分 2 位。 可存范围大致是:
-9999999.99 到 9999999.994)实际开发怎么设计?
常见设计:
- 价格:
DECIMAL(10,2) - 金额:
DECIMAL(18,2) - 汇率:
DECIMAL(18,6)或更高精度 - 财务系统余额:
DECIMAL(20,4)视业务而定
如果是支付、财务、结算类系统,精度一定要充分考虑。
5)另一种做法:用“分”存整数
有些系统还会用整数存金额,比如:
- 1 元存为 100 分
- 字段类型用
BIGINT
例如:
amount = 1999表示 19.99 元
这种方式优点是:
- 不存在小数精度问题
- 运算更快
- 很适合支付系统
缺点是:
- 可读性差一点
- 业务代码需要自己处理单位转换
所以实践中常见两种方案:
- 数据库直接用
DECIMAL - 或者用整数分/厘来存
6)总结
记录货币数据时,推荐:
DECIMAL:适合大多数业务- 或整数分存储:适合支付、高性能场景
不推荐:
FLOATDOUBLE
因为它们会有精度误差。
9. MySQL 怎么存储 emoji 😊?
答:
MySQL 可以存储 emoji,但前提是字符集必须支持 4 字节字符。
很多人以为用 utf8 就够了,其实 MySQL 的 utf8 不是完整的 UTF-8。
1)为什么普通 utf8 不能存 emoji?
MySQL 里的 utf8 实际上最多只支持 3 字节 编码。
而 emoji、部分生僻字、部分特殊符号需要 4 字节 才能表示。
例如:
- 普通汉字:通常 3 字节以内
- emoji 😊:需要 4 字节
所以如果表字段是 utf8,插入 emoji 时常会报错:
Incorrect string value2)正确做法:使用 utf8mb4
MySQL 中真正支持完整 UTF-8 的字符集是:
utf8mb4它支持最多 4 字节字符,可以正常存储 emoji。
3)如何修改表字段?
例如把某个字段改成支持 emoji:
ALTER TABLE blogsMODIFY content TEXTCHARACTER SET utf8mb4COLLATE utf8mb4_unicode_ci NOT NULL;4)仅改字段够不够?
不够。
为了完整支持 emoji,通常需要保证以下几个层次都使用 utf8mb4:
- 数据库字符集
- 表字符集
- 字段字符集
- 客户端连接字符集
例如数据库层面:
ALTER DATABASE testdb CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;表层面:
ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;连接层面也要确保 JDBC URL 或驱动配置正确。
5)为什么现代项目默认建议 utf8mb4?
因为除了 emoji 之外,还有很多 Unicode 字符都可能需要 4 字节。
为了避免后期出现兼容问题,现代项目通常直接统一使用 utf8mb4,而不是 utf8。
6)总结
- MySQL 的
utf8最多只支持 3 字节 - emoji 需要 4 字节
- 所以必须使用
utf8mb4 - 数据库、表、字段、连接最好都统一成
utf8mb4
10. drop、delete 与 truncate 的区别?
答:
这三个命令都和“删除”有关,但删除的对象、速度、能否回滚、对表结构的影响完全不同,是面试中非常高频的问题。
1)DELETE
DELETE 是 DML(数据操作语言),用于删除表中的数据行。
特点
- 可以带
WHERE - 可以删除部分数据
- 也可以删除全部数据
- 通常可回滚(在事务支持下)
- 删除时是逐行删除
例如:
DELETE FROM user WHERE id = 1;删除 id=1 的一条记录。
如果写成:
DELETE FROM user;表示删除整张表的数据,但表结构还在。
适用场景
- 删除部分记录
- 对数据删除需要事务控制
- 需要回滚时
2)TRUNCATE
TRUNCATE 是 DDL(数据定义语言),用于快速清空整张表的数据。
例如:
TRUNCATE TABLE user;特点
- 只能删除整表数据,不能带
WHERE - 删除速度快
- 一般不可回滚
- 表结构保留
- 通常会重置自增值(auto_increment)
适用场景
- 需要快速清空整张表
- 不关心逐行删除过程
- 不需要回滚
3)DROP
DROP 也是 DDL,它删除的是整个对象,包括:
- 表结构
- 数据
- 索引
- 相关权限信息(视对象而定)
例如:
DROP TABLE user;特点
- 表直接没了
- 数据也没了
- 索引也没了
- 不可回滚(通常)
适用场景
- 这张表以后不再需要
- 彻底删除表对象
4)三者对比表
| 对比项 | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| 类型 | DML | DDL | DDL |
| 删除对象 | 数据行 | 全部数据 | 整张表 |
| 表结构是否保留 | 保留 | 保留 | 不保留 |
| 是否可带 WHERE | 可以 | 不可以 | 不可以 |
| 是否可回滚 | 通常可以 | 通常不可以 | 通常不可以 |
| 删除速度 | 慢 | 快 | 最快 |
| 自增值重置 | 一般不重置 | 通常重置 | 表已删除 |
5)怎么选?
- 删部分数据:
DELETE - 清空整张表但保留结构:
TRUNCATE - 连表都不要了:
DROP
6)为什么 TRUNCATE 比 DELETE 快?
因为:
DELETE通常是逐行删除TRUNCATE更像是直接重置表数据页,代价更小
所以面对大表全量清空时,TRUNCATE 通常比 DELETE 快很多。
11. UNION 与 UNION ALL 的区别?
答:
UNION 和 UNION ALL 都用于合并多个 SELECT 查询结果集。
它们最大的区别在于:是否去重。
1)UNION
UNION 会对多个查询结果进行合并,并且自动去除重复记录。
例如:
SELECT name FROM employee_2023UNIONSELECT name FROM employee_2024;如果两个查询结果中都出现了相同的 name,那么结果里只会保留一条。
特点
- 自动去重
- 结果集更“干净”
- 需要额外做去重处理,性能开销更大
2)UNION ALL
UNION ALL 会把多个查询结果直接合并,但不会去重。
SELECT name FROM employee_2023UNION ALLSELECT name FROM employee_2024;如果两个结果集里有重复值,会全部保留下来。
特点
- 不去重
- 性能更高
- 适合明确允许重复数据的场景
3)执行效率对比
很多人容易记反,这里要特别注意:
UNION ALL比UNION快- 因为
UNION需要额外去重,通常需要排序或临时表处理 UNION ALL只是简单拼接结果集
所以在业务上如果不需要去重,应优先使用 UNION ALL
4)使用要求
使用 UNION / UNION ALL 时,多个 SELECT 语句必须满足:
- 列数相同
- 对应列的数据类型兼容
- 结果集顺序上要能一一对应
例如:
SELECT id, name FROM userUNION ALLSELECT id, name FROM customer;这是合法的。
但下面这种通常不合法:
SELECT id, name FROM userUNION ALLSELECT id FROM customer;因为列数不一致。
5)典型应用场景
UNION
适合:
- 需要多个查询结果合并后去重
- 报表类查询
- 统一查询多个来源但不希望重复展示
UNION ALL
适合:
- 明确允许重复
- 分表结果汇总
- 日志合并
- 追求更高性能
6)总结
UNION:合并并去重UNION ALL:合并但不去重- 性能上:
UNION ALL更高 - 不需要去重时,优先用
UNION ALL
12. count(1)、count(*) 与 count(列名) 的区别?
答:
这是 MySQL 面试中的经典题。 它主要考察两个点:
- 统计结果的语义区别
- 执行效率的理解
1)count(*)
count(*) 表示统计所有行数,不管列值是不是 NULL。
例如表:
| id | name |
|---|---|
| 1 | 张三 |
| 2 | NULL |
| 3 | 李四 |
执行:
SELECT COUNT(*) FROM user;结果是:
3因为它统计的是行数,不关心某列是否为 NULL。
2)count(1)
count(1) 也表示统计行数。
SELECT COUNT(1) FROM user;这里的 1 是一个常量,对每一行来说都不是 NULL,所以效果上等价于统计总行数。
结果同样是:
33)count(列名)
count(列名) 表示统计该列不为 NULL 的行数。
例如:
SELECT COUNT(name) FROM user;如果 name 列中有一行为 NULL,那么不会统计进去。
结果就是:
24)语义区别总结
| 写法 | 含义 |
|---|---|
count(*) | 统计总行数 |
count(1) | 统计总行数 |
count(列名) | 统计该列非 NULL 的行数 |
5)执行效率怎么理解?
这个问题很多资料讲得比较乱,需要区分版本和场景。
在现代 MySQL / InnoDB 中
通常:
count(*)count(1)
二者执行效率差异非常小,通常可以认为几乎等价。
MySQL 优化器会对它们做优化,不需要纠结 count(1) 一定比 count(*) 快这种说法。
6)为什么 count(列名) 可能更慢?
因为它需要额外判断这一列是否为 NULL。
尤其当这个列不是索引列,或者需要回表时,性能可能不如 count(*)。
7)开发中怎么选?
- 统计总行数:优先写
count(*) - 统计某列非空数量:用
count(列名)
为什么推荐 count(*)?
因为语义最清晰,表示就是“统计所有行”。
8)补充:InnoDB 和 MyISAM 的区别
- MyISAM 会保存表总行数,
count(*)无where时很快 - InnoDB 不保存精确总行数,通常需要扫描
所以在 InnoDB 中:
SELECT COUNT(*) FROM big_table;如果没有额外优化,可能是比较慢的。
9)总结
count(*):统计总行数,推荐用于总数统计count(1):本质上也是统计总行数count(列名):只统计该列非 NULL 的数量- 在现代 MySQL 中,
count(*)和count(1)性能通常差不多 - 语义上总数统计优先用
count(*)