当前位置: 首页 > news >正文

优化04-选择率和直方图

选择率

在Oracle数据库中,选择率(Selectivity) 是优化器(CBO,基于成本的优化器)用来评估SQL语句中某个条件(如WHERE子句)过滤数据的比例的关键指标。它直接影响优化器选择执行计划的策略,例如决定是否使用索引或全表扫描。

选择率表示满足某个条件的行数占总行数的比例

对于等值查询,参考Oracle的数据字典dba_tab_columns的DENSITY和NUM_DISTINCT字段。

col table_name for a20
col column_name for a20
col LOW_VALUE for a20
col HIGH_VALUE for a20
set line 400
select table_name,column_name,num_distinct,density,NUM_NULLS,LOW_VALUE,HIGH_VALUE
from dba_tab_columns where table_name='EMP';TABLE_NAME           COLUMN_NAME          NUM_DISTINCT    DENSITY  NUM_NULLS LOW_VALUE            HIGH_VALUE
-------------------- -------------------- ------------ ---------- ---------- -------------------- --------------------
EMP                  EMPNO                          14 .071428571          0 C24A46               C25023
EMP                  ENAME                          14 .071428571          0 4144414D53           57415244
EMP                  JOB                             5         .2          0 414E414C595354       53414C45534D414E
EMP                  MGR                             6 .038461538          1 C24C43               C25003
EMP                  HIREDATE                       13 .076923077          0 77B40C11010101       77BB0517010101
EMP                  SAL                            12 .083333333          0 C209                 C233
EMP                  COMM                            4        .25         10 80                   C20F
EMP                  DEPTNO                          3 .333333333          0 C10B                 C11F

对于等值查询,如果该列没有空值和直方图统计信息,选择率就是DENSITY的值或(1/NUM_DISTINCT);如果有空值,则可选择率为:(1/NUM_DISTINCT)*(NUM_ROWS-NUM_NULLS)/NUM_ROWS

对于范围查询,选择率的计算方法就在上述基础上加入最大值和最小值的统计信息,这里就不多做赘述。

选择率和索引

选择率影响着一个SQL的执行计划,准确的来说,选择率影响表的访问方式(即全表扫描还是索引扫描)。Oracle的SQL优化器是基于成本的,我们称为CBO,CBO会依据选择率来确定对某一数据集的访问的成本(COST),从而选择成本最低的访问方式。

例如,表A有8行数据,在表A上对列col1有索引,列col1上有8个不同值,如果SQLA的谓词条件为col1的等值查询,对与SQLA的最优执行计划,CBO会选择索引扫描;如果表A对列col2有索引,列col2上只有2个不同值,如果SQLB的谓词条件为col2的等值查询,对与SQLB的最优执行计划,CBO可能会选择全表扫描,因为索引扫描的寻找叶子块+回表的成本可能会大于全表扫描的成本。

下面我们做一下选择率的测试

--创建表和索引
create table tab1(id int,name varchar2(10),gender varchar2(5));
create index idx_id on tab1(id);
create index idx_gender on tab1(gender);
--插入2000条数据,id列从1递增,name列为随机的5个字符串,gender列为随机的‘f’或‘m’。
DECLARE-- 定义记录类型和集合类型TYPE t_employee IS RECORD (id       NUMBER,name     VARCHAR2(5),gender   CHAR(1));TYPE t_employee_tbl IS TABLE OF t_employee;v_data t_employee_tbl := t_employee_tbl(); -- 初始化集合
BEGIN-- 批量生成测试数据(200行)SELECT LEVEL AS id,-- 生成5位随机大写字母和数字组合(若只要字母可改用'X'参数)DBMS_RANDOM.STRING('X', 5) AS name,CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN 'm' ELSE 'f' END AS genderBULK COLLECT INTO v_dataFROM DUALCONNECT BY LEVEL <= 2000;-- 批量插入数据(使用FORALL提升性能)FORALL i IN 1 .. v_data.COUNTINSERT INTO scott.tab1 (id, name, gender)VALUES (v_data(i).id, v_data(i).name, v_data(i).gender);COMMIT; -- 提交事务
EXCEPTIONWHEN OTHERS THENROLLBACK; -- 异常回滚RAISE;
END;
/
--查看统计信息
ANALYZE TABLE scott.tab1 COMPUTE STATISTICS;
col table_name for a10
col column_name for a10
col LOW_VALUE for a20
col HIGH_VALUE for a20
set line 400
select table_name,column_name,num_distinct,density,NUM_NULLS,LOW_VALUE,HIGH_VALUE
from dba_tab_columns where table_name='TAB1';TABLE_NAME COLUMN_NAM NUM_DISTINCT    DENSITY  NUM_NULLS LOW_VALUE            HIGH_VALUE
---------- ---------- ------------ ---------- ---------- -------------------- --------------------
TAB1       ID                 2000      .0005          0 C102                 C215
TAB1       NAME               2000      .0005          0 3030463839           5A5A555138
TAB1       GENDER                2         .5          0 66                   6D
--分别以id和gener列为谓词条件查看
SQL> set autotrace traceonly;
SQL> select * from scott.tab1 where id=6;Execution Plan
----------------------------------------------------------
Plan hash value: 4102116554----------------------------------------------------------------------------------------------
| Id  | Operation                           | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |        |     1 |     9 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TAB1   |     1 |     9 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_ID |     1 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------SQL> select * from scott.tab1 where gender='f';1019 rows selected.Execution Plan
----------------------------------------------------------
Plan hash value: 2211052296--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  1000 |  9000 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB1 |  1000 |  9000 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

