web-dev-qa-db-fra.com

Le moyen le plus rapide de compter le nombre exact de lignes dans une très grande table?

Je suis tombé sur des articles qui déclarent que SELECT COUNT(*) FROM TABLE_NAME sera lent lorsque la table aura beaucoup de lignes et beaucoup de colonnes.

J'ai un tableau qui pourrait même contenir des milliards de lignes [il a environ 15 colonnes]. Existe-t-il un meilleur moyen d'obtenir le nombre EXACT du nombre de lignes d'une table?

Veuillez considérer les éléments suivants avant votre réponse:

  • Je cherche un fournisseur de base de données Solution indépendante. C’est bien si cela couvre Couvre MySQL, Oracle, MS SQL Server. Mais s’il existe vraiment non base de données solution indépendante du fournisseur alors je sera régler pour différentes solutions pour différents fournisseurs de base de données.

  • Je ne peux utiliser aucun autre outil externe. Je recherche principalement une solution basée sur SQL.

  • Je ne peux plus normaliser la conception de ma base de données Il est déjà en 3NF et de plus, beaucoup de code a déjà été écrit autour de celui-ci.

201
Swaranga Sarma

Réponse simple:

  • Solution indépendante du fournisseur de base de données = utiliser la norme = COUNT(*)
  • Il y a approximatif solutions SQL Server mais n'utilisez pas COUNT (*) = hors de portée

Remarques:

COUNT (1) = COUNT (*) = COUNT (PrimaryKey) juste au cas où

Modifier:

Exemple SQL Server (1,4 milliard de lignes, 12 colonnes)

SELECT COUNT(*) FROM MyBigtable WITH (NOLOCK)
-- NOLOCK here is for me only to let me test for this answer: no more, no less

1 points, 5:46 minutes, compte = 1 401 659 700

--Note, sp_spaceused uses this DMV
SELECT
   Total_Rows= SUM(st.row_count)
FROM
   sys.dm_db_partition_stats st
WHERE
    object_name(object_id) = 'MyBigtable' AND (index_id < 2)

2 courses, les deux moins de 1 seconde, compte = 1.401.659.670

Le second a moins de lignes = faux. Serait la même chose ou plus selon les écritures (les suppressions sont effectuées en dehors des heures ici) 

212
gbn

Le moyen le plus rapide de loin sur MySQL est:

SHOW TABLE STATUS;

Vous obtiendrez instantanément toutes vos tables avec le nombre de lignes (qui est le total) ainsi que de nombreuses informations supplémentaires si vous le souhaitez.

25
salbahra

Je suis tombé sur des articles indiquant que SELECT COUNT (*) FROM TABLE_NAME sera lent lorsque la table contient beaucoup de lignes et de colonnes.

Cela dépend de la base de données. Certains accélèrent les comptages, par exemple en gardant une trace du fait que les lignes sont vivantes ou non dans l'index, permettant à un index uniquement d'analyser le nombre de lignes. D'autres ne le font pas et, par conséquent, nécessitent de visiter toute la table et de compter les lignes en direct, un par un. Soit sera lent pour une grande table.

Notez que vous pouvez généralement extraire une bonne estimation à l'aide d'outils d'optimisation de requête, de statistiques de table, etc. Dans le cas de PostgreSQL, par exemple, vous pouvez analyser la sortie de explain count(*) from yourtable et obtenir une estimation raisonnable du nombre de lignes. Ce qui m'amène à votre deuxième question.

J'ai un tableau qui pourrait même contenir des milliards de lignes [il a environ 15 colonnes]. Existe-t-il un meilleur moyen d'obtenir le nombre exact du nombre de lignes d'une table?

Sérieusement? :-) Tu veux vraiment dire le exact nombre d'une table avec des milliards de lignes? Êtes-vous vraiment sûr? :-)

Si vous vraiment faites, vous pouvez garder une trace du total en utilisant des déclencheurs, mais gardez bien l’accès simultané et les blocages si vous le faites.

10

Vous pouvez essayer ceci sp_spaceused (Transact-SQL)

Affiche le nombre de lignes, disque espace réservé et espace disque utilisé par une table, une vue indexée ou un service File d'attente du broker dans la base de données actuelle, ou affiche l'espace disque réservé et utilisé par la base de données entière.

9
jams

Existe-t-il un meilleur moyen d'obtenir le nombre exact du nombre de lignes d'une table?

Pour répondre simplement à votre question, Non.

