IT Share you

Oracle에서 SELECT COUNT (*) FROM sometable에 대한 더 빠른 대안

shareyou 2020. 12. 6. 22:19
반응형

Oracle에서 SELECT COUNT (*) FROM sometable에 대한 더 빠른 대안


Oracle에서는 쿼리가

SELECT COUNT(*) FROM sometable;

큰 테이블의 경우 매우 느립니다. 실제로 모든 행을 통과하고 한 번에 하나씩 카운터를 증가시키는 데이터베이스처럼 보입니다. 테이블 어딘가에 테이블에 행이 몇 개 있는지 카운터가 있다고 생각합니다.

따라서 Oracle에서 테이블의 행 수를 확인하려면 가장 빠른 방법은 무엇입니까?


생각해보십시오. 데이터베이스는이를 수행하기 위해 모든 행으로 이동해야합니다. 다중 사용자 환경 COUNT(*)에서 내COUNT(*) . 각 세션마다 다른 카운터를 사용하는 것은 비현실적이므로 문자 그대로 행을 계산해야합니다. 어쨌든 대부분의 경우 쿼리에 WHERE 절 또는 JOIN이 있으므로 가상 카운터는 실용적인 가치가 있습니다.

그러나 속도를 높이는 방법이 있습니다. NOT NULL 열에 INDEX가있는 경우 Oracle은 테이블 대신 인덱스의 행을 계산합니다. 적절한 관계형 모델에서는 모든 테이블에 기본 키가 있으므로는 기본 키 COUNT(*)의 인덱스를 사용합니다.

비트 맵 인덱스에는 NULL 행에 대한 항목이 있으므로 COUNT (*)는 사용 가능한 경우 비트 맵 인덱스를 사용합니다.


대략적인 추정치를 원하면 샘플에서 외삽 할 수 있습니다.

SELECT COUNT(*) * 100 FROM sometable SAMPLE (1);

더 빠른 속도 (낮은 정확도)를 위해 샘플 크기를 줄일 수 있습니다.

SELECT COUNT(*) * 1000 FROM sometable SAMPLE (0.1);

더 빠른 속도 (더 낮은 정확도)를 위해 블록 단위 샘플링을 사용할 수 있습니다.

SELECT COUNT(*) * 100 FROM sometable SAMPLE BLOCK (1);


이것은 큰 테이블에 적합합니다.

SELECT NUM_ROWS FROM ALL_TABLES WHERE TABLE_NAME = 'TABLE_NAME_IN_UPPERCASE';

중소형 테이블의 경우 다음과 같은 것이 좋습니다.

SELECT COUNT(Primary_Key) FROM table_name;

건배,


테이블에 NOT NULL 열에 대한 인덱스가 있으면 COUNT (*)가이를 사용합니다. 그렇지 않으면 전체 테이블 스캔을 실행합니다. 인덱스는 UNIQUE 일 필요는 없으며 NOT NULL이어야합니다.

여기 테이블이 있습니다 ...

SQL> desc big23
 Name                                      Null?    Type
 ----------------------------------------- -------- ---------------------------
 PK_COL                                    NOT NULL NUMBER
 COL_1                                              VARCHAR2(30)
 COL_2                                              VARCHAR2(30)
 COL_3                                              NUMBER
 COL_4                                              DATE
 COL_5                                              NUMBER
 NAME                                               VARCHAR2(10)

SQL>

먼저 인덱스없이 카운트를 할 것입니다 ....

SQL> explain plan for
  2      select count(*) from big23
  3  /

Explained.

SQL> select * from table(dbms_xplan.display)
  2  /
select * from table)dbms_xplan.display)

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
Plan hash value: 983596667

--------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |  1618   (1)| 00:00:20 |
|   1 |  SORT AGGREGATE    |       |     1 |            |          |
|   2 |   TABLE ACCESS FULL| BIG23 |   472K|  1618   (1)| 00:00:20 |
--------------------------------------------------------------------

Note

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
   - dynamic sampling used for this statement

13 rows selected.

SQL>

아니요, NULL 항목을 포함 할 수있는 열에 인덱스를 만듭니다.

SQL> create index i23 on big23(col_5)
  2  /

Index created.

