6547 字
33 分钟
100 道常见 MySQL 数据库题目基础篇
2026-04-16 10:15:09

1. 什么是内连接、外连接、交叉连接、笛卡尔积?#

答:

这几个概念本质上都和“多表查询”有关,区别主要在于:连接后保留哪些记录

1)内连接(INNER JOIN)#

内连接只返回两张表中满足连接条件的记录。 如果两张表之间某条记录无法匹配上,就不会出现在结果中。

例如:

SELECT *
FROM user u
INNER 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 u
LEFT JOIN orders o ON u.id = o.user_id;

含义:所有用户都要返回,即使这个用户没有订单,也会显示出来,只不过订单字段是 NULL


4)右连接(RIGHT JOIN)#

返回右表的全部记录,以及左表中匹配成功的记录。 如果左表中没有匹配,则左表对应字段填 NULL

SELECT *
FROM user u
RIGHT JOIN orders o ON u.id = o.user_id;

含义:所有订单都要返回,即使某个订单在用户表里找不到对应用户。


5)交叉连接(CROSS JOIN)#

交叉连接返回两张表所有记录的组合,也就是 SQL 中的笛卡尔积实现。

如果:

  • A 表有 m
  • B 表有 n

那么交叉连接结果就是 m * n 行。

SELECT *
FROM A
CROSS 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 JOIN
  • LEFT JOIN
  • RIGHT JOIN

它们最大的区别是:结果集保留哪边的数据


1)INNER JOIN#

只保留两张表中连接条件匹配成功的记录。

例如用户表和订单表:

SELECT *
FROM user u
INNER JOIN orders o ON u.id = o.user_id;

只有用户存在并且订单也存在时,数据才会出现在结果里。

如果某个用户没有订单,则这个用户不会被查出来。 如果某个订单对应的用户不存在,也不会被查出来。

所以可以理解为:取交集


2)LEFT JOIN#

左连接会把左表所有行都返回,右表中如果找不到匹配值,就补 NULL

SELECT *
FROM user u
LEFT JOIN orders o ON u.id = o.user_id;

意思是:

  • 用户表中的所有用户都要显示
  • 如果用户没有订单,也要显示出来
  • 只是订单字段会是 NULL

所以左连接经常用于:查“主表全部 + 附表匹配信息”


3)RIGHT JOIN#

右连接和左连接相反,会保留右表全部记录。

SELECT *
FROM user u
RIGHT JOIN orders o ON u.id = o.user_id;

意思是:

  • 订单表中的所有订单都要显示
  • 即使某些订单找不到对应用户,也要保留
  • 用户表字段用 NULL 补齐

实际开发中,RIGHT JOIN 用得相对少,因为通过调整表顺序,用 LEFT JOIN 往往也能实现相同效果。


举例说明#

假设有两张表:

用户表 user:

idname
1张三
2李四
3王五

订单表 orders:

iduser_idamount
1011100
1021200
1034300

INNER JOIN 结果:#

SELECT *
FROM user u
INNER JOIN orders o ON u.id = o.user_id;

结果:

user.idnameorder.iduser_idamount
1张三1011100
1张三1021200

因为:

  • user.id=1 和订单匹配成功
  • user.id=23 没订单,不显示
  • orders.user_id=4 找不到用户,不显示

LEFT JOIN 结果:#

SELECT *
FROM user u
LEFT JOIN orders o ON u.id = o.user_id;

结果:

user.idnameorder.iduser_idamount
1张三1011100
1张三1021200
2李四NULLNULLNULL
3王五NULLNULLNULL

因为左表 user 全保留。


RIGHT JOIN 结果:#

SELECT *
FROM user u
RIGHT JOIN orders o ON u.id = o.user_id;

结果:

user.idnameorder.iduser_idamount
1张三1011100
1张三1021200
NULLNULL1034300

因为右表 orders 全保留。


总结#

  • INNER JOIN:只返回两表匹配上的数据
  • LEFT JOIN:左表全返回,右表匹配不上补 NULL
  • RIGHT JOIN:右表全返回,左表匹配不上补 NULL

