某个列唯一键(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 30808PUBLIC 27699SYSMAN 3491ORDSYS 2532APEX_030200 2406MDSYS 1509XDB 844OLAPSYS 719SYSTEM 529CTXSYS 366WMSYS 316EXFSYS 310SH 306ORDDATA 248OE 127DBSNMP 57IX 55HR 34PM 27FLOWS_FILES 12OWBSYS_AUDIT 12ORDPLUGINS 10OUTLN 9BI 8SI_INFORMTN_SCHEMA 8ORACLE_OCM 8SCOTT 7APPQOSSYS 3OWBSYS 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 ;
