代码之家  ›  专栏  ›  技术社区  ›  Eli Courtwright

在Oracle中,从某个表中选择count(*)的速度更快

  •  52
  • Eli Courtwright  · 技术社区  · 16 年前

    我注意到在Oracle中

    SELECT COUNT(*) FROM sometable;
    

    对于大桌子来说很慢。它看起来像是一个数据库,它实际上在每一行中遍历,一次递增一个计数器。我想在表的某个地方会有一个计数器,这个表有多少行。

    所以,如果我想检查Oracle中表中的行数,最快的方法是什么?

    10 回复  |  直到 16 年前
        1
  •  27
  •   jdthood ewall    12 年前

    想想看:数据库真的必须转到每一行才能做到这一点。 在多用户环境中 COUNT(*) 可能和你的不同 伯爵(*) . 每一个会话都有一个不同的计数器是不切实际的,因此您必须逐字计算行数。不管怎样,大多数情况下,查询中都会有一个WHERE子句或JOIN,这样您的假设计数器就没有什么实际价值。

    但是,有一些方法可以加快速度:如果在非空列上有索引,Oracle将计算索引的行而不是表的行。在适当的关系模型中,所有表都有主键,因此 伯爵(*) 将使用主键的索引。

    位图索引具有空行的条目,因此如果有位图索引,则计数(*)将使用该索引。

        2
  •  52
  •   Jeffrey Kemp    16 年前

    如果您只需要一个粗略的估计,您可以从一个样本中进行外推:

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

    为了提高速度(但降低精度),可以减小样本大小:

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

    对于更高的速度(但精度更差),可以使用逐块采样:

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

        3
  •  43
  •   rationalboss    9 年前

    这对大桌子很有用。

    SELECT NUM_ROWS FROM ALL_TABLES WHERE TABLE_NAME = 'TABLE_NAME_IN_UPPERCASE';
    

    对于中小型桌子,以下内容可以。

    SELECT COUNT(Primary_Key) FROM table_name;
    

    干杯,

        4
  •  13
  •   APC    16 年前

    如果表的非空列上有索引,则计数(*)将使用该索引。否则,它将执行全表扫描。请注意,索引不必是唯一的,它必须不为空。

    这是一张桌子…

    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>
    

    不,我们在可以包含空项的列上创建索引…

    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>
    

    最后,让我们在非空列上构建索引….

    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>
    
        5
  •  7
  •   David Aldridge    16 年前

    选项1:在非空列上有一个索引,该索引可用于扫描。或创建基于函数的索引,如下所示:

    create index idx on t(0);
    

    然后可以对其进行扫描以给出计数。

    选项2:如果您打开了监控,则检查监控视图用户选项卡的修改,并将相关值添加/减去到表统计中。

    选项3:要快速估计大表,请调用SAMPLE子句…例如。。。

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

    选项4:使用物化视图维护计数(*)。但是药效很强。

    嗯。。。

        6
  •  5
  •   tuinstoel    16 年前

    您可以创建一个快速刷新物化视图来存储计数。

    例子:

    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; 
    

    它会稍微减缓某些表上的突变,但计数会变得更快。

        7
  •  3
  •   David    16 年前

    计算一张桌子的最快方法就是你所做的。没有什么技巧是你能做到的,Oracle还不知道。

    有些事情你没有告诉我们。也就是说,你为什么认为这应该更快?

    例如:

    1. 你是否至少做了一个解释计划来看看Oracle在做什么?
    2. 这张表有几行?
    3. 您使用的是什么版本的Oracle?8、9、10、11…7岁?
    4. 您是否曾在此表上运行过数据库统计信息?
    5. 这是一个经常更新的表还是批量加载的,还是仅仅是静态数据?
    6. 这是你唯一的慢计数吗?
    7. 从Dual Take中选择Count(*)需要多长时间?

    我承认我对41秒并不满意,但你真的认为它应该更快吗?如果你告诉我们这张桌子有180亿行,并且是在你2001年从车库里买来的笔记本电脑上运行的,那么41秒可能就不远了,除非你有更好的硬件。但是,如果你说你在Oracle9上,并且去年夏天运行了统计数据,你可能会得到不同的建议。

        8
  •  1
  •   Rezaeimh7    9 年前

    有一个相关的答案来自 Ask Tom 2016年4月出版。

    如果您有足够的服务器电源,可以

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

    如果你只是在做一个近似,你可以做:

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

    还有,如果有的话

    1. 不包含空值但未定义为非空值的列,以及
    2. 该列上有索引

    你可以试试:

    select /*+ index_ffs(t) */ count(*) from sometable  t where indexed_col is not null
    
        9
  •  0
  •   Peter Reynier    7 年前

    您可以使用以下方法获得更好的性能:

    SELECT COUNT(1) FROM (SELECT /*+FIRST_ROWS*/ column_name 
    FROM table_name 
    WHERE column_name = 'xxxxx' AND ROWNUM = 1);
    
        10
  •  -4
  •   11g    11 年前

    你可以用计数(1)代替