Si vous avez besoin d'un moyen indépendant du SGBD, le moyen le plus rapide sera toujours:

SELECT COUNT(*) FROM TableName

Certains fournisseurs de SGBD peuvent avoir des moyens plus rapides qui fonctionneront uniquement pour leurs systèmes. Certaines de ces options sont déjà affichées dans d'autres réponses.

COUNT(*) devrait de toute façon être optimisé par le SGBD (au moins toute DB digne de PROD), aussi, n'essayez pas de contourner leurs optimisations.

Sur une note de côté:
Je suis sûr que beaucoup de vos autres requêtes prennent également beaucoup de temps à cause de la taille de votre table. Les problèmes de performances doivent probablement être résolus en réfléchissant rapidement à la conception de votre schéma. Je me rends compte que vous avez dit que ce n’est pas une option à changer, mais il se peut que des requêtes de plus de 10 minutes ne soient pas non plus une option. 3ème NF n'est pas toujours la meilleure approche lorsque vous avez besoin de rapidité, et parfois les données peuvent être partitionnées dans plusieurs tables si les enregistrements ne n'ont pas à stocker simultanément. Quelque chose à quoi penser...

8
Jesse Webb

J'utilise 

select /*+ parallel(a) */  count(1) from table_name a;
5
Mainsh S

Je ne suis pas aussi expert que les autres qui ont répondu, mais un problème que je rencontrais avec une procédure que j'utilisais pour sélectionner une ligne aléatoire dans une table (sans pertinence), mais il me fallait connaître le nombre de lignes de ma table de référence calculer l'index aléatoire. Utiliser le travail traditionnel de comptage (*) ou de comptage (1), mais il me fallait parfois 2 secondes pour que ma requête soit exécutée. Donc au lieu de cela (pour ma table nommée 'tbl_HighOrder'), j'utilise:

Declare @max int

Select @max = Row_Count
From sys.dm_db_partition_stats
Where Object_Name(Object_Id) = 'tbl_HighOrder'

Cela fonctionne très bien et les temps de requête dans Management Studio sont nuls.

5
john rains

Eh bien, tard de 5 ans et incertain si cela aide:

J'essayais de compter le non. de lignes dans une table SQL Server avec MS SQL Server Management Studio et a rencontré une erreur de débordement, puis j'ai utilisé ce qui suit:

sélectionnez count_big (1) FROM [nombase]. [dbo]. [FactSampleValue];

Le résultat :

24296650578 rangées

4

Si l'édition SQL Server est 2005/2008, vous pouvez utiliser les DMV pour calculer le nombre de lignes dans une table:

-- Shows all user tables and row counts for the current database 
-- Remove is_ms_shipped = 0 check to include system objects 
-- i.index_id < 2 indicates clustered index (1) or hash table (0) 
SELECT o.name, 
 ddps.row_count 
FROM sys.indexes AS i 
 INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID 
 INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID 
 AND i.index_id = ddps.index_id 
WHERE i.index_id < 2 
 AND o.is_ms_shipped = 0 
ORDER BY o.NAME 

Sysindexes fonctionnera pour le moteur de base de données SQL Server 2000, mais il est vivement conseillé d'éviter de l'utiliser dans les éditions futures de SQL Server, car il pourrait être supprimé dans un proche avenir.

Exemple de code extrait de: Comment obtenir des comptages de lignes de table rapidement et sans douleur

4
Alireza Maddah

Je suis en retard à cette question, mais voici ce que vous pouvez faire avec MySQL (car j'utilise MySQL). Je partage mes observations ici:

1) SELECT COUNT(*) AS TOTAL_ROWS FROM <TABLE_NAME>

Résultat  
Nombre de lignes: 508534
Sortie console: Lignes concernées: 0 Lignes trouvées: 1 Avertissements: 0 Durée pour 1 requête: 0.125 sec.
Prend un certain temps pour une table avec un grand nombre de lignes, mais le nombre de lignes est très exact.

2) SHOW TABLE STATUS or SHOW TABLE STATUS WHERE NAME="<TABLE_NAME>"

Résultat  
Nombre de lignes: 511235
Sortie console: Lignes concernées: 0 Lignes trouvées: 1 Avertissements: 0 Durée pour 1 requête: 0.250 sec Résumé: Le nombre de lignes n'est pas exact.

3) SELECT * FROM information_schema.tables WHERE table_schema = DATABASE();