一般情况下,实际开发中最常用的是:

  • INNER JOIN
  • LEFT JOIN

3. 说一下数据库的三大范式?#

答:

数据库三大范式是设计关系型数据库表结构时的规范,目的主要是:

  • 减少数据冗余
  • 避免插入、更新、删除异常
  • 保证数据一致性
  • 让表结构更清晰合理

三大范式分别是:

  • 第一范式(1NF)
  • 第二范式(2NF)
  • 第三范式(3NF)

1)第一范式(1NF)#

第一范式要求:数据库表中的每个字段都必须是原子性的,不可再分。

也就是说,一个字段里不能再嵌套多个值。

不符合 1NF 的例子#

用户表:

idnameaddress
1张三广东省-深圳市-南山区

这里 address 可以再拆分成:

说明这个字段不是最小不可分单位,不符合第一范式。

改进后#

idnameprovincecitydistrict
1张三广东省深圳市南山区

这就符合第一范式。


2)第二范式(2NF)#

第二范式要求:在满足第一范式的基础上,非主键字段必须完全依赖主键,而不能只依赖主键的一部分。

这个要求主要针对联合主键

举例#

订单明细表:

order_idproduct_idproduct_namepricecount

假设主键是 (order_id, product_id)

这里:

  • count 依赖于 order_id + product_id
  • 但是 product_nameprice 只依赖于 product_id

这就叫部分依赖,不符合第二范式。

改进方式#

拆成两张表:

商品表:

product_idproduct_nameprice

订单明细表:

order_idproduct_idcount

这样非主键字段都完全依赖主键,就符合第二范式。


3)第三范式(3NF)#

第三范式要求:在满足第二范式的基础上,非主键字段不能依赖其他非主键字段。

也就是说,不能存在传递依赖。

举例#

订单表:

order_iduser_iduser_nameuser_address

主键是 order_id

这里:

  • user_id 依赖 order_id
  • user_nameuser_address 又依赖 user_id

所以 user_nameuser_address 是通过 user_id 间接依赖 order_id,这就是传递依赖,不符合第三范式。

改进方式#

拆成:

订单表:

order_iduser_id

用户表:

user_iduser_nameuser_address

这样就符合第三范式。


三大范式的核心理解#

  • 1NF:字段不能再拆
  • 2NF:非主键字段必须完全依赖主键
  • 3NF:非主键字段不能依赖其他非主键字段

三大范式的优点#

  1. 减少数据冗余
  2. 更新时不容易出现不一致
  3. 插入删除更安全
  4. 表结构更规范

互联网场景为什么常常反范式?#

虽然范式设计规范,但现实业务里,为了性能,很多系统会适当做反范式设计

比如订单表里会冗余:

  • 用户名
  • 商品名称
  • 商品价格

为什么? 因为如果完全按三范式设计,查询订单详情时就得频繁 join 用户表、商品表,性能较差。 尤其在高并发场景下,适当冗余数据可以减少联表操作,提高查询效率

所以实际设计里往往是:

  • 核心表结构遵循范式思想
  • 在高性能场景下适度反范式

这本质上是:空间换时间


4. varcharchar 的区别?#

答:

charvarchar 都是 MySQL 中用来存储字符串的类型,但它们的存储方式和适用场景不同。


1)char#

char定长字符串,长度固定。

例如:

name CHAR(10)

无论你实际存储的是:

  • 'a'
  • 'abc'
  • 'hello'

都会按固定的 10 个字符长度存储,不足的部分会补空格。

特点:

  • 长度固定
  • 存储速度相对快
  • 会浪费空间
  • 最大长度为 255 个字符

适合场景:

适合长度基本固定的数据,比如:

  • 手机号
  • 身份证号
  • 性别
  • MD5 值
  • 固定长度状态码

2)varchar#

varchar变长字符串,存储时只占用实际内容长度加少量额外字节。

例如:

name VARCHAR(10)

如果插入 'abc',就只存 'abc' 实际内容,不会补满 10 个字符。

