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?
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.
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);
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é,
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>
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 ...
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.
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:
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.
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
- une colonne qui ne contient pas de null, mais n'est pas définie comme NOT NULL, et
- 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
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);