对韩峰著《SQL优化最佳实践》P7 案例的质疑

事先申明下,我的DB环境是Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production,如果与作者环境不同而导致结论差异则另当别论。

该案例做了一个id为varchar类型的两种查询对比,我模拟了一下。

我是这样建表的:

复制代码
create table tb_varchar2id(
   id varchar2(20) primary key,
   name nvarchar2(20),
   sal number(5,0)
)

insert into tb_varchar2id 
select rownum,dbms_random.string('*',dbms_random.value(6,20)),dbms_random.value(1000,30000)  from dual
connect by level<=2000000
order by dbms_random.random
复制代码

原作中是320万,我机器受限只能弄200万,这个差别不影响作者的思路和我的结论。

建表完提交后,开始第一个查询并观察其执行计划:

复制代码
SQL> select * from tb_varchar2id where id>='1900000';
已用时间:  00: 00: 00.00

执行计划
----------------------------------------------------------
Plan hash value: 3377844066

-----------------------------------------------------------------------------------
| Id  | Operation         | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |               |  1006K|    45M|  3602   (2)| 00:00:44 |
|*  1 |  TABLE ACCESS FULL| TB_VARCHAR2ID |  1006K|    45M|  3602   (2)| 00:00:44 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("ID">='1900000')

Note
-----
   - dynamic sampling used for this statement (level=2)
复制代码

这里走的也是全表扫描,cost是3602,作者那边不同的是8927.

 

再看封闭范围的查询及执行计划:

复制代码
SQL> select * from tb_varchar2id where id between '1900000' and '2000000';
已用时间:  00: 00: 00.00

执行计划
----------------------------------------------------------
Plan hash value: 1409398992

---------------------------------------------------------------------------------------------
| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |               |   104K|  4773K|   399   (0)| 00:00:05 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TB_VARCHAR2ID |   104K|  4773K|   399   (0)| 00:00:05 |
|*  2 |   INDEX RANGE SCAN          | SYS_C0011453  |   104K|       |   357   (0)| 00:00:05 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID">='1900000' AND "ID"<='2000000')

Note
-----
   - dynamic sampling used for this statement (level=2)
复制代码

这把走的是索引范围扫描,cost是399,原著中是5(作者加了索引SYS_C0025295)。

 

就本例而言,换了查询方式后cost从3602降到399,似乎有了数量级的提升,而原著中8927到5,更是令人瞠目结舌!

很多看官看到这里都要作者所云以为开放(>=)区间查询要次于封闭区间(between)查询了,还以为作者给出了一条可行的优化之路。

但是,下面两条SQL执行结果是不一样的。

select * from tb_varchar2id where id>='1900000';
select * from tb_varchar2id where id between '1900000' and '2000000';

让我们看看它们的数量:

复制代码
SQL> set autotrace off;
SQL> select count(*) from tb_varchar2id where id>='1900000';

  COUNT(*)
----------
    999995

已用时间:  00: 00: 00.21
SQL> select count(*) from tb_varchar2id where id between '1900000' and '2000000';

  COUNT(*)
----------
    111113

已用时间:  00: 00: 00.00
复制代码

前者是将近一百万条,后者是十一万条,数据量有一个数量级的差距,cost自然也有一个数量级的差距。

 

为什么会这样?因为id是varchar2类型,不是number类型,上面SQL在搞字符串比较呢。

就比如运行select * from tb_varchar2id where id>='1900000' and rownum<20;

复制代码
SQL> select * from tb_varchar2id where id>='1900000' and rownum<20;

ID                   NAME                                            SAL
-------------------- ---------------------------------------- ----------
1900000              YQJQLHKTYVLSZX                                12533
1900001              SPLMMLXO                                      18104
1900002              TYGGIMJCSIWOWUX                                6383
1900003              SYYYNRXSL                                     15890
1900004              GEGQAG                                         9448
1900005              SFGBZMMPOSEVMNEHQ                             20339
1900006              OMQGZZWVEPRWIMTYK                             13421
1900007              PWHATEOVY                                     11135
1900008              TLBRFDWDCEMXFYUXYH                            15930
1900009              ZUIQECXIRQXBTO                                15961
190001               WKEAMSE                                       25082

ID                   NAME                                            SAL
-------------------- ---------------------------------------- ----------
1900010              CMPQCVUBXSMBCMI                               17296
1900011              QDPNUNBDXBKV                                  17393
1900012              OYQBIBRADGE                                   12009
1900013              VIRWDAKEE                                     18760
1900014              NQJYHGKREUKGENWH                              28990
1900015              IKUUFL                                         7899
1900016              ACQDSR                                         1195
1900017              NXIECMAVNE                                     4208

已选择19行。
复制代码

连190001都混迹其中,这不应该是符合两个SQL意图的记录。 

所以,这是不同SQL在比较性能,这有意义吗?

作者一开头就出这么一个让人费解的地方,或是有些细节没有明写在书里,引起读者疑惑,有点不应该。

--2020年1月31日--

 

posted @   逆火狂飙  阅读(219)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
生当作人杰 死亦为鬼雄 至今思项羽 不肯过江东
点击右上角即可分享
微信分享提示