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

原文地址:https://www.cnblogs.com/heyang78/p/15215124.html