web-dev-qa-db-fra.com

Alternative plus rapide dans Oracle à SELECT COUNT (*) FROM sometable

J'ai remarqué que dans Oracle, la requête

SELECT COUNT(*) FROM sometable;

est très lent pour les grandes tables. Il semble que la base de données parcourt chaque ligne et incrémente un compteur un à la fois. Je penserais qu'il y aurait un compteur quelque part dans la table le nombre de lignes de cette table.

Donc, si je veux vérifier le nombre de lignes dans une table dans Oracle, quel est le moyen le plus rapide de le faire?

52
Eli Courtwright

Pensez-y: la base de données doit vraiment aller à tous les rangs pour le faire. Dans un environnement multi-utilisateur, ma COUNT(*) pourrait être différente de votre COUNT(*). Il ne serait pas pratique d’avoir un compteur différent pour chaque session, vous devez donc littéralement compter les lignes. La plupart du temps, de toute façon, vous auriez une clause WHERE ou une jointure dans votre requête afin que votre compteur hypothétique ait une valeur pratique moindre.

Il existe toutefois des moyens d’accélérer les choses: si vous avez un index sur une colonne non nulle, Oracle comptera les lignes de l’index au lieu de la table. Dans un modèle relationnel approprié, toutes les tables ont une clé primaire afin que COUNT(*) utilise l'index de la clé primaire.

Les index bitmap ont des entrées pour les lignes NULL, donc COUNT (*) utilisera un index bitmap s'il en existe un.

27
Vincent Malgrat

Si vous voulez juste une estimation approximative, vous pouvez extrapoler à partir d'un échantillon:

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

Pour plus de rapidité (mais une précision moindre), vous pouvez réduire la taille de l'échantillon:

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

Pour une vitesse encore plus grande (mais une précision encore pire), vous pouvez utiliser l'échantillonnage par blocs:

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

55
Jeffrey Kemp

Cela fonctionne très bien pour les grandes tables.

SELECT NUM_ROWS FROM ALL_TABLES WHERE TABLE_NAME = 'TABLE_NAME_IN_UPPERCASE';

Pour les tables de taille petite à moyenne, cela va suivre.

SELECT COUNT(Primary_Key) FROM table_name;

À votre santé,

43
AMISH G SHAH

Si la table a un index sur une colonne NOT NULL, COUNT (*) l'utilisera. Sinon, il exécute une analyse complète de la table. Notez que l'index n'a pas besoin d'être UNIQUE, il doit simplement être NOT NULL.

Voici une table ...

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>

D'abord, nous allons faire un décompte sans index ....

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>

Non, nous créons un index sur une colonne pouvant contenir des entrées 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>

Enfin, construisons l'index sur la colonne 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>
13
APC

Option 1: créer un index sur une colonne non NULL pouvant être utilisé pour l'analyse. Ou créez un index basé sur une fonction comme:

create index idx on t(0);

cela peut ensuite être numérisé pour donner le compte.

Option 2: Si la surveillance est activée, vérifiez la vue de surveillance USER_TAB_MODIFICATIONS et ajoutez/soustrayez les valeurs appropriées aux statistiques de la table.

Option 3: pour une estimation rapide sur de grandes tables, appelez la clause SAMPLE ... par exemple ...

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

Option 4: Utilisez une vue matérialisée pour conserver le nombre (*). Un médicament puissant cependant.

euh ...

7
David Aldridge

Vous pouvez créer une vue matérialisée à rafraîchissement rapide pour stocker le nombre.

Exemple:

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; 

Cela ralentira un peu les mutations sur la table mais le comptage deviendra beaucoup plus rapide. 

5
tuinstoel

Le moyen le plus rapide d’obtenir le décompte d’une table est exactement ce que vous avez fait. Vous ne pouvez faire aucune astuce que Oracle ne connaît pas déjà.

Il y a des choses que vous ne nous avez pas dites. Pourquoi pensez-vous que cela devrait être plus rapide?

Par exemple:

  1. Avez-vous au moins élaboré un plan pour voir ce que fait Oracle?
  2. Combien de lignes y a-t-il dans cette table?
  3. Quelle version d'Oracle utilisez-vous? 8,9,10,11 ... 7?
  4. Avez-vous déjà exécuté des statistiques de base de données sur cette table?
  5. Est-ce une table fréquemment mise à jour ou un lot chargé ou juste des données statiques?
  6. Est-ce le seul compte lent que vous avez (*)?
  7. Combien de temps dure SELECT COUNT (*) FROM Dual?

J'admets que je ne serais pas heureux avec 41 secondes mais vraiment POURQUOI pensez-vous que cela devrait être plus rapide? Si vous nous dites que la table a 18 milliards de lignes et tourne sur l'ordinateur portable que vous avez acheté dans une vente de garage en 2001, 41 secondes ne sont probablement pas si éloignées de la distance "de qualité, car elles auront", à moins d'obtenir un meilleur matériel. Cependant, si vous dites que vous utilisez Oracle 9 et que vous avez publié des statistiques l'été dernier, vous obtiendrez probablement des suggestions différentes.

3
David

Il y avait une réponse pertinente de Ask Tom publiée en avril 2016.

Si vous avez suffisamment de puissance de serveur, vous pouvez faire

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

Si vous êtes juste après une approximation, vous pouvez faire:

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

Aussi, s'il y a

  1. une colonne qui ne contient pas de null, mais n'est pas définie comme NOT NULL, et
  2. il y a un index sur cette colonne

tu pourrais essayer:

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

Vous pouvez améliorer les performances en utilisant la méthode suivante:

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