SQL> delete from plan_table
  2  /

3 rows deleted.

SQL> explain plan for
  2      select count(*) from big23
  3  /

Explained.

SQL> select * from table(dbms_xplan.display)
  2  /

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
Plan hash value: 983596667

--------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |  1618   (1)| 00:00:20 |
|   1 |  SORT AGGREGATE    |       |     1 |            |          |
|   2 |   TABLE ACCESS FULL| BIG23 |   472K|  1618   (1)| 00:00:20 |
--------------------------------------------------------------------

Note

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
   - dynamic sampling used for this statement

13 rows selected.

SQL>

마지막으로 NOT NULL 열에 인덱스를 작성해 보겠습니다.

SQL> drop index i23
  2  /

Index dropped.

SQL> create index i23 on big23(pk_col)
  2  /

Index created.

SQL> delete from plan_table
  2  /

3 rows deleted.

SQL> explain plan for
  2      select count(*) from big23
  3  /

Explained.

SQL> select * from table(dbms_xplan.display)
  2  /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------
Plan hash value: 1352920814

----------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |     1 |   326   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE       |      |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| I23  |   472K|   326   (1)| 00:00:04 |
----------------------------------------------------------------------

Note

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------
   - dynamic sampling used for this statement

13 rows selected.

SQL>

옵션 1 : 스캔에 사용할 수있는 널이 아닌 열에 대한 인덱스가 있습니다. 또는 다음과 같이 함수 기반 인덱스를 만듭니다.

create index idx on t(0);

this can then be scanned to give the count.

Option 2: If you have monitoring turned on then check the monitoring view USER_TAB_MODIFICATIONS and add/subtract the relevant values to the table statistics.

Option 3: For a quick estimate on large tables invoke the SAMPLE clause ... for example ...

SELECT 1000*COUNT(*) FROM sometable SAMPLE(0.1); 

Option 4: Use a materialized view to maintain the count(*). Powerful medicine though.

um ...


You can create a fast refresh materialized view to store the count.

Example:

create table sometable (
id number(10) not null primary key
, name varchar2(100) not null);

create materialized view log on sometable with rowid including new values;

create materialized view sometable_count
refresh on commit
as
select count(*) count
from   sometable;

insert into sometable values (1,'Raymond');
insert into sometable values (2,'Hans');

commit;

select count from sometable_count; 

It will slow mutations on table sometable a bit but the counting will become a lot faster.


The fastest way to get a count of a table is exactly what you did. There are no tricks you can do that Oracle doesn't already know about.

There are somethings you have not told us. Namely why do you think think this should be faster?

For example:

  1. Have you at least done an explain plan to see what Oracle is doing?
  2. How many rows are there in this table?
  3. What version of Oracle are you using? 8,9,10,11 ... 7?
  4. Have you ever run database statistics on this table?
  5. Is this a frequently updated table or batch loaded or just static data?
  6. Is this the only slow COUNT(*) you have?
  7. How long does SELECT COUNT(*) FROM Dual take?

I'll admit I wouldn't be happy with 41 seconds but really WHY do you think it should be faster? If you tell us the table has 18 billion rows and is running on the laptop you bought from a garage sale in 2001, 41 seconds is probably not that far outside "good as it will get" unless you get better hardware. However if you say you are on Oracle 9 and you ran statistics last summer well you'll probably get a different suggestions.


There was a relevant answer from Ask Tom published in April 2016.

If you have sufficient server power, you can do

select /*+ parallel */ count(*) from sometable

If you are just after an approximation, you can do :

select 5 * count(*) from sometable sample block (10);

Also, if there is

  1. a column that contains no nulls, but is not defined as NOT NULL, and
  2. there is an index on that column

you could try:

select /*+ index_ffs(t) */ count(*) from sometable  t where indexed_col is not null

You can have better performance by using the following method:

SELECT COUNT(1) FROM (SELECT /*+FIRST_ROWS*/ column_name 
FROM table_name 
WHERE column_name = 'xxxxx' AND ROWNUM = 1);

You could use COUNT(1) instead

참고URL : https://stackoverflow.com/questions/1840538/faster-alternative-in-oracle-to-select-count-from-sometable

반응형