Résultat  
Nombre de lignes: 507806
Sortie console: Lignes concernées: 0 Lignes trouvées: 48 Avertissements: 0 Durée pour 1 requête: 1.701 sec.
Le nombre de lignes n'est pas exact.

Je ne suis pas un expert en bases de données ni en MySQL, mais j’ai constaté que pour les très grandes tables, vous pouvez utiliser l’option 2 ou 3 et avoir une «idée juste» du nombre de lignes présentes.

J'avais besoin de connaître le nombre de lignes pour afficher des statistiques sur l'interface utilisateur. Avec les requêtes ci-dessus, je savais que le nombre total de lignes comptait plus de 500 000. Je suis donc parvenu à afficher des statistiques telles que "Plus de 500 000 lignes" sans indiquer le nombre exact de lignes. 

Peut-être que je n'ai pas vraiment répondu à la question du PO, mais je partage ce que j'ai fait dans une situation où de telles statistiques étaient nécessaires. Dans mon cas, montrer les rangées approximatives était acceptable et donc ce qui précède a fonctionné pour moi.

3
sunitkatkar

Je ne pense pas qu'il existe une solution générale toujours plus rapide: certains SGBDR/versions ont une optimisation spécifique pour SELECT COUNT(*) qui utilise des options plus rapides alors que d'autres se contentent d'analyser des tables. Pour le second ensemble, vous devez vous rendre sur les sites de documentation/support, ce qui nécessitera probablement l'écriture d'une requête plus spécifique, généralement celle qui atteint un index.

MODIFIER:

Voici une idée qui pourrait fonctionner, selon votre schéma et votre distribution de données: avez-vous une colonne indexée qui référence une valeur croissante, un ID numérique croissant, par exemple, ou même un horodatage ou une date? Ensuite, en supposant que les suppressions ne se produisent pas, il devrait être possible de stocker le nombre jusqu'à une valeur récente (date d'hier, valeur d'identifiant la plus élevée à un point d'échantillonnage récent) et d'ajouter le nombre au-delà, ce qui devrait être résolu très rapidement dans l'index . Très dépendant des valeurs et des indices, bien sûr, mais applicable à pratiquement toutes les versions de SGBD.

3
Mike Woodhouse

Si insert trigger est trop coûteux à utiliser, mais qu'un delete trigger peut être utilisé et qu'il existe une auto-incrémentation id, après avoir compté une fois la table entière et en avoir mémorisé last-count et le last-counted-id,

puis chaque jour il suffit de compter id> last-counted-id, de l'ajouter à last-count et de stocker le nouveau last-counted-id.

Le déclencheur de suppression réduirait le dernier compte, si l'identifiant de l'enregistrement supprimé <= last-counted-id.

2
ToolmakerSteve

Ce n'est pas vraiment une solution indépendante du SGBD, mais au moins votre code client ne verra pas la différence ...

Créer une autre table T avec une seule ligne et un champ entier N1, et créez INSERT TRIGGER qui exécute simplement:

UPDATE T SET N = N + 1

Créez également un déclencheur DELETE qui exécute:

UPDATE T SET N = N - 1

Un SGBD digne de ce nom garantira l'atomicité des opérations ci-dessus2, et N contiendra le nombre exact de lignes à tout moment, ce qui est alors très rapide à obtenir simplement:

SELECT N FROM T

Bien que les déclencheurs soient spécifiques à un SGBD, la sélection dans T ne l’est pas et votre code client n’a pas besoin de changer pour chaque SGBD pris en charge.

Toutefois, cela peut poser des problèmes d’évolutivité si la table nécessite beaucoup d’insert ou de suppression, en particulier si vous ne le faites pas immédiatement après INSERT/DELETE.


 1 Ces noms ne sont que des espaces réservés - utilisez quelque chose de plus significatif en production.

 2 C'est à dire. N ne peut pas être modifié par une transaction simultanée entre lecture et écriture sur N, tant que la lecture et l'écriture sont effectuées dans une seule instruction SQL.

2

J'ai trouvé ce bon article SQL Server - HOW-TO: récupérer rapidement le nombre de lignes exact pour table à partir de martijnh1, ce qui donne une bonne récapitulation pour chaque scénario.

J'ai besoin que cela soit élargi là où j'ai besoin de fournir un décompte basé sur une condition spécifique et lorsque je figurerai cette partie, je mettrai à jour cette réponse.

