是时候总结一下以前的SQL优化例子了😁哈哈哈哈,欢迎交流
注意:SQL含有伪代码
1、投影(只取所需要的字段)
号称最简单的SQL优化-投影,就是只返回需要的字段数据。
比如我想查询groupID和成员ID list,就可以直接查询group_id,user_id,减少查询返回的数据。
vbnet
//实体的投影:group_member表字段很多,当我只需要查询 group的ID和group的成员ID时
@Data
public class GroupRelId implements Serializable {
Long id;
String userIds;
}
sql
@Select(value = "SELECT group_id as id, GROUP_CONCAT(user_id) AS user_ids FROM ch_group_member WHERE group_id is not null GROUP BY group_id")
List<GroupRelId> findRelIdAll();
2、多个SQL 优化为单个SQL(适合级联查询的数据)
有时为了减少数据库的访问次数,会将几个简单的SQL 改成一条SQL去执行。 可能有人会说不要连表查询,阿里规约上禁止连表,我觉得应该根据业务需求去选择,而不是用金科玉律去束缚自己。
比如:查询群组Group的详情,group和member虽然是两个实体对象,但是他们的关系不仅仅是组合,而是聚合关系。而且一个group的members不会太大,顶多1000多个。
多次SQL查询,再组装数据:group.setMembers(members)
csharp
-- 查询 group
select * from tb_group where id=#{groupId};
-- 查询 member list
select * from tb_group_member where group_id=#{groupId};
优化为 直接级联查询。
vbnet
SELECT g.*,
m.id AS m_id,
m.group_id AS m_group_id,
m.user_id AS m_user_id,
m.alias_name AS m_alias_name,
m.nickname AS m_nickname,
m.avatar_url AS m_avatar_url,
m.gender AS m_gender
FROM tb_group g
LEFT JOIN tb_group_member m ON g.id = m.group_id
WHERE g.group_id=#{groupId} )
3、复杂SQL 拆分为 简单SQL(将一条业务复杂的SQL操作拆分为几条简单的SQL)
有时为了降低业务的复杂程度,会将一条复杂的SQL拆分为几条简单的SQL去执行。
比如现在要分页查询商品goods+分类名+品牌名+销售属性list(因为前端页面要展示这些数据):
sql
SELECT g.*, c.name AS cate_name, b.name AS brand_name, sa.*
FROM
(SELECT * FROM tb_goods WHERE is_sale = TRUE LIMIT ${size} OFFSET ${offset}) g --商品主表
LEFT JOIN tb_category c
ON g.category_id = c.id -商品分类表
LEFT JOIN tb_brand b
ON g.brand_id = b.id --商品品牌表
LEFT JOIN rel_goods_saleattr rs --关联表
ON g.id = rs.goods_id
LEFT JOIN tb_sale_attribute sa --商品销售属性表(一对多)
ON rs.attr_id = sa.id;
拆分为多条SQL执行
step1------查询商品主表分页; step2------查询商品的销售属性list; 再组合并返回前端渲染
sql
-- 查询商品主表分页
SELECT * FROM tb_goods WHERE is_sale = TRUE LIMIT ${size} OFFSET ${offset};
-- 查询销售属性list(但是这样会在分页内,循环查询SQL)
SELECT * FROM tb_sale_attribute sa --商品销售属性表(一对多)
INNER JOIN rel_goods_saleattr rs --关联表
ON rs.attr_id = sa.id
WHERE rs.goods_id=#{goodsId}
-- (优化为批量查询消息属性list,业务代码组装为map,再去goods.setSaleAttrs(map[goodsId]))
SELECT * FROM tb_sale_attribute sa --商品销售属性表(一对多)
INNER JOIN rel_goods_saleattr rs --关联表
ON rs.attr_id = sa.id
WHERE rs.goods_id IN #{goodsIds} -- goodsIds为每次分页后的id list
进一步优化:由于品牌名称、分类名称几乎不会更改。避免每次连表查询。
可以冗余brandName、categoryName字段到goods表中(缺点是名称更改时,需要刷数据)。 页可以缓存到Redis中: id2brandName、id2categoryName(名称更改时,需更新缓存)。
4、使用窗口函数
比如要查询 每个分类下销量前100的商品
sql
SELECT g1.*, cate.name AS category_name
FROM serve_goods g1
JOIN tb_category cate --关联分类表获取分类名
ON g1.cate_id = cate.id
WHERE
( SELECT COUNT(*) FROM serve_goods g2
WHERE g2.cate_id = g1.cate_id AND g2.sale_count > g1.sale_count
) < 100
AND g1.is_sale = true
ORDER BY g1.cate_id, g1.sale_count DESC;
优化后
sql
-- 根据 category 分组且排序
WITH SortedGoods AS (
SELECT sg.*, cate.name AS category_name,
ROW_NUMBER() OVER (PARTITION BY sg.cate_id ORDER BY sg.sales_count DESC) as rn
FROM serve_goods sg
LEFT JOIN tb_category cate --关联分类表获取分类名
ON sg.cate_id = cate.id
WHERE sg.is_sale = true
)
SELECT * FROM SortedGoods WHERE rn<=#{limitSize} ORDER BY cate_id,rn;
。。。持续更新。。。