【DB筆試面試622】在Oracle中,說說COUNT(*)計算行數有哪些優化手段?

  • 2019 年 10 月 10 日
  • 筆記

題目部分

在Oracle中,說說COUNT(*)計算行數有哪些優化手段?

答案部分

手段

命令

執行計劃

主要原理

詳細說明

性能情況

全表掃描

TABLE ACCESS FULL

全表掃描

OLTP中,通常是最慢的方式。

邏輯讀為1139

增加普通索引

CREATE INDEX IDX_OBJECT_NAME ON T(OBJECT_NAME);

INDEX FAST FULL SCAN

從全表掃描轉成全索引掃描。

因為索引一般比表小的多,所以全表掃描轉成全索引掃描,性能能大幅度提升。

邏輯讀為400

常數索引

CREATE INDEX IDX_OBJECT_NAME ON T(0);

INDEX FAST FULL SCAN

從全表掃描轉成全索引掃描。

常數索引比普通索引更小。

邏輯讀為151

常數壓縮索引

CREATE INDEX IDX_OBJECT_NAME ON T(0) COMPRESS;

INDEX FAST FULL SCAN

從全表掃描轉成全索引掃描。

常數壓縮索引比常數索引更小。

邏輯讀為129

位圖索引

CREATE BITMAP INDEX IDX_OBJECT_NAME ON T(OBJECT_NAME);

BITMAP INDEX FAST FULL SCAN

從BTREE索引掃描轉成位圖索引掃描。

位圖索引的大小比BTREE索引要小的多,所以位圖索引掃描快。

邏輯讀為5

物化視圖

CREATE MATERIALIZED VIEW MV_COUNT_T BUILD IMMEDIATE REFRESH ON COMMIT ENABLE QUERY REWRITEAS SELECT COUNT(*) FROM T;

MAT_VIEW REWRITE ACCESS FULL

空間換時間。

要注意,如果數據要求比較實時,就不適用。

邏輯讀為3

緩存結果

SELECT /*+ RESULT_CACHE */ COUNT(*) FROM T;

RESULT CACHE

直接把查詢結果拿來用。

要注意,如果數據頻繁更新,就不適用。

邏輯讀為0

業務理解

SELECT COUNT(*) FROM T WHERE ROWNUM=1;

如果COUNT(*)只是為了判斷條數,就加上ROWNUM=1來判斷是否為1。

業務需求轉換,獲取條數有的時候,只是為了看看錶是否為空,這時候是否是1條和是否大於0其實是一樣的。

不言而喻

分析需求

據說,這個COUNT(*)統計條數語句,是多餘的!直接砍了這條語句,這裡沒有SQL!

無敵!

位圖索引可以按很高密度存儲數據,因此往往比B樹索引小很多,前提是在基數比較小(列重複度比較高)的情況下。位圖索引是保存空值的,因此可以在COUNT中利用。位圖索引不太適合OLTP類型數據庫。物化視圖是應用在數據要求不怎麼及時的場景下。若表頻繁更新,則不適合緩存結果集。

優化沒有止境,對數據庫了解越多,能想到的方法就越多。

--無索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  SET AUTOTRACE TRACEONLY  SET LINESIZE 1000  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      --普通索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE  INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      --唯一索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  UPDATE T_20170704_COUNT_LHR_01 T SET T.OBJECT_NAME=T.OBJECT_NAME||ROWNUM;  CREATE unique INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      --常數索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(0);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      --常數壓縮索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(0) COMPRESS;  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;          --位圖索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  UPDATE T_20170704_COUNT_LHR_01  SET OBJECT_NAME='lhr';  UPDATE T_20170704_COUNT_LHR_01 SET OBJECT_NAME='ldd' WHERE ROWNUM<=20000;  CREATE BITMAP INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  ALTER INDEX IDX_OBJECT_NAME PARALLEL 8;  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      -- 物化視圖  DROP MATERIALIZED VIEW MV_COUNT_T_LHR;  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  UPDATE T_20170704_COUNT_LHR_01  SET OBJECT_NAME='lhr';  UPDATE T_20170704_COUNT_LHR_01 SET OBJECT_NAME='ldd' WHERE ROWNUM<=20000;  CREATE  MATERIALIZED VIEW  MV_COUNT_T_LHR  BUILD IMMEDIATE  REFRESH ON COMMIT  ENABLE QUERY REWRITE  AS  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  SET AUTOTRACE TRACEONLY  SET LINESIZE 1000  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      --緩存結果集  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  SET LINESIZE 1000  SET AUTOTRACE TRACEONLY  SELECT /*+ RESULT_CACHE */ COUNT(*) FROM T_20170704_COUNT_LHR_01;      --業務分析  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01 WHERE ROWNUM=1;  
一、 普通表(無索引)
DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;    LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;      COUNT(*)  ----------       79300    LHR@orclasm > SET AUTOTRACE TRACEONLY  LHR@orclasm > SET LINESIZE 1000  LHR@orclasm > SET TIMING ON  LHR@orclasm > LHR@orclasm > LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.01    Execution Plan  ----------------------------------------------------------  Plan hash value: 1395805058    --------------------------------------------------------------------------------------  | Id  | Operation          | Name                    | Rows  | Cost (%CPU)| Time     |  --------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT   |                         |     1 |   317   (1)| 00:00:04 |  |   1 |  SORT AGGREGATE    |                         |     1 |            |          |  |   2 |   TABLE ACCESS FULL| T_20170704_COUNT_LHR_01 | 91429 |   317   (1)| 00:00:04 |  --------------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets         1139  consistent gets            0  physical reads            0  redo size          527  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  
二、 普通索引
DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE  INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON    LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.20    Execution Plan  ----------------------------------------------------------  Plan hash value: 1178070731    ---------------------------------------------------------------------------------  | Id  | Operation             | Name            | Rows  | Cost (%CPU)| Time     |  ---------------------------------------------------------------------------------  |   0 | SELECT STATEMENT      |                 |     1 |   114   (1)| 00:00:02 |  |   1 |  SORT AGGREGATE       |                 |     1 |            |          |  |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_NAME | 91429 |   114   (1)| 00:00:02 |  ---------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets          400  consistent gets            0  physical reads            0  redo size          527  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  
--1、主鍵索引(唯一索引)  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE UNIQUE INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    --2、常數索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(0);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.01    Execution Plan  ----------------------------------------------------------  Plan hash value: 1178070731    ---------------------------------------------------------------------------------  | Id  | Operation             | Name            | Rows  | Cost (%CPU)| Time     |  ---------------------------------------------------------------------------------  |   0 | SELECT STATEMENT      |                 |     1 |    45   (3)| 00:00:01 |  |   1 |  SORT AGGREGATE       |                 |     1 |            |          |  |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_NAME | 86597 |    45   (3)| 00:00:01 |  ---------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets          151  consistent gets            0  physical reads            0  redo size          528  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed      --3、常數壓縮索引  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  ALTER TABLE T_20170704_COUNT_LHR_01 MODIFY OBJECT_NAME NOT NULL;  CREATE INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(0) COMPRESS;  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  LHR@orclasm > LHR@orclasm > LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.00    Execution Plan  ----------------------------------------------------------  Plan hash value: 1178070731    ---------------------------------------------------------------------------------  | Id  | Operation             | Name            | Rows  | Cost (%CPU)| Time     |  ---------------------------------------------------------------------------------  |   0 | SELECT STATEMENT      |                 |     1 |    38   (0)| 00:00:01 |  |   1 |  SORT AGGREGATE       |                 |     1 |            |          |  |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_NAME | 86597 |    38   (0)| 00:00:01 |  ---------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets          129  consistent gets            0  physical reads            0  redo size          528  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  
三、 位圖索引

試驗如下:

DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  UPDATE T_20170704_COUNT_LHR_01  SET OBJECT_NAME='lhr';  UPDATE T_20170704_COUNT_LHR_01 SET OBJECT_NAME='ldd' WHERE ROWNUM<=20000;  CREATE BITMAP INDEX IDX_OBJECT_NAME ON T_20170704_COUNT_LHR_01(OBJECT_NAME);  SET AUTOTRACE TRACEONLY  SET TIMING ON  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.00    Execution Plan  ----------------------------------------------------------  Plan hash value: 1696023018    -----------------------------------------------------------------------------------------  | Id  | Operation                     | Name            | Rows  | Cost (%CPU)| Time     |  -----------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT              |                 |     1 |     5   (0)| 00:00:01 |  |   1 |  SORT AGGREGATE               |                 |     1 |            |          |  |   2 |   BITMAP CONVERSION COUNT     |                 | 91429 |     5   (0)| 00:00:01 |  |   3 |    BITMAP INDEX FAST FULL SCAN| IDX_OBJECT_NAME |       |            |          |  -----------------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets            5  consistent gets            0  physical reads            0  redo size          527  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  
1、 位圖索引+並行
ALTER INDEX IDX_OBJECT_NAME PARALLEL 8;    SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  

並行技術可以加快執行速度,但一致性讀有所增加,但並行還是能加快整體運行速度。

四、 物化視圖

這主要是應用在數據庫更新不是非常頻繁場景,用的是空間換時間。

DROP MATERIALIZED VIEW MV_COUNT_T_LHR;  DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  UPDATE T_20170704_COUNT_LHR_01  SET OBJECT_NAME='lhr';  UPDATE T_20170704_COUNT_LHR_01 SET OBJECT_NAME='ldd' WHERE ROWNUM<=20000;  CREATE  MATERIALIZED VIEW  MV_COUNT_T_LHR  BUILD IMMEDIATE  REFRESH ON COMMIT  ENABLE QUERY REWRITE  AS  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  SET AUTOTRACE TRACEONLY  SET LINESIZE 1000  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  LHR@orclasm > SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.08    Execution Plan  ----------------------------------------------------------  Plan hash value: 571421573    -----------------------------------------------------------------------------------------------  | Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |  -----------------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT             |                |     1 |    13 |     3   (0)| 00:00:01 |  |   1 |  MAT_VIEW REWRITE ACCESS FULL| MV_COUNT_T_LHR |     1 |    13 |     3   (0)| 00:00:01 |  -----------------------------------------------------------------------------------------------    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets            3  consistent gets            0  physical reads            0  redo size          531  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  
五、 緩存結果

在Oracle 11g中提供了結果集緩存特性。該緩存是在共享內存中存儲全部的結果集。如果一個查詢SQL被執行,且它對應的結果集在緩存中,那麼,該SQL的幾乎全部開銷都可以避免。

DROP TABLE T_20170704_COUNT_LHR_01 PURGE;  CREATE TABLE T_20170704_COUNT_LHR_01 AS  SELECT * FROM DBA_OBJECTS;  SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01;  SET LINESIZE 1000  SET AUTOTRACE TRACEONLY  SELECT /*+ RESULT_CACHE */ COUNT(*) FROM T_20170704_COUNT_LHR_01;  LHR@orclasm > LHR@orclasm > SELECT /*+ RESULT_CACHE */ COUNT(*) FROM T_20170704_COUNT_LHR_01;    Elapsed: 00:00:00.00    Execution Plan  ----------------------------------------------------------  Plan hash value: 1395805058    ------------------------------------------------------------------------------------------  | Id  | Operation           | Name                       | Rows  | Cost (%CPU)| Time     |  ------------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT    |                            |     1 |   317   (1)| 00:00:04 |  |   1 |  RESULT CACHE       | 6pp2f468gdjnj9v3s3mfwffd7t |       |            |          |  |   2 |   SORT AGGREGATE    |                            |     1 |            |          |  |   3 |    TABLE ACCESS FULL| T_20170704_COUNT_LHR_01    | 86597 |   317   (1)| 00:00:04 |  ------------------------------------------------------------------------------------------    Result Cache Information (identified by operation id):  ------------------------------------------------------       1 - column-count=1; dependencies=(LHR.T_20170704_COUNT_LHR_01); attributes=(single-row); name="SELECT /*+ RESULT_CACHE */ COUNT(*) FROM T_20170704_COUNT_LHR_01"    Note  -----     - dynamic sampling used for this statement (level=2)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets            0  consistent gets            0  physical reads            0  redo size          528  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed    
六、 根據業務規則判斷

若統計行數只是為了判斷表中是否有記錄,則可以使用ROWNUM=1,所以改寫後的SQL變為:

SELECT COUNT(*) FROM T_20170704_COUNT_LHR_01 WHERE ROWNUM=1;  

該SQL無論表中數據多大,性能都不會太差。

八、 分析需求

仔細分析需求後,可能會發現,統計行數這條SQL根本就是多餘的,那麼這條SQL語句就可以直接砍掉了。

& 說明:

有關COUNT的更多內容可以參考我的BLOG:http://blog.itpub.net/26736162/viewspace-2141601/

本文選自《Oracle程序員面試筆試寶典》,作者:李華榮。