En attendant, voici les détails de l'article:

Méthode 1:

Requête: 

SELECT COUNT(*) FROM Transactions 

Commentaires: 

Effectue une analyse complète de la table. Lent sur les grandes tables. 

Méthode 2:

Requête:

SELECT CONVERT(bigint, rows) 
FROM sysindexes 
WHERE id = OBJECT_ID('Transactions') 
AND indid < 2 

Commentaires:

Moyen rapide de récupérer le nombre de lignes. Dépend des statistiques et est inexacte. 

Exécutez DBCC UPDATEUSAGE (Base de données) WITH COUNT_ROWS, ce qui peut prendre beaucoup de temps pour les grandes tables. 

Méthode 3:

Requête:

SELECT CAST(p.rows AS float) 
FROM sys.tables AS tbl 
INNER JOIN sys.indexes AS idx ON idx.object_id = tbl.object_id and
idx.index_id < 2 
INNER JOIN sys.partitions AS p ON p.object_id=CAST(tbl.object_id AS int) 
AND p.index_id=idx.index_id 
WHERE ((tbl.name=N'Transactions' 
AND SCHEMA_NAME(tbl.schema_id)='dbo')) 

Commentaires:

La façon dont le studio de gestion SQL compte les lignes (regardez les propriétés de la table, le stockage, le nombre de lignes). Très rapide, mais toujours un nombre approximatif de lignes. 

Méthode 4:

Requête:

SELECT SUM (row_count) 
FROM sys.dm_db_partition_stats 
WHERE object_id=OBJECT_ID('Transactions')    
AND (index_id=0 or index_id=1); 

Commentaires:

Opération rapide (bien que pas aussi rapide que la méthode 2) et tout aussi importante, fiable. 

2
Thierry