特点

  • 长度可变
  • 节省存储空间
  • 读取和写入时处理稍复杂
  • 最大长度理论上可到 65535 字节,但会受到行大小限制

适合场景

适合长度不固定的数据,比如:

  • 用户名
  • 标题
  • 邮箱
  • 地址
  • 评论内容(较短时)

3)核心区别总结#

对比项charvarchar
长度固定可变
存储方式不足补空格按实际长度存储
空间使用更浪费更节省
存取速度理论更快理论稍慢
适合场景固定长度字段可变长度字段

4)实际开发如何选?#

一般原则是:

  • 固定长度字段:优先 char
  • 长度不确定字段:优先 varchar

比如:

  • gender CHAR(1)
  • phone CHAR(11)(如果统一存 11 位)
  • username VARCHAR(50)
  • email VARCHAR(100)

5)为什么实际中 varchar 更常见?#

因为大多数业务字段长度都不固定。 如果都用 char,会造成大量空间浪费。 尤其当表数据量很大时,这种浪费会非常明显。

所以在实际开发中,除非字段长度真的固定,否则通常首选 varchar


5. blobtext 有什么区别?#

答:

BLOBTEXT 都用于存储大字段,但两者的核心区别在于:

  • BLOB 用于存储二进制数据
  • TEXT 用于存储文本数据

1)BLOB#

BLOB 全称是 Binary Large Object,即二进制大对象。 它适合存放:

  • 图片
  • 音频
  • 视频片段
  • 文件内容
  • 加密后的二进制流

特点

  • 存储的是原始字节数据
  • 不涉及字符集和排序规则
  • 比较和排序按字节进行

2)TEXT#

TEXT 用于存储大文本字符串,适合:

  • 文章正文
  • 评论内容
  • 日志文本
  • 富文本内容

特点

  • 有字符集
  • 有校对规则(collation)
  • 可以按字符语义比较和排序

3)主要区别#

对比项BLOBTEXT
存储内容二进制数据文本数据
字符集
排序规则
适用场景图片、文件流文章、评论、日志

4)实际开发建议#

  • 如果存的是文件、图片二进制内容,用 BLOB
  • 如果存的是纯文本内容,用 TEXT

不过需要注意,现代系统里通常不建议把大文件直接存数据库,而是:

  • 文件存对象存储(如 OSS、S3、MinIO)
  • 数据库存文件路径或 URL

这样性能和维护性更好。


6. DATETIMETIMESTAMP 的异同?#

答:

DATETIMETIMESTAMP 都可以表示日期和时间,在 MySQL 中非常常见。 它们看起来格式相似,但在存储范围、空间占用、时区处理上有明显不同。


1)相同点#

  • 都能表示日期和时间
  • 显示格式都类似:
YYYY-MM-DD HH:MM:SS
  • 都支持到秒,MySQL 高版本也支持微秒

2)不同点#

(1)存储范围不同#

  • DATETIME 范围更大:
1000-01-01 00:00:00 到 9999-12-31 23:59:59
  • TIMESTAMP 范围较小:
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_timeupdate_time 很方便

例如:

create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

3)怎么选?#

DATETIME 的场景#

  • 业务时间,不希望受时区影响
  • 时间范围可能很大
  • 例如预约时间、出生日期、活动时间

TIMESTAMP 的场景#

  • 记录系统操作时间
  • 需要自动更新时间
  • 例如创建时间、更新时间、日志时间

4)总结#

对比项DATETIMETIMESTAMP
存储范围
存储空间8 字节4 字节
时区影响
常用场景业务时间系统时间

实际开发中:

  • 业务含义时间常用 DATETIME
  • 记录系统创建/更新时间常用 TIMESTAMP

7. MySQL 中 inexists 的区别?#

答:

INEXISTS 都常用于子查询场景,用来判断一条记录是否满足某个集合或关联条件。 它们在逻辑上有时能实现相同效果,但执行方式、适用场景、性能特点有所不同。


1)IN 的含义#

IN 用于判断某个值是否在某个集合中。

例如:

SELECT *
FROM user
WHERE id IN (SELECT user_id FROM orders);

