🔥 MySQL 小重点(精华中的精华)
一、WHERE 和 HAVING 的区别(必考!)
| 对比项 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前筛选 | 分组后筛选 |
| 能否用聚合函数 | ❌ 不能用 | ✅ 能用 |
| 能否用别名 | ❌ 不能用 | ✅ 能用 |
sql
-- ✅ 正确:WHERE 在分组前
SELECT city, COUNT(*) FROM users WHERE age > 18 GROUP BY city;
-- ✅ 正确:HAVING 在分组后,用聚合函数
SELECT city, COUNT(*) as cnt FROM users GROUP BY city HAVING cnt > 5;
-- ❌ 错误:WHERE 不能用聚合函数
SELECT city, COUNT(*) FROM users WHERE COUNT(*) > 5 GROUP BY city;
二、COUNT 的三种用法区别
sql
COUNT(*) -- 统计所有行,包括 NULL(最慢,最准确)
COUNT(1) -- 和 COUNT(*) 一样,性能相同
COUNT(列名) -- 统计该列非 NULL 的行数
-- 实际区别
SELECT COUNT(*), COUNT(age), COUNT(phone) FROM users;
-- 如果 phone 列有很多 NULL,COUNT(phone) 会少很多
三、DELETE 和 TRUNCATE 的区别
| 对比项 | DELETE | TRUNCATE |
|---|---|---|
| 速度 | 慢(逐行删) | 快(直接清空) |
| 能否回滚 | ✅ 能 | ❌ 不能 |
| 重置自增ID | ❌ 不重置 | ✅ 重置为1 |
| 触发触发器 | ✅ 触发 | ❌ 不触发 |
sql
DELETE FROM users; -- 可回滚,ID继续增长
TRUNCATE TABLE users; -- 不可回滚,ID从1开始
四、CHAR 和 VARCHAR 的区别
| 对比项 | CHAR | VARCHAR |
|---|---|---|
| 长度 | 固定长度 | 可变长度 |
| 存储空间 | 固定分配 | 实际长度+1 |
| 速度 | 更快 | 稍慢 |
| 适用场景 | 长度固定(如手机号、身份证) | 长度不定(如姓名、地址) |
sql
-- CHAR(10) 存 "abc":占10个字节
-- VARCHAR(10) 存 "abc":占4个字节(3+1)
五、IN 和 EXISTS 的区别(面试常问)
sql
-- IN:适合子查询结果小的情况
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- EXISTS:适合主表小、子查询大的情况
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
执行顺序不同:
-
IN:先执行子查询,再执行主查询 -
EXISTS:先执行主查询,再执行子查询
六、UNION 和 UNION ALL 的区别
sql
UNION -- 合并结果并去重(慢)
UNION ALL -- 合并结果不去重(快,推荐)
sql
-- 两个查询结果合并
SELECT name FROM users_a
UNION ALL
SELECT name FROM users_b;
七、LIKE 模糊查询的坑
sql
-- % 在右边:可以用索引(快)
WHERE name LIKE '张%' -- ✅ 以张开头的
-- % 在左边:不能用索引(慢)
WHERE name LIKE '%三' -- ❌ 以三结尾的
-- % 两边都有:不能用索引(很慢)
WHERE name LIKE '%三%' -- ❌ 包含三的
八、NULL 的特殊处理(必坑指南)
sql
-- NULL 参与运算结果都是 NULL
SELECT 1 + NULL; -- 结果 NULL
SELECT 'a' || NULL; -- 结果 NULL
-- 处理 NULL 的函数
SELECT IFNULL(age, 0) FROM users; -- 如果 age 是 NULL 就返回 0
SELECT COALESCE(age, phone, 0) FROM users; -- 返回第一个非 NULL 的值
九、EXPLAIN 查看执行计划(调优必备)
sql
EXPLAIN SELECT * FROM users WHERE name = '张三';
看懂这几列就够了:
| 列名 | 含义 | 好 | 坏 |
|---|---|---|---|
type |
访问类型 | ref、eq_ref |
ALL(全表扫描) |
possible_keys |
可能用的索引 | 有值 | NULL |
key |
实际用的索引 | 有值 | NULL |
rows |
扫描行数 | 越小越好 | 越大越慢 |
Extra |
额外信息 | Using index |
Using filesort |
十、索引失效的情况(背下来!)
sql
-- ❌ 这些情况索引会失效
WHERE name LIKE '%三' -- 左模糊
WHERE age + 1 = 20 -- 对列做了运算
WHERE LEFT(phone, 3) = '138' -- 用了函数
WHERE name = '张三' OR age = 18 -- OR 两边有一个没索引
WHERE age != 18 -- 不等于
WHERE name IS NULL -- IS NULL 可能失效(看版本)
-- ✅ 正确写法
WHERE name LIKE '张%' -- 右模糊
WHERE age = 19 -- 把运算放右边
WHERE phone LIKE '138%' -- 用 LIKE
十一、自增 ID 的坑
sql
-- 删除最后一条后,新插入的ID不会复用
DELETE FROM users WHERE id = 10;
INSERT INTO users ...; -- 新ID是11,不是10
-- 想要重置自增ID
TRUNCATE TABLE users; -- 方法1:清空表
ALTER TABLE users AUTO_INCREMENT = 1; -- 方法2:手动重置
十二、常用字符串函数
sql
CONCAT('a', 'b', 'c') -- 拼接:'abc'
LENGTH('你好') -- 字节长度:6
CHAR_LENGTH('你好') -- 字符长度:2
SUBSTRING('abcdef', 2, 3) -- 截取:'bcd'
REPLACE('abc', 'b', 'x') -- 替换:'axc'
TRIM(' abc ') -- 去空格:'abc'
UPPER('abc') -- 大写:'ABC'
LOWER('ABC') -- 小写:'abc'
十三、常用日期函数
sql
NOW() -- 当前日期时间:2024-01-15 10:30:00
CURDATE() -- 当前日期:2024-01-15
CURTIME() -- 当前时间:10:30:00
YEAR(NOW()) -- 提取年:2024
MONTH(NOW()) -- 提取月:1
DAY(NOW()) -- 提取日:15
DATE_ADD(NOW(), INTERVAL 1 DAY) -- 加1天
DATEDIFF('2024-01-20', '2024-01-15') -- 日期差:5
十四、CASE WHEN 条件判断
sql
-- 相当于 if-else
SELECT
name,
score,
CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 60 THEN '及格'
ELSE '不及格'
END as 等级
FROM students;
十五、最常用的3个优化技巧
sql
-- 1. 只查需要的列,不用 SELECT *
SELECT id, name FROM users; -- ✅
SELECT * FROM users; -- ❌
-- 2. 分页查询用 LIMIT
SELECT * FROM orders ORDER BY id LIMIT 100; -- ✅
-- 3. 用 EXISTS 代替 IN(子查询数据量大时)
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id); -- ✅
📌 一句话记住这些小重点
WHERE 分组前,HAVING 分组后;COUNT(*) 最准,TRUNCATE 更快;CHAR 固定 VARCHAR 变,左模糊会让索引失效;NULL 用 IS 不用等,UNION ALL 比 UNION 快;EXPLAIN 看执行计划,CASE WHEN 做条件判断。