一条"简单"的sql语句和小兔子买面包的故事 (r3笔记第25天)

时间:2022-05-04
本文章向大家介绍一条"简单"的sql语句和小兔子买面包的故事 (r3笔记第25天),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

有时候开发人员写sql语句的时候,接触的性能问题越多,可能对sql语句的结构,性能考虑会多一些,这也是一件好事,不过如果考虑不当,本来原本想做的的一些优化却使得问题变得更加严重。 在生产环境中我们对指定的客户端都有一定的监控,在下午的时候发现一个sql语句执行的时间太长了,抓到语句,一看倒不复杂,是一个开发人员运行的。 从他的sql语句可以看出他在尝试自连接account表(account表示百万级别的),l9_id不是account的主键,ban这个字段是主键字段。 select * from account a where l9_id = 'XX' and l9_id is not null and exists (select 1 from account where l9_id = a.l9_id and ban <> a.ban and rownum = 1) order by a.l9_id 他要做的查询从结构上来看类似下面的形式,比如表account里的数据如下 ban l9_id 1 1001 2 1001 3 1002 4 1001 5 1002 每个account对应一个l9_id字段,是不唯一的,比如根据account ban=1得到l9_id=1001,现在要查的是除了ban=1之外的l9_id为1001的ban列表。 从sql结构中可以看到,开发人员还专门使用了exists rownum,看起来好像还不错。 来看看oracle生成的执行计划。 你会看到cost已经到1886M,时间是999:59:59,从计划来看,oracle都不知道什么时候能执行完成。 的确从生成环境来看,这条语句执行很慢,用了2个小时。 SQL> @plan Plan hash value: 2418382151 -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 204 | | 1886M (1)|999:59:59 | | 1 | SORT ORDER BY | | 1 | 204 | 537M| 1886M (1)|999:59:59 | |* 2 | FILTER | | | | | | | |* 3 | TABLE ACCESS FULL | ACCOUNT | 1651K| 321M| | 33945 (1)| 00:06:48 | |* 4 | COUNT STOPKEY | | | | | | | |* 5 | TABLE ACCESS FULL| ACCOUNT | 2 | 26 | | 1146 (1)| 00:00:14 | -------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter( EXISTS (SELECT 0 FROM "ACCOUNT" "ACCOUNT" WHERE ROWNUM=1 AND "L9_ID"=:B1 AND "BAN"<>:B2)) 3 - filter("L9_COMPANY_CODE"='XX' AND "L9_ID" IS NOT NULL) 4 - filter(ROWNUM=1) 5 - filter("L9_ID"=:B1 AND "BAN"<>:B2) 可以举个笑话来说明一下,这个逻辑的问题, 有一天,小白兔到面包房买面包,问老板,“老板啊,你有100个面包吗?",老板说没有啊,小白兔第二天又去买面包,又问老板有没有100个面包啊。老板还是说没有,第三天的时候老板忙了很久终于做好了100个面包,小白兔又来买面包了,问老板你又100个面包吗,老板高兴的说有啊,小白兔也高兴的说,给我买一个! 如果仔细分析逻辑,就会发现那个rownum是画蛇添足,本来可以顺利得到ban的列表,但是反复循环,循环几百万次,每次都是一个全表扫描,还不一定能够查到对应的ban值。 对于这个语句,其实不用那么复杂。写成下面的形式就好。基本没有什么特别的地方。 select t1.ban from account t1,account t2 where t1.id = 'TD' and t1.id is not null and t2.l9_id is not null and t1.l9_id=t2.l9_lid and t1.ban!=t2.ban 执行计划来看确实是一个可以达到目标的计划。 Plan hash value: 1286362100 ------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | ------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 48M| 1353M| | 78038 (2)| 00:15:37 | |* 1 | HASH JOIN | | 48M| 1353M| 44M| 78038 (2)| 00:15:37 | |* 2 | TABLE ACCESS FULL| ACCOUNT | 1651K| 25M| | 33924 (1)| 00:06:48 | |* 3 | TABLE ACCESS FULL| ACCOUNT | 6605K| 81M| | 33915 (1)| 00:06:47 | ------------------------------------------------------------------------------------------ 所以,有些东西还是大道至简的道理,其实oracle内部也做了很多的优化,对于exists和in已经没有那么明显的差别了。而且它会从数据的分布角度进行计划的解析。从第一个执行计划来看,数据库分析的还是很合理的。