记录一次对order by、goup by和distinct的sql优化

问题描述

起初是因为一个B端的商品评价列表查询很慢,一次查询大概需要7秒左右的时间。相关sql是几个表的关联查询,主表数据大概只有十几万,最多的表数据也不超过50万,第一反应就是sql有问题或者没有建索引。

原始sql大致如下

java 复制代码
SELECT DISTINCT
	table_a.id,
	table_a.a,
	table_a.b,
	table_a.c,
	table_b.a,
	table_b.b,
	table_b.c
FROM
	table_a 
LEFT JOIN table_b  ON (table_b.a_id = table_a.id)
WHERE
	( table_a.gcflag = 0)
ORDER BY

	table_a.create_time DESC
LIMIT 10

实际sql比这要复杂,提取影响效率的关键部分精简后大致如上,就是两个表的join查询,根据查询条件 WHERE后边还可能会有table_b的字段查询,因不影响问题分析,暂时就不列出来了。

table_a和table_b是1对多的关系,用到了DISTINCT关键字去重,t1的create_time有索引,gcflag是删除标记,值只有0或1所以没有建索引。

第一反应是DISTINCT用的不合理,以我之前的了解DISTINCT更多的用于查询单个字段去重,或者接口count()函数使用,更多的时候去重使用的是group by。

sql优化

先来看下sql分析计划

可以看到table_a是全表扫描,并且从extra中可以看出该sql用了临时表,并且用了文件排序,效率很差,所以sql才会很慢。

我们来看下mysql官网对DISTINCT的优化描述 首先说到DISTINCT结合ORDER BY使用在很多情况下是会使用临时表的,并且大多数情况下DISTINCT是等价于GROUP BY的,我们这条sql使用DISTINCT等价于GROUP BY table_a.id,table_a.a,table_a.b,table_a.c,table_b.a,table_b.b,table_b.c,也就是说会对所有查询条件进行排序去重。

实际上我们只需要对table_a.id去重就可以了,所以先尝试用group by简化下sql看下效果,简化后sql如下

java 复制代码
SELECT
	table_a.id,
	table_a.a,
	table_a.b,
	table_a.c,
	table_b.a,
	table_b.b,
	table_b.c
FROM
	table_a
LEFT JOIN table_b ON (table_b.a_id = table_a.id)
WHERE
	(table_a.gcflag = 0)
GROUP BY
	table_a.id
ORDER BY
	table_a.create_time DESC
LIMIT 10

现在这条sql执行已经降到1秒多,从执行计划来看的话table_a现在已经走主键索引查询了,虽说还是用到了临时表和文件排序,但是效率已经大大提升了。 针对官方提到的GROUP BY和ORDER BY一起使用大多数情况下都会用到临时表做下验证,把优化后的语句去掉ORDER BY后看下执行计划如下: 果然已经没有了Using temporary; Using filesort信息,并且查询时间已经降到了0.03秒了。但是业务有排序的要求,ORDER BY不能贸然拿掉。

在回头看下优化后的语句,虽然执行时间已经从7秒优化到了1秒多,但是针对当前表的数量总觉得这个查询效率还是比较差,进一步思考还有没有其他优化方案。

在官方对ORDER BY索引失效场景描述中有一项说的是,ORDER BY 和GROUP BY具有不同的表达式。

也就是说只有ORDER BY 和GROUP BY的条件相同时才可以用索引排序,否则将会建立临时表,可能会用到文件排序。

基于此尝试将ORDER BY table_a.create_time DESC改成 ORDER BY table_a.id DESC试一下,改动后的执行计划如下: 可以看出此时利用了索引排序,效果和去掉ORDER BY一样,并且查询也是只用了0.03秒。业务的排序要求是按照创建时间倒序排列,如果id是自增的话,按照id排序几乎可以等价于创建时间排序,至此也可以完成优化,但是我们的id用的是分布式雪花算法,虽说也是按照时间递增的,但是由于并发原因导致id排序和创建时间排序会有一些差异,所以不能采用这种方案。

既然排序需要先全表扫描或者扫表全部索引,建立临时表后在进行排序,那么可不可以通过子查询语句现将table_a数据查出来,在基于查出来的数据在进行排序去重,这样可以避免大量数据扫描。

基于这个思路优化后的sql如下:

java 复制代码
SELECT
	table_a.id,
	table_a.a,
	table_a.b,
	table_a.c,
	table_b.a,
	table_b.b,
	table_b.c
FROM
	(
		SELECT
			table_a.id,
			tablea.a,
			table_a.b,
			table_a.c
		FROM
			table_a
		WHERE
			gcflag = 0
		ORDER BY
			create_time DESC
		LIMIT 10
	) table_a
LEFT JOIN table_b ON (table_b.a_id = table_a.id)
GROUP BY
	table_a.id

sql执行计划如下 id大的执行顺序排在前边,所以这条sql的执行顺序是先根据条件查询table_a并走索引排序取前10条,查询结果作为驱动表去和table_b做关联查询,由于table_a的结果集只有10条数据,所以后边的GROUP BY的排序去重哪怕用到临时表或文件排序效率也很高,整个sql执行下来用时0.04秒,并且排序也满足业务要求,最终采用这种优化方案。

后边有时间在补充下mysql在同时具有order by和group by的条件时的执行顺序及逻辑。

相关推荐
计算机毕业设计小帅15 分钟前
【2026计算机毕业设计】基于Springboot的校园电动车短租平台
spring boot·后端·课程设计
调试人生的显微镜15 分钟前
Web前端开发工具实战指南 从开发到调试的完整提效方案
后端
Java水解16 分钟前
【SQL】MySQL中空值处理COALESCE函数
后端·mysql
Python私教25 分钟前
DRF:Django REST Framework框架介绍
后端·python·django
间彧26 分钟前
Java HashMap如何合理指定初始容量
后端
用户40993225021239 分钟前
PostgreSQL全表扫描慢到崩溃?建索引+改查询+更统计信息三招能破?
后端·ai编程·trae
PFinal社区_南丞1 小时前
PostgreSQL-10个鲜为人知的强大功能
数据库·后端
superlls1 小时前
(Spring)Spring Boot 中 @Valid 与全局异常处理器的联系详解
java·spring boot·后端
0110_10241 小时前
tauri + rust的环境搭建---初始化以及构建
开发语言·后端·rust
文心快码BaiduComate1 小时前
限时集福!Comate挂件/皮肤上线,符(福)气掉落中~
前端·后端·程序员