2009-12-03 108 views
48

我注意到在Oracle中,查詢更快的替代SELECT COUNT(*)FROM sometable

SELECT COUNT(*) FROM sometable; 

是大表非常慢。它看起來像數據庫實際上遍歷每一行,並一次遞增一個計數器。我認爲在表格的某個位置會有一個計數器,表中有多少行。

因此,如果我想檢查Oracle中表中的行數,那麼最快的方法是什麼?

+2

的目的是什麼,你檢查的行數?這是你特設的東西,例如在SQLDeveloper或SQL * PLUS中用於您自己的目的,或者這將成爲程序的一部分,例如獲得行數,以便您可以顯示「345頁的第1頁」或其他內容?你需要確切的數量,還是近似工作? – shoover 2009-12-03 19:06:57

+5

順便說一句,我想如果你只是需要一個近似值,這是你臨時做的事情,那麼如果你最近分析了你的表,你可以選擇NUM_ROWS FROM USER_TABLES WHERE TABLE_NAME ='SOME_TABLE'; – shoover 2009-12-03 19:29:14

+0

謝謝,那正是我所需要的,因爲正如你所猜測的,我通常只是在尋找一個近似值。 – 2010-01-07 22:18:40

回答

25

想一想:數據庫真的必須去每一行來做到這一點。 在多用戶環境中,我的COUNT(*)可能與您的COUNT(*)不同。爲每一個會話都設置一個不同的計數器是不切實際的,所以你可以從字面上計算行數。無論如何,大多數情況下,您的查詢中都會有一個WHERE子句或一個JOIN,因此您的假設計數器將具有較低的實用價值。

然而,有些方法可以加快速度:如果您在NOT NULL列上有INDEX,Oracle將計算索引的行數而不是表的數量。在適當的關係模型中,所有表都有一個主鍵,因此COUNT(*)將使用主鍵的索引。

位圖索引具有空行的條目,所以如果有可用的COUNT(*)將使用位圖索引。

+0

我在這個表上的4個列上有一個主鍵,每個列都不是NULL,並且還需要41秒來完成一個COUNT(*)。該索引是否必須位於單列上才能快速生成COUNT(*)查詢? – 2009-12-03 15:50:28

+3

@Eli:索引鍵越小,查詢就越快。如果你的桌子很大,那麼COUNT(*)'無論如何都需要時間。在其他答案中看到一些對COUNT(*)的替代方法的建議(在一個實例化視圖中記錄結果,在一個樣本上記下COUNT,...) – 2009-12-03 16:13:30

+0

爲了記錄,我看到了一個簡單的SELECT(*)多達40分鐘。在具有4個非空數字列和844 mio主鍵的表上。條目。 – 2017-02-13 16:54:17

13

如果表在NOT NULL列上有索引,COUNT(*)將使用該索引。否則它執行全表掃描。請注意,索引不一定是UNIQUE,它只是非空。

下面是一張桌子

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> 
7

選項1:對可用於掃描的非空列存在索引。或者創建一個基於函數的索引:

create index idx on t(0); 

然後可以掃描這個計數。選項2:如果您啓用了監控,請檢查監控視圖USER_TAB_MODIFICATIONS並將相關值添加/減去表格統計信息。

方案3:有關大表可以快速估計調用樣本條款......比如......

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

選項4:使用物化視圖保持COUNT(*)。雖然功能強大。

...

5

您可以創建快速刷新物化視圖來存儲計數。

例子:

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; 

它會減慢對錶突變sometable了一點,但計數會變得快了很多。

50

如果你想只是一個粗略的估計,你可以從一個樣本推斷:

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

得到更快的速度(但精度較低),可以減少試樣尺寸:

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

對於更高的速度(但更差的精度),您可以使用塊式採樣:

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

+0

是否可以在子查詢上做到這一點? – uesports135 2016-05-02 17:05:24

+4