直方图

上面都是该列上数据分布均匀的情况,如果数据分布不均匀,及时统计信息是最新的,但其执行计划可能不是最优的。下面我们测试,

假设一个中学6年级有2000名学生,期中考试分为ABCDE五个等级,其中大部分同学的分数都集中在B,那么查询分数为B的同学信息可能存在执行计划不优的情况。

--创建表和索引
SQL> create table tab2(id int,name varchar2(10),grade char(1));
SQL> create  index grade_idx on tab2(grade);
--插入数据
DECLARE-- 定义记录类型和集合类型TYPE t_student IS RECORD (id     NUMBER,name   VARCHAR2(5),grade  CHAR(1));TYPE t_student_tbl IS TABLE OF t_student;v_data t_student_tbl := t_student_tbl(); -- 初始化集合
BEGIN-- 批量生成测试数据(300行)SELECT LEVEL AS id,-- 生成5位随机大写字母和数字组合(若只要字母可改用'X'参数)DBMS_RANDOM.STRING('X', 5) AS name,CHR(65 + FLOOR(DBMS_RANDOM.VALUE(0,5))) AS grade  -- 生成A-EBULK COLLECT INTO v_dataFROM DUALCONNECT BY LEVEL <= 2000;-- 批量插入数据(使用FORALL提升性能)FORALL i IN 1 .. v_data.COUNTINSERT INTO scott.tab2 (id, name, grade)VALUES (v_data(i).id, v_data(i).name, v_data(i).grade);COMMIT; -- 提交事务
EXCEPTIONWHEN OTHERS THENROLLBACK; -- 异常回滚RAISE;
END;
/把id为50-250的学生分数改为B
update tab2 set grade='B' where id>=50 and id <=2500;
commit;#查看统计信息
SQL> ANALYZE TABLE scott.tab2 COMPUTE STATISTICS;Table analyzed.select table_name,column_name,num_distinct,density,NUM_NULLS,LOW_VALUE,HIGH_VALUE2  from dba_tab_columns where table_name='TAB2';TABLE_NAME COLUMN_NAM NUM_DISTINCT    DENSITY  NUM_NULLS LOW_VALUE            HIGH_VALUE
---------- ---------- ------------ ---------- ---------- -------------------- --------------------
TAB2       ID                 2000      .0005          0 C102                 C215
TAB2       NAME               2000      .0005          0 3031324C58           5A5A543245
TAB2       GRADE                 5         .2          0 41                   45#查询分数为B的学生
SQL> set  autotrace traceonly statistic;
SQL> select * from scott.tab2 where grade='B';1963 rows selected.Execution Plan
----------------------------------------------------------
Plan hash value: 1237454846-------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |           |   400 |  3600 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TAB2      |   400 |  3600 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | GRADE_IDX |   400 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("GRADE"='B')Statistics
----------------------------------------------------------1  recursive calls0  db block gets272  consistent gets0  physical reads0  redo size59224  bytes sent via SQL*Net to client2037  bytes received via SQL*Net from client132  SQL*Net roundtrips to/from client0  sorts (memory)0  sorts (disk)1963  rows processed
--上面查询采用的索引扫描,逻辑读为272,如果强制让SQL使用全表扫描SQL> select /*+FULL(tab2) */ *  from scott.tab2 where grade='B';1963 rows selected.Execution Plan
----------------------------------------------------------
Plan hash value: 2156729920--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   400 |  3600 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB2 |   400 |  3600 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter("GRADE"='B')Statistics
----------------------------------------------------------1  recursive calls0  db block gets138  consistent gets0  physical reads0  redo size54904  bytes sent via SQL*Net to client2037  bytes received via SQL*Net from client132  SQL*Net roundtrips to/from client0  sorts (memory)0  sorts (disk)1963  rows processed--逻辑读为138,

明明全表扫描的逻辑读更低,为什么CBO还是使用索引扫描的执行计划呢?因为统计信息不知道列grade分布不均匀,安装1/5的选择率生成的执行计划,这种情况可以通过收集列的直方图来解决。

收集之前确定grade没有直方图统计信息

 select table_name,column_name,num_distinct,density,HISTOGRAMfrom dba_tab_columns where table_name='TAB2';
TABLE_NAME COLUMN_NAM NUM_DISTINCT    DENSITY HISTOGRAM
---------- ---------- ------------ ---------- ---------------
TAB2       ID                 2000      .0005 NONE
TAB2       NAME               2000      .0005 NONE
TAB2       GRADE                 5         .2 NONE

收集grade列直方图

EXEC DBMS_STATS.GATHER_TABLE_STATS('SCOTT', 'TAB2', METHOD_OPT => 'FOR COLUMNS GRADE SIZE AUTO');

再次查看grade列统计信息

SQL> select table_name,column_name,num_distinct,density,HISTOGRAM2   from dba_tab_columns where table_name='TAB2';TABLE_NAME COLUMN_NAM NUM_DISTINCT    DENSITY HISTOGRAM
---------- ---------- ------------ ---------- ---------------
TAB2       ID                 2000      .0005 NONE
TAB2       NAME               2000      .0005 NONE
TAB2       GRADE                 5     .00025 FREQUENCY

这时我们再次查看分数为B的学生信息

SQL> set autotrace traceonly;
SQL>  select * from scott.tab2 where grade='B';1963 rows selected.Execution Plan
----------------------------------------------------------
Plan hash value: 2156729920--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  1963 | 19630 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB2 |  1963 | 19630 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter("GRADE"='B')Statistics
----------------------------------------------------------1  recursive calls0  db block gets138  consistent gets0  physical reads0  redo size54904  bytes sent via SQL*Net to client2037  bytes received via SQL*Net from client132  SQL*Net roundtrips to/from client0  sorts (memory)0  sorts (disk)1963  rows processed

收集了直方图后,选择了成本更低的执行计划。

相关文章:

  • 小红书怎么看自己ip地址?小红书更改ip地址教学
  • 地级市-机器人、人工智能等未来产业水平(2009-2023年)-社科数据
  • 电子电器架构 --- 网关ECU中采用多CPU解决方案来实现网关功能
  • postgresql 创建、移出数据保留策略
  • 基于 Flask的深度学习模型部署服务端详解
  • 关于Java多态简单讲解
  • 社交距离调节下的情绪神经反应:基于360°视频的fMRI证据
  • 浏览器开发者工具Network面板中复制请求的相关信息(结合PostMan)
  • Redis设置永久密码
  • 指针的应用
  • DIC技术在焊接试样循环加载疲劳试验中的应用探究
  • uDistil-Whisper:低数据场景下基于无标签数据过滤的知识蒸馏方法
  • Kaamel白皮书:GenAI 时代的隐私困境
  • 05 Supply Chain Operations Reference (SCOR) Model
  • MySQL性能调优探秘:我的实战笔记 (上篇:从EXPLAIN到SQL重写)
  • [java八股文][Java并发编程面试篇]并发安全
  • OpenCV的floodFill(漫水填充)分割
  • 如何开发一个笑话管理小工具
  • 拟南芥T2T基因组-文献精读127
  • POC-公网对讲机的实现
  • 百济首次实现季度营业利润扭亏,泽布替尼销售额近57亿元
  • 姜再冬大使会见巴基斯坦副总理兼外长达尔
  • 万里云端遇见上博--搭乘“上博号”主题飞机体验记
  • 又一日军“慰安妇”制度受害者去世,大陆登记在册幸存者仅剩7人
  • 一网红被指涉脱衣殴打霸凌事件,沈阳警方:刑拘1人,处罚5人
  • 加拿大总理访美与特朗普“礼貌交火”