Une réponse complètement folle, mais si vous avez une sorte de système de réplication configuré (pour un système avec un milliard de lignes, j'espère que vous le ferez), vous pouvez utiliser un estimateur approximatif (comme MAX(pk)), divisez cette valeur par le nombre de esclaves que vous avez, exécutez plusieurs requêtes en parallèle. 

La plupart du temps, vous partitionneriez les requêtes entre les esclaves en fonction de la meilleure clé (ou de la clé primaire, je suppose), de la manière suivante (nous allons utiliser 250000000 comme nos lignes/esclaves):

-- First slave
SELECT COUNT(pk) FROM t WHERE pk < 250000000
-- Ith slave where 2 <= I <= N - 1
SELECT COUNT(pk) FROM t WHERE pk >= I*250000000 and pk < (I+1)*250000000
-- Last slave
SELECT COUNT(pk) FROM t WHERE pk > (N-1)*250000000

Mais vous avez besoin de SQL uniquement. Quel buste. Ok, alors disons que vous êtes sadomasochiste… .. Sur le maître (ou l'esclave le plus proche), vous aurez probablement besoin de créer un tableau pour cela:

CREATE TABLE counter_table (minpk integer, maxpk integer, cnt integer, slaveid integer)

Ainsi, au lieu d’avoir uniquement les sélections en cours d’exécution sur vos esclaves, vous devez effectuer une insertion, semblable à ceci:

INSERT INTO counter_table VALUES (I*25000000, (I+1)*250000000, (SELECT COUNT(pk) FROM ... ), @@SLAVE_ID)

Vous pouvez rencontrer des problèmes avec les esclaves écrivant sur une table sur maître. Vous aurez peut-être besoin d'être encore plus sadique, je veux dire, créatif:

-- A table per slave!
INSERT INTO counter_table_slave_I VALUES (...)

Vous devriez finalement avoir un esclave qui existe en dernier dans le chemin parcouru par le graphe de réplication, par rapport au premier esclave. Cet esclave devrait maintenant avoir toutes les autres valeurs de compteur et devrait avoir ses propres valeurs. Mais au moment où vous avez terminé, des lignes ont probablement été ajoutées. Vous devez donc en insérer une autre pour compenser le maximum de pk enregistré dans votre table counter_table et le maximum de pk actuel.

À ce stade, vous devez créer une fonction d'agrégat pour déterminer le nombre total de lignes, mais c'est plus facile, car vous l'exécutez tout au plus avec le nombre de lignes "esclaves que vous avez et modifiez".

Si vous avez des tables séparées dans les esclaves, vous pouvez UNION pour obtenir toutes les lignes dont vous avez besoin.

SELECT SUM(cnt) FROM (
    SELECT * FROM counter_table_slave_1
      UNION
    SELECT * FROM counter_table_slave_2
      UNION
    ...
  )

Ou vous savez, soyez un peu moins fou et migrez vos données vers un système de traitement distribué, ou utilisez peut-être une solution d'entreposage de données (qui vous offrira également une capacité de traitement des données impressionnante).

Notez que cela dépend de la configuration de votre réplication. Étant donné que le principal goulot d'étranglement sera probablement le stockage persistant, si vous avez un stockage cruddy ou des magasins de données mal séparés avec un bruit de voisinage important, cela vous exécutera probablement plus lentement que d'attendre un seul SELECT COUNT(*) ....

Mais si vous avez une bonne réplication, vos gains de vitesse doivent être directement liés au nombre ou aux esclaves. En fait, s'il faut 10 minutes pour exécuter la requête de comptage seul et que vous avez 8 esclaves, vous réduisez votre temps à moins de quelques minutes. Peut-être une heure pour régler les détails de cette solution. 

Bien sûr, vous ne recevrez jamais une réponse incroyablement précise puisque cette résolution distribuée introduit un peu de temps pendant lequel les lignes peuvent être supprimées et insérées, mais vous pouvez essayer d'obtenir un verrou distribué de lignes sur la même instance et d'obtenir un décompte précis. des lignes dans la table pour un moment particulier dans le temps. 

En fait, cela semble impossible, car vous êtes fondamentalement coincé avec une solution SQL uniquement et je ne pense pas que vous disposiez d'un mécanisme pour exécuter instantanément une requête fragmentée et verrouillée sur plusieurs esclaves. Peut-être que si vous aviez le contrôle du fichier journal de réplication ... ce qui signifie que vous alliez littéralement créer des esclaves à cette fin, ce qui est sans aucun doute plus lent que l'exécution de la requête de comptage sur une seule machine.

Donc, il y a mes deux centimes de 2013.

2
Yangmun Choi

J'ai eu ce script d'une autre question/réponse de StackOverflow:

SELECT SUM(p.rows) FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON s.[schema_id] = t.[schema_id]
  WHERE t.name = N'YourTableNameHere'
  AND s.name = N'dbo'
  AND p.index_id IN (0,1);

Ma table a 500 millions d’enregistrements et ce qui précède revient en moins de 1ms ..

SELECT COUNT(id) FROM MyTable

prend 39 minutes, 52 secondes!

Ils donnent exactement le même nombre de lignes (dans mon cas, exactement 519326012).

Je ne sais pas si ce serait toujours le cas.

2
JakeJ

Si vous avez une structure de table typique avec une colonne de clé primaire auto-incrémentée dans laquelle les lignes ne sont jamais supprimées, voici le moyen le plus rapide de déterminer le nombre d'enregistrements.

SELECT TOP(1) <primarykeyfield> FROM <table> ORDER BY <primarykeyfield> DESC;

Je travaille avec des tables MS SQL contenant des milliards de lignes qui nécessitent des temps de réponse inférieurs à la seconde pour les données, y compris le nombre d'enregistrements. Un compte SELECT SELECT similaire (*) prend quelques minutes à traiter par comparaison.

1
KevinS

Pour le serveur SQL, essayez ceci

SELECT T.name, 
       I.rows AS [ROWCOUNT] 
FROM   sys.tables AS T 
       INNER JOIN sys.sysindexes AS I 
               ON T.object_id = I.id AND I.indid < 2 
WHERE T.name = 'Your_Table_Name'
ORDER  BY I.rows DESC 
1
Abhishek B Patel

Si vous utilisez Oracle, que diriez-vous de cela (en supposant que les statistiques de la table sont mises à jour):

select <TABLE_NAME>, num_rows, last_analyzed from user_tables

last_analyzed affichera l'heure à laquelle les statistiques ont été collectées pour la dernière fois.

0
ZenithDreams

Mettez un index sur une colonne. Cela devrait permettre à l'optimiseur d'effectuer une analyse complète des blocs d'index, au lieu d'une analyse complète de la table. Cela réduira vos IO coûts. Regardez le plan d'exécution avant et après. Puis mesurez l’horloge murale dans les deux sens.

0
EvilTeach

sélectionner des lignes dans sysindexes où id = Object_ID ('TableName') et indid <2

0
Enzero

Avec PostgreSQL:

SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = 'table_name'
0
Dorian