不,不幸的是,SAMPLE子句僅適用於基表。 – 2016-05-03 12:14:46

+0

你能想到任何其他的方式來實現一個子查詢類似的概念? – uesports135 2016-05-03 15:09:02

3

獲得一張表的最快方法正是你所做的。 Oracle不知道你可以做什麼。

有些東西你沒有告訴我們。即爲什麼你認爲這應該更快?

例如:

  1. 你是否至少做了解釋計劃,看看甲骨文是幹什麼的?
  2. 此表中有多少行?
  3. 您使用的是哪個版本的Oracle? 8,9,10,11 ... 7?
  4. 你有沒有在這個表上運行數據庫統計?
  5. 這是一個頻繁更新的表或批處理或只是靜態數據?
  6. 這是唯一很慢的COUNT(*)嗎?
  7. SELECT COUNT(*)FROM Dual需要多長時間?

我承認我不會快樂41秒,但真的,爲什麼你認爲它應該是更快?如果你告訴我們這張桌子有180億行,並且正在2001年從一個車庫出售中購買的筆記本電腦上運行,那麼除非你有更好的硬件,否則41秒可能不會那麼遠。但是如果你說你在Oracle 9上,並且你在去年夏天運行統計,那麼你可能會得到不同的建議。

+0

其他答案指出還有其他更快的方法來獲取行數。包括甲骨文內部的可疑計數器。只是不如計數(*)準確。 – 2015-11-06 19:45:39

37

這對於大型表格非常適用。

SELECT NUM_ROWS FROM ALL_TABLES WHERE TABLE_NAME = 'TABLE_NAME_IN_UPPERCASE'; 

對於中小尺寸的桌子,以下是可以的。

SELECT COUNT(Primary_Key) FROM table_name; 

乾杯,

+6

確保'table_name'的格式是''TABLE_NAME'' – imanuelcostigan 2013-02-15 02:08:10

+10

這不會實時更新,根據您的數據庫不同,可能根本不會更新。 http://docs.oracle.com/cd/B28359_01/server.111/b28320/statviews_2105.htm#REFRN20286 – Joseph 2013-12-10 18:19:48

-4

你可以使用COUNT(1),而不是

+0

請注意與此[刪除的答案](http://stackoverflow.com/a/10335036/)相關的註釋(需要10K代表才能看到它)。註釋說'COUNT(1)'和'COUNT(*)'被視爲相同 - 在任何SQL數據庫中通常都是如此。這是一個「城市神話」,有差異(或者如果DBMS存在差異,DBMS在其優化器中有嚴格的監督;應該沒有區別)。 – 2016-09-06 06:37:20

1

有來自Ask Tom相關答案刊登在2016年4月。

如果你有足夠的服務器電源,你可以做

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

如果你只是一個近似值後,你可以這樣做:

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

而且,如果有

  1. 不包含空值但未定義爲NOT NULL的列,以及
  2. 有該列

索引,你可以嘗試:

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

另外,您在逐字引用「詢問湯姆」的答案時,並未明確表示您正在這樣做。這並不是真正遵循SO的規則,至少需要這樣的確認 - 比您的原始版本的答案更明確,甚至比我在查看Ask Tom頁面之前編輯的版本更加明確。 – 2016-09-06 05:02:50

+1

好的;我修改了頁面,所以很明顯這基本上是Ask Tom答案的逐字拷貝。現在剩下的問題給你 - [m.r226](http://stackoverflow.com/users/5516527/m-r226) - 你是否真的有權制作答案的副本並重新發布?如果不是,那麼你違反了版權法,這對於SO來說是有問題的。 – 2016-09-06 05:13:15

+0

這裏的大量信息是由[Jeffrey Kemp](http://stackoverflow.com/users/103295/jeffrey-kemp)在他的[答案](http://stackoverflow.com/a/1844985)中提供的/ 15168)from 2009. – 2016-09-06 05:15:56

相關問題