某个列唯一键(Distinct_Keys)的数量叫作基数。比如性别列,该列只有男女之分,所以这一列基数是 2。主键列的基数等于表的总行数。基数的高低影响列的数据分布。
以测试表 test 为例,owner 列和 object_id 列的基数分别如下所示。
select count(distinct owner),count(distinct object_id),count(*) from test;
COUNT(DISTINCTOWNER) COUNT(DISTINCTOBJECT_ID) COUNT(*)
-------------------- ------------------------ ----------
29 72462 72462
TEST 表的总行数为 72 462,owner 列的基数为 29,说明 owner 列里面有大量重复值,object_id 列的基数等于总行数,说明 object_id 列没有重复值,相当于主键。owner 列的数据分布如下。
select owner,count(*) from test group by owner order by 2 desc;
OWNER COUNT(*)
-------------------- ----------
SYS 30808
PUBLIC 27699
SYSMAN 3491
ORDSYS 2532
APEX_030200 2406
MDSYS 1509
XDB 844
OLAPSYS 719
SYSTEM 529
CTXSYS 366
WMSYS 316
EXFSYS 310
SH 306
ORDDATA 248
OE 127
DBSNMP 57
IX 55
HR 34
PM 27
FLOWS_FILES 12
OWBSYS_AUDIT 12
ORDPLUGINS 10
OUTLN 9
BI 8
SI_INFORMTN_SCHEMA 8
ORACLE_OCM 8
SCOTT 7
APPQOSSYS 3
OWBSYS 2
owner 列的数据分布极不均衡,我们运行如下 SQL。
select * from test where owner='SYS';
SYS 有 30 808 条数据,从 72 462 条数据里面查询 30 808 条数据,也就是说要返回表中 42.5% 的数据。
select 30808/72462*100 "Percent" from dual;
Percent
----------
42.5160774
那么请思考,你认为以上查询应该使用索引吗?现在我们换一种查询语句。
select * from test where owner='SCOTT';
SCOTT 有 7 条数据,从 72 462 条数据里面查询 7 条数据,也就是说要返回表中 0.009% 的数据。
select 7/72462*100 "Percent" from dual;
Percent
----------
.009660236
请思考,返回表中 0.009% 的数据应不应该走索引?
如果你还不懂索引,没关系,后面的章节我们会详细介绍。如果你回答不了上面的问题,我们先提醒一下。当查询结果是返回表中 5% 以内的数据时,应该走索引;当查询结果返回的是超过表中 5% 的数据时,应该走全表扫描。
当然了,返回表中 5% 以内的数据走索引,返回超过 5% 的数据就使用全表扫描,这个结论太绝对了,因为你还没掌握后面章节的知识,这里暂且记住 5% 这个界限就行。我们之所以在这里讲 5%,是怕一些初学者不知道上面问题的答案而纠结。
现在有如下查询语句。
select * from test where owner=:B1;
语句中,「:B1」是绑定变量,可以传入任意值,该查询可能走索引也可能走全表扫描。
现在得到一个结论:如果某个列基数很低,该列数据分布就会非常不均衡,由于该列数据分布不均衡,会导致 SQL 查询可能走索引,也可能走全表扫描。在做 SQL 优化的时候,如果怀疑列数据分布不均衡,我们可以使用 select 列,count(*) from 表 group by 列 order by 2 desc 来查看列的数据分布。
如果 SQL 语句是单表访问,那么可能走索引,可能走全表扫描,也可能走物化视图扫描。在不考虑有物化视图的情况下,单表访问要么走索引,要么走全表扫描。现在,回忆一下走索引的条件:返回表中 5% 以内的数据走索引,超过 5% 的时候走全表扫描。相信大家读到这里,已经搞懂了单表访问的优化方法。
我们来看如下查询。
select * from test where object_id=:B1;
不管 object_id 传入任何值,都应该走索引。
我们再思考如下查询语句。
select * from test where object_name=:B1;
不管给 object_name 传入任何值,请问该查询应该走索引吗?
请你去查看 object_name 的数据分布。写到这里,其实有点想把本节名称改为「数据分布」。大家在以后的工作中一定要注意列的数据分布!
本节SQL
SELECT COUNT(DISTINCT owner),COUNT(DISTINCT object_id),COUNT(*) FROM TEST;
SELECT owner,COUNT(*) FROM test GROUP BY owner ORDER BY 2 DESC ;
SELECT OBJECT_NAME,COUNT(*) FROM TEST GROUP BY OBJECT_NAME ORDER BY 2 DESC ;