你可以学习到一些奇巧淫技帮助sql优化、explain分析等

数据库是mysql 5.7.26

开始的sql忘记了, 但不是重点, 首先通过一些常见优化方案, 例如先将内连接改为左外连接,并过滤子查询数据,去掉部分由于应用逻辑恒成立的某些条件,增加关键索引等,将查询时间从90s逐步提升到1.7s,但是1.7s仍然不可接受,继续优化

tu表只有2条记录,因此将左外连接改为内连接

(使用exists是考虑到将来wfcs_file_data表数据会大量增加,因为这是一个省以及下级各机构的各类文件, 如果将优化后的sql改为in可以从0.14s提升到0.08s, 但是这种提升是暂时的)

优化前

hive sql 迪卡尔乘机 sparksql笛卡尔积_hive sql 迪卡尔乘机

优化后

hive sql 迪卡尔乘机 sparksql笛卡尔积_database_02

从1.7s提升到0.14s,在之前的sql优化过程中能提升速度的left join,竟然成为了速度变慢的元凶,根据经验, 我们知道左外连接是比内连接要慢, 因为内连接会自动使用小表驱动大表, 而左外连接只能用左表来驱动, 且需要考虑右边的表为null的情况, 因此一般内连接是比左外连接快的,
通过EXPLAIN我们可以看到执行计划的差异

  1. 一是链接顺序的不同, 在左连接方案里使用tf驱动t1, 在内连接使用t1驱动tf(t1表在根据id=4的相关子查询filtered之后实际只剩2条会进入内层嵌套循环),此外, 在内连接中, id=4的子查询执行4567次, 而在左连接中, id=4的子查询需要执行591*4567次!
  2. 二是tf的filtered不同, filtered越小代表过滤越有效(不过这个值是悲观的估算, 仅作参考)

另外一种优化方案

根据explain执行计划,没有派生表, 很明显mysql没有先查询from(这正是mysql关联查询一直被诟病的地方, 反向优化), 我改写了左连接的sql让mysql强制先查询from子句(因为from子句的派生表过滤后只剩2行), 速度从1.7s提升到0.12s

sql如下

hive sql 迪卡尔乘机 sparksql笛卡尔积_内连接_03


后来出于学习的心态, 尝试了一下in + GROUP_CONCAT()函数进行优化, 对比直接使用in查询速度从0.077s提升到0.065s

select *  
        from wfcs_file_data tf ,
        (select t2.userId,t2.userName,t2.orgId,t2.orgName,t3.divisions
				 from
            (select t1.id userId,t1.userName,IF(ISNULL(t0.orgId) or t0.orgId='',t0.departmentId,t0.orgId) orgId,t0.orgName from cloud_management.cloud_user t1,cloud_management.cloud_staff t0
						-- where t1.id in (select DISTINCT(createManId) from wfcs_file_data)
						 -- where exists (select createManId from wfcs_file_data where createManId=t1.id)
			       where FIND_IN_SET(t1.id, (select GROUP_CONCAT(DISTINCT(createManId)) from wfcs_file_data))
							and t1.staffId=t0.id
						) t2 
						LEFT JOIN
							(select departId,divisions from cloud_wfcs_online.wfcs_department_with_division where beenDeleted=0 and tenantId='01d5ea39a7a74b4e8ed219af2bb4eeab') t3
						ON t2.orgId=t3.departId
        ) tu
        where tf.createManId=tu.userId 
				and tf.beenDeleted=0 and tf.tenantId='01d5ea39a7a74b4e8ed219af2bb4eeab'
            order by createTime desc
            limit 400,10

根据explain分析判断, 区别是in直接使用笛卡尔积(嵌套循环)然后过滤, GROUP_CONCAT先执行子查询, 然后将其结果充当过滤条件, 通过show warnings查看优化后的sql验证了我的想法(tips:如果in后的列表条件不算多的话,可以使用GROUP_CONCAT函数,如果列表数量很多还是使用in吧,因为in使用二分查找会比较快,而且sql可移植性比较高)