1.多表查询关键点在哪
📖 1️⃣ 明确关联关系
先搞清楚多表之间的关联关系:
-
一对一(1:1)
-
一对多(1:N)
-
多对多(M:N)
比如:
-
一个课程对应一个教室(1:1)
-
一个教师可以教多门课程(1:N)
-
一个学生可以选多门课,一门课也可以有多个学生(M:N)
📖 2️⃣ 确定关联字段
两个表之间的关联靠什么字段:
-
通常是主键、外键
例子:
teacher.id = course.teacher_id
📖 3️⃣ 选择合适的连接方式
常见的 SQL 连接:
-
INNER JOIN:内连接,只有匹配上的行会出现在结果里
-
LEFT JOIN:左连接,左表有、右表无的情况也保留,右表补 null
-
RIGHT JOIN:右连接,同理
-
FULL JOIN:全连接,左右都保留,匹配不到的补 null
SELECT c.name, t.name
FROM course c
INNER JOIN teacher t ON c.teacher_id = t.id;
📖 4️⃣ 查询字段控制
多表查询容易造成字段重名冲突 或返回无用字段,建议:
-
指定查询字段而非
SELECT *
-
可以给字段取别名
📖 5️⃣ WHERE 和 ON 条件区别
-
ON
:连接条件,决定哪些行配对 -
WHERE
:筛选最终结果行
如果是 LEFT JOIN
,一定注意条件写在 ON
里还是 WHERE
里,区别很大:
-- 错误写法,WHERE 会过滤掉左表独有行
SELECT ...
FROM A
LEFT JOIN B ON A.id = B.a_id
WHERE B.name = '张三'
-- 正确写法,条件写 ON,保证左表行保留
SELECT ...
FROM A
LEFT JOIN B ON A.id = B.a_id AND B.name = '张三'
?📖 6️⃣ 多表分页优化
-
避免直接
ORDER BY
多表字段,特别是大表关联 -
尽量先子查询确定 id,再回表查详细
SELECT * FROM (
SELECT c.id
FROM course c
INNER JOIN teacher t ON c.teacher_id = t.id
WHERE t.name = '张三'
LIMIT 0,10
) temp
INNER JOIN course c ON temp.id = c.id
?📖 7️⃣ ORM 框架多表查询要点
比如 MyBatis-Plus:
-
@TableField(exist = false)
标记非表字段 -
自定义 SQL 或用 XML 配置多表
-
多表分页用
IPage
配合自定义Mapper
2.多表查询和联合索引
✅定义不同
项目 | 多表查询 | 联合索引 |
---|---|---|
概念 | 同时查询多张表的数据,根据关联字段建立关系 | 在一张表中,把多个列组合成一个索引,提高查询效率 |
作用 | 跨表取数据,组合关联结果 | 优化单表或多条件查询的执行效率 |
SQL表现 | JOIN / INNER JOIN / LEFT JOIN |
CREATE INDEX idx_name ON table(col1, col2, ...) |
涉及表数量 | 多张表 | 单张表 |
3.where和having
📖 如果要查出【2024年1月1日之后的订单】,该把条件放哪?
👉 这个就该放 WHERE
,因为是筛选原始订单数据
WHERE o.order_date > '2024-01-01'
📖 如果要查出【分组后,订单总金额超过 1000】的客户,就要放哪?
👉 放 HAVING
,因为是对聚合后的结果筛选
HAVING SUM(o.amount) > 1000
📊 执行顺序:
SQL 实际执行顺序是:
FROM → ON → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
练习题
📌 sales
销售表
sale_id | product_id | quantity | sale_date |
---|---|---|---|
1 | 2001 | 5 | 2024-03-01 |
2 | 2002 | 3 | 2024-03-02 |
3 | 2001 | 7 | 2024-03-05 |
4 | 2003 | 2 | 2024-03-06 |
📌 products
商品表
product_id | product_name |
---|---|
2001 | 笔记本 |
2002 | 鼠标 |
2003 | 键盘 |
📖 要求:
查出销售数量大于 3,2024-03-01 之后的记录 ,
按商品分组,统计每个商品的总销售数量 ,
筛选出总数量超过 8 的商品 ,
按总数量降序排列
SELECT p.product_name, SUM(s.quantity) AS total_quantity
FROM sales s
INNER JOIN products p ON s.product_id = p.product_id
WHERE s.quantity > 3
AND s.sale_date > '2024-03-01'
GROUP BY p.product_name
HAVING SUM(s.quantity) > 8
ORDER BY total_quantity DESC;
锁机制
1.为什么要有可重入锁
📌 ① 递归 / 嵌套调用场景必需
👉 当一个线程已经获得锁,在未释放锁的情况下又调用了需要同一把锁的方法
如果锁不可重入:
-
第二次获取锁时线程自己把自己锁死
-
要么死锁,要么抛异常,要么业务异常返回
而可重入锁,允许同一个线程多次获取相同的锁,每次获取计数+1,释放计数-1,直至0释放资源。
📌 ② 保证调用链一致性,不破坏业务流程
比如:
public void methodA() {
lock.lock();
try {
methodB();
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock(); // 如果这里加锁,不可重入就死锁了
try {
// do sth
} finally {
lock.unlock();
}
}
这种情况,methodA 和 methodB 可能被不同地方调用,为了保证方法内数据一致性、线程安全,两者都加了锁。
-
如果用不可重入锁:
-
methodA锁住
-
调用methodB,methodB尝试加锁 → 死锁
-
-
如果用可重入锁:
-
methodA锁住
-
methodB尝试加锁,发现是自己线程持有的锁
-
直接通过,重入次数+1,等释放两次unlock,锁才真正释放
-
📌 ③ 提升代码复用性和灵活性
方法内部不需要管调用方是不是持有锁,自己业务该加就加,不用小心翼翼的判断锁状态,降低耦合。
📌 分布式环境下为什么也要有可重入锁?
📌 1. 保证递归 / 嵌套远程调用正常执行
比如:
-
调用链A服务 → B服务 → A服务(分布式递归或双向调用)
-
Redis分布式锁,如果不可重入,A服务第二次进来直接阻塞或异常
📌 2. 保证分布式事务、流程节点锁安全
多节点执行某个任务流程,如果内部套了多个步骤,每个步骤加同一把锁,锁不可重入会中断流程。
📌 3. Redisson做到了
Redisson的分布式锁(RLock)天然支持可重入,记录requestId + 重入次数,完美解决这个坑。
📌 面试标准答法 ✅
问:为什么要设置可重入锁特性?
答 :
可重入锁允许同一线程在持有锁的情况下,多次获取同一把锁,不会发生阻塞或死锁。它可以解决递归调用、嵌套方法调用等场景中的锁冲突问题,保证调用链一致性,提升代码复用性和灵活性。
在分布式场景中,Redisson实现了分布式可重入锁,通过requestId和重入次数计数,确保同一线程或调用链可以多次安全加锁,避免业务阻塞。