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 firebase database traverse chaque ligne et incrémente un compteur à la fois. Je pense qu’il y aurait un compteur quelque part dans la table, combien de lignes cette table a.

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

Pensez-y: la firebase database doit vraiment aller à chaque ligne pour le faire. Dans un environnement multi-utilisateur, mon COUNT(*) peut être différent de votre COUNT(*) . Il ne serait pas pratique d’avoir un compteur différent pour chaque session afin de littéralement compter les lignes. La plupart du temps, de toute façon, vous auriez une clause WHERE ou un JOIN dans votre requête afin que votre compteur hypothétique ait une valeur pratique.

Il existe cependant des moyens d’accélérer les choses: si vous avez un INDEX sur une colonne NOT NULL, 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, de sorte que COUNT(*) utilise l’index de la clé primaire.

L’index bitmap a des entrées pour les lignes NULL, donc un 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 moins de précision), vous pouvez réduire la taille de l’échantillon:

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

Pour une vitesse encore supérieure (mais une précision encore pire), vous pouvez utiliser un é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, les opérations suivantes seront possibles.

 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 parsing complète de la table. Notez que l’index ne doit pas ê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> 

Nous allons d’abord 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: Avoir un index sur une colonne non nulle qui peut être utilisé pour l’parsing. Ou créez un index basé sur une fonction en tant que:

 create index idx on t(0); 

cela peut alors être scanné 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 pertinentes aux statistiques de la table.

Option 3: Pour une estimation rapide sur les 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 compte (*). Médecine puissante 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 de compter un tableau est exactement ce que vous avez fait. Il n’y a pas d’astuces que Oracle ne connaisse 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 fait un plan d’explication 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 firebase database sur cette table?
  5. S’agit-il d’une table fréquemment mise à jour ou de données chargées par lots ou uniquement statiques?
  6. Est-ce le seul COUNT lent (*) que vous avez?
  7. Combien de temps SELECT COUNT (*) FROM Dual prend-il?

J’admets que je ne serais pas content avec 41 secondes mais vraiment pourquoi pensez-vous que ça devrait être plus rapide? Si vous nous dites que la table a 18 milliards de lignes et fonctionne sur l’ordinateur portable que vous avez acheté lors d’une vente de garage en 2001, 41 secondes n’est probablement pas si loin que vous obtiendrez un meilleur matériel. Cependant, si vous dites que vous utilisez Oracle 9 et que vous avez réalisé des statistiques l’été dernier, vous aurez probablement des suggestions différentes.

Ask Tom a publié une réponse pertinente en avril 2016.

Si vous avez suffisamment de puissance serveur, vous pouvez le 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 aucun NULL, mais qui 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 

Vous pouvez avoir de meilleures 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); 

Vous pouvez utiliser COUNT (1) à la place