意思是:查询那些出现在订单表中的用户。

也可以理解成: 先把子查询结果查出来,形成一个集合,然后判断外层记录的 id 是否在这个集合里。


2)EXISTS 的含义#

EXISTS 用于判断子查询是否有结果返回,只要子查询能查出一条记录,就返回 true。

例如:

SELECT *
FROM user u
WHERE 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 u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);

这类“存在性”判断通常用 EXISTS 语义更清晰。


5)NOT INNOT EXISTS 的区别#

这是面试里很容易考的点。

NOT IN 的坑#

如果子查询结果中存在 NULL,那么 NOT IN 可能返回空结果。

例如:

SELECT * FROM user
WHERE id NOT IN (SELECT user_id FROM orders);

如果 orders.user_id 中有 NULL,那么比较会出现三值逻辑问题,结果可能不符合预期。

NOT EXISTS#

通常没有这个问题,更安全。

SELECT * FROM user u
WHERE 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

而不推荐使用:

  • FLOAT
  • DOUBLE

1)为什么不用 FLOAT / DOUBLE#

FLOATDOUBLE 属于浮点数类型,底层使用二进制近似表示。 二进制浮点数在表示十进制小数时,很多值无法做到完全精确,因此会出现精度误差。

例如:

  • 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.99

4)实际开发怎么设计?#

常见设计:

  • 价格:DECIMAL(10,2)
  • 金额:DECIMAL(18,2)
  • 汇率:DECIMAL(18,6) 或更高精度
  • 财务系统余额:DECIMAL(20,4) 视业务而定

如果是支付、财务、结算类系统,精度一定要充分考虑。


5)另一种做法:用“分”存整数#

有些系统还会用整数存金额,比如:

  • 1 元存为 100 分
  • 字段类型用 BIGINT

例如:

  • amount = 1999 表示 19.99 元

这种方式优点是:

  • 不存在小数精度问题
  • 运算更快
  • 很适合支付系统

缺点是:

  • 可读性差一点
  • 业务代码需要自己处理单位转换

所以实践中常见两种方案:

  • 数据库直接用 DECIMAL
  • 或者用整数分/厘来存

6)总结#

记录货币数据时,推荐:

  • DECIMAL:适合大多数业务
  • 或整数分存储:适合支付、高性能场景

不推荐:

  • FLOAT
  • DOUBLE

因为它们会有精度误差。


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 value

2)正确做法:使用 utf8mb4#

MySQL 中真正支持完整 UTF-8 的字符集是:

utf8mb4

它支持最多 4 字节字符,可以正常存储 emoji。


3)如何修改表字段?#

例如把某个字段改成支持 emoji:

ALTER TABLE blogs
MODIFY content TEXT
CHARACTER SET utf8mb4
COLLATE 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. dropdeletetruncate 的区别?#

答:

这三个命令都和“删除”有关,但删除的对象、速度、能否回滚、对表结构的影响完全不同,是面试中非常高频的问题。


1)DELETE#

DELETEDML(数据操作语言),用于删除表中的数据行。

特点

  • 可以带 WHERE
  • 可以删除部分数据
  • 也可以删除全部数据
  • 通常可回滚(在事务支持下)
  • 删除时是逐行删除

例如:

DELETE FROM user WHERE id = 1;

删除 id=1 的一条记录。

如果写成:

DELETE FROM user;

表示删除整张表的数据,但表结构还在。

适用场景

  • 删除部分记录
  • 对数据删除需要事务控制
  • 需要回滚时

2)TRUNCATE#

TRUNCATEDDL(数据定义语言),用于快速清空整张表的数据。

例如:

TRUNCATE TABLE user;

特点

  • 只能删除整表数据,不能带 WHERE
  • 删除速度快
  • 一般不可回滚
  • 表结构保留
  • 通常会重置自增值(auto_increment)

适用场景

  • 需要快速清空整张表
  • 不关心逐行删除过程
  • 不需要回滚

3)DROP#

DROP 也是 DDL,它删除的是整个对象,包括:

  • 表结构
  • 数据
  • 索引
  • 相关权限信息(视对象而定)

例如:

DROP TABLE user;

特点

  • 表直接没了
  • 数据也没了
  • 索引也没了
  • 不可回滚(通常)

适用场景

  • 这张表以后不再需要
  • 彻底删除表对象

4)三者对比表#

对比项DELETETRUNCATEDROP
类型DMLDDLDDL
删除对象数据行全部数据整张表
表结构是否保留保留保留不保留
是否可带 WHERE可以不可以不可以
是否可回滚通常可以通常不可以通常不可以
删除速度最快
自增值重置一般不重置通常重置表已删除

5)怎么选?#

  • 删部分数据DELETE
  • 清空整张表但保留结构TRUNCATE
  • 连表都不要了DROP

6)为什么 TRUNCATEDELETE 快?#

因为:

  • DELETE 通常是逐行删除
  • TRUNCATE 更像是直接重置表数据页,代价更小

所以面对大表全量清空时,TRUNCATE 通常比 DELETE 快很多。


11. UNIONUNION ALL 的区别?#

答:

UNIONUNION ALL 都用于合并多个 SELECT 查询结果集。 它们最大的区别在于:是否去重


1)UNION#

UNION 会对多个查询结果进行合并,并且自动去除重复记录

例如:

SELECT name FROM employee_2023
UNION
SELECT name FROM employee_2024;

如果两个查询结果中都出现了相同的 name,那么结果里只会保留一条。

特点

  • 自动去重
  • 结果集更“干净”
  • 需要额外做去重处理,性能开销更大

2)UNION ALL#

UNION ALL 会把多个查询结果直接合并,但不会去重

SELECT name FROM employee_2023
UNION ALL
SELECT name FROM employee_2024;

如果两个结果集里有重复值,会全部保留下来。

特点

  • 不去重
  • 性能更高
  • 适合明确允许重复数据的场景

3)执行效率对比#

很多人容易记反,这里要特别注意:

  • UNION ALLUNION
  • 因为 UNION 需要额外去重,通常需要排序或临时表处理
  • UNION ALL 只是简单拼接结果集

所以在业务上如果不需要去重,应优先使用 UNION ALL


4)使用要求#

使用 UNION / UNION ALL 时,多个 SELECT 语句必须满足:

  1. 列数相同
  2. 对应列的数据类型兼容
  3. 结果集顺序上要能一一对应

例如:

SELECT id, name FROM user
UNION ALL
SELECT id, name FROM customer;

这是合法的。

但下面这种通常不合法:

SELECT id, name FROM user
UNION ALL
SELECT id FROM customer;

因为列数不一致。


5)典型应用场景#

UNION

适合:

  • 需要多个查询结果合并后去重
  • 报表类查询
  • 统一查询多个来源但不希望重复展示

UNION ALL

适合:

  • 明确允许重复
  • 分表结果汇总
  • 日志合并
  • 追求更高性能

6)总结#

  • UNION:合并并去重
  • UNION ALL:合并但不去重
  • 性能上:UNION ALL 更高
  • 不需要去重时,优先用 UNION ALL

12. count(1)count(*)count(列名) 的区别?#

答:

这是 MySQL 面试中的经典题。 它主要考察两个点:

  1. 统计结果的语义区别
  2. 执行效率的理解

1)count(*)#

count(*) 表示统计所有行数,不管列值是不是 NULL

例如表:

idname
1张三
2NULL
3李四

执行:

SELECT COUNT(*) FROM user;

结果是:

3

因为它统计的是行数,不关心某列是否为 NULL


2)count(1)#

count(1) 也表示统计行数。

SELECT COUNT(1) FROM user;

这里的 1 是一个常量,对每一行来说都不是 NULL,所以效果上等价于统计总行数。

结果同样是:

3

3)count(列名)#

count(列名) 表示统计该列不为 NULL 的行数

例如:

SELECT COUNT(name) FROM user;

如果 name 列中有一行为 NULL,那么不会统计进去。 结果就是:

2

4)语义区别总结#

写法含义
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(*)
Comment seems to stuck. Try to refresh?✨