Delete..In.. 删除语句的优化再次探讨
有这样一张成绩表
CREATE TABLE stu_score ( id int , stu_id int, course_id int, score int, primary key(id) )
可以这样给它插入实验值:
Insert into stu_score select rownum,dbms_random.value(0,10000),dbms_random.value(1,5),dbms_random.value(0,150) from dual connect by level<10001
然后需求来了,stu_id和course_id相同时,最高的成绩保留,其余删除。用日常话来说就是取每个学生单科的最佳成绩。
第一种删除方式:
delete from stu_score where (stu_id,course_id,score) not in (select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id);
处理结果:
SQL> delete from stu_score where (stu_id,course_id,score) not in (select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id); 已删除1048行。 已用时间: 00: 00: 28.68
耗时约28秒,select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id的结果是8943条,这种删除需要从一万条里逐条取出去和8943条比对,比对次数为10000*8943次。虽说小表产生性能问题是小概率事件,但迟早会碰上。
这条删除语句慢的原因部分在于 stu_id,course_id,score 都不是主键,没有索引帮助,只能走全表查询的路子。但是,从解释计划来看,cost的飙升段发生在Delete Statement,达到了八千多,和后面的查询关系真心不大。我试了select * from stu_score where (stu_id,course_id,score) not in (select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id) 这样的语句,其中只是把delete替换成了select * ,执行起来就是秒出,并无delete时的阻滞。
有些书上提到过给甄别列stu_id,course_id,score 加上索引能加快删除速度的说法,我尝试过没有效果。具体如下:
SQL> create index idx_stuscore_three on stu_score(stu_id,course_id,score); 索引已创建。 已用时间: 00: 00: 00.06 SQL> delete from stu_score where (stu_id,course_id,score) not in (select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id); 已删除1011行。 已用时间: 00: 00: 29.47
第二种删除方式:
delete from stu_score where id not in (select a.id from stu_score a,( select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id ) b
where a.stu_id=b.stu_id and a.course_id=b.course_id and a.score=b.score)
处理结果:
SQL> delete from stu_score where id not in (select a.id from stu_score a,( select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id ) b 2 where a.stu_id=b.stu_id and a.course_id=b.course_id and a.score=b.score); 已删除1048行。 已用时间: 00: 00: 00.06
用时不到一秒,这种方式形成了一个id临时结果集,一万条里逐条拿id去看在不在这个结果集里,比对次数为10000次加一次结果集查询。由于id是主键,有效利用了id上的索引,这自然比全表查询要快。从解释计划上看,包括delete statement部分的cost只有区区三十几。
第三种删除方式:
delete from stu_score where not exists (
select null from stu_score a,
(select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id) b
where a.stu_id=b.stu_id and a.course_id=b.course_id and a.score=b.score and stu_score.id=a.id)
处理结果:
SQL> delete from stu_score where not exists ( 2 select null from stu_score a, 3 (select stu_id,course_id,max(score) as score from stu_score group by stu_id,course_id) b 4 where a.stu_id=b.stu_id and a.course_id=b.course_id and a.score=b.score and stu_score.id=a.id); 已删除1048行。 已用时间: 00: 00: 00.12
用时不到一秒,这种方式取出每行id进行了一次存在性查询,总体为一万次存在性查询。从解释计划上看,包括delete statement部分的cost也只有区区三十几。
可以看出,虽然存在子查询,但后两种相当于一万次函数调用,且函数参数只有一个,结果都是秒出;而第一种是10000*8943约九千万次比对,比对参数还有三个,相当于九千万次三个参数的函数调用,这自然落了下风。
由于SQL语句的度量不像程序般透彻,只能以运行时间去套函数调用的概念,目前水平也只能解释到这个程度,大家见谅。
前作:https://www.cnblogs.com/heyang78/p/12263253.html
END