Schneller Weg, um die Zeilenanzahl einer Tabelle in PostgreSQL zu ermitteln


107

Ich muss die Anzahl der Zeilen in einer Tabelle kennen, um einen Prozentsatz zu berechnen. Wenn die Gesamtzahl größer als eine vordefinierte Konstante ist, verwende ich den Konstantenwert. Ansonsten werde ich die tatsächliche Anzahl der Zeilen verwenden.

Ich kann verwenden SELECT count(*) FROM table. Wenn mein konstanter Wert jedoch 500.000 beträgt und ich 5.000.000.000 Zeilen in meiner Tabelle habe, wird das Zählen aller Zeilen viel Zeit verschwenden.

Kann ich aufhören zu zählen, sobald mein konstanter Wert überschritten wird?

Ich brauche die genaue Anzahl der Zeilen nur, solange sie unter dem angegebenen Grenzwert liegt. Andernfalls, wenn die Anzahl über dem Grenzwert liegt, verwende ich stattdessen den Grenzwert und möchte die Antwort so schnell wie möglich.

Etwas wie das:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;

5
Könnten Sie nicht einfach versuchen, die ersten n Zeilen auszuwählen, in denen n = Konstante + 1 ist ? Wenn es mehr als Ihre Konstante zurückgibt, wissen Sie, dass Sie Ihre Konstante verwenden sollten, und wenn es nicht gut ist, sind Sie gut?
GDDC

Haben Sie ein Identitäts- oder Auto-Inkrement-Feld in der Tabelle
Sparky

1
@Sparky: Es wird nicht garantiert, dass sequenzgestützte PKs zusammenhängend sind, Zeilen können gelöscht werden oder es können Lücken durch abgebrochene Transaktionen auftreten.
Mu ist zu kurz

Ihr Update scheint Ihrer ursprünglichen Frage zu widersprechen. Müssen Sie die genaue Anzahl der Zeilen kennen oder müssen Sie die genaue Anzahl nur kennen, wenn sie unter einem Schwellenwert liegt?
Flimzy

1
@ RenatoDinhaniConceição: Können Sie das genaue Problem erklären, das Sie lösen möchten ? Ich denke, meine Antwort unten löst das, was Sie ursprünglich gesagt haben, war Ihr Problem. Mit dem Update sieht es so aus, als ob Sie count (*) sowie viele andere Felder möchten. Es wäre hilfreich, wenn Sie genau erklären könnten, was Sie versuchen zu tun. Vielen Dank.
Ritesh

Antworten:


224

Das Zählen von Zeilen in großen Tabellen ist in PostgreSQL bekanntermaßen langsam. Um eine genaue Zahl zu erhalten, muss aufgrund der Art von MVCC eine vollständige Anzahl von Zeilen durchgeführt werden . Es gibt einen Weg , um diese dramatisch zu beschleunigen , wenn die Zählung nicht nicht sein muß genau wie es in Ihrem Fall zu sein scheint.

Anstatt die genaue Anzahl zu erhalten ( langsam bei großen Tischen):

SELECT count(*) AS exact_count FROM myschema.mytable;

Sie erhalten eine genaue Schätzung wie diese ( extrem schnell ):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

Wie nah die Schätzung ist, hängt davon ab, ob Sie ANALYZEgenug laufen . Es ist normalerweise sehr nah.
Siehe die FAQ zum PostgreSQL-Wiki .
Oder die spezielle Wiki-Seite für die Leistung von count (*) .

Besser noch

Der Artikel in dem PostgreSQL - Wiki ist war ein bisschen schlampig . Die Möglichkeit, dass mehrere Tabellen mit demselben Namen in einer Datenbank vorhanden sein können, wurde ignoriert - in verschiedenen Schemata. Um das zu erklären:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

Oder noch besser

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

Schneller, einfacher, sicherer, eleganter. Siehe das Handbuch zu Objektkennungstypen .

Verwenden Sie diese to_regclass('myschema.mytable')Option in Postgres 9.4+, um Ausnahmen für ungültige Tabellennamen zu vermeiden:


TABLESAMPLE SYSTEM (n) in Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

Wie @a_horse kommentiert , kann die neu hinzugefügte Klausel für den SELECTBefehl nützlich sein, wenn die Statistiken in pg_classaus irgendeinem Grund nicht aktuell genug sind. Beispielsweise:

  • Kein autovacuumLaufen.
  • Sofort nach einem großen INSERToder DELETE.
  • TEMPORARYTabellen (die nicht abgedeckt sind von autovacuum).

Dies betrachtet nur eine zufällige Auswahl von n % ( 1im Beispiel) von Blöcken und zählt die darin enthaltenen Zeilen. Eine größere Stichprobe erhöht die Kosten und reduziert den Fehler, Ihre Wahl. Die Genauigkeit hängt von weiteren Faktoren ab:

  • Verteilung der Zeilengröße. Wenn ein bestimmter Block breitere Zeilen als üblich enthält, ist die Anzahl niedriger als üblich usw.
  • Tote Tupel oder ein FILLFACTORPlatz pro Block belegen. Bei ungleichmäßiger Verteilung auf die Tabelle ist die Schätzung möglicherweise nicht korrekt.
  • Allgemeine Rundungsfehler.

In den meisten Fällen ist die Schätzung von pg_classschneller und genauer.

Antwort auf die eigentliche Frage

Zuerst muss ich die Anzahl der Zeilen in dieser Tabelle kennen, wenn die Gesamtzahl größer als eine vordefinierte Konstante ist.

Und ob es ...

... ist in dem Moment möglich, in dem die Zählung meinen konstanten Wert überschreitet, stoppt sie die Zählung (und wartet nicht, bis die Zählung beendet ist, um mitzuteilen, dass die Zeilenanzahl größer ist).

Ja. Sie können eine Unterabfrage verwenden mitLIMIT :

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres hört tatsächlich auf, über den angegebenen Grenzwert hinaus zu zählen. Sie erhalten eine genaue und aktuelle Zählung für bis zu n Zeilen (im Beispiel 500000) und ansonsten n . Nicht annähernd so schnell wie die Schätzung in pg_class.


8
Ich habe schließlich die Postgres-Wiki-Seite mit der verbesserten Abfrage aktualisiert.
Erwin Brandstetter

5
Mit 9.5 sollte es möglich sein, schnell eine Schätzung zu erhalten, indem folgende tablesampleKlausel verwendet wird: zBselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name

1
@ JeffWidman: Alle diese Schätzungen können aus verschiedenen Gründen größer sein als die tatsächliche Zeilenanzahl . Nicht zuletzt sind in der Zwischenzeit möglicherweise Löschvorgänge aufgetreten.
Erwin Brandstetter

2
@ErwinBrandstetter erkennt, dass diese Frage alt ist, aber wenn Sie die Abfrage in eine Unterabfrage eingeschlossen haben, ist das Limit dann immer noch effizient oder würde die gesamte Unterabfrage ausgeführt und dann in der äußeren Abfrage begrenzt. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Ich frage, weil ich versuche, eine Zählung von einer willkürlichen Abfrage zu erhalten, die möglicherweise bereits eine Limit-Klausel enthält)
Nicholas Erdenberger

1
@NicholasErdenberger: Das hängt von der Unterabfrage ab. Postgres muss möglicherweise ohnehin mehr Zeilen als das Limit berücksichtigen (z. B. ORDER BY somethingwenn kein Index verwendet werden kann oder wenn Aggregatfunktionen verwendet werden). Abgesehen davon wird nur die begrenzte Anzahl von Zeilen aus der Unterabfrage verarbeitet.
Erwin Brandstetter

12

Ich habe dies einmal in einer Postgres-App gemacht, indem ich Folgendes ausgeführt habe:

EXPLAIN SELECT * FROM foo;

Untersuchen Sie dann die Ausgabe mit einem regulären Ausdruck oder einer ähnlichen Logik. Für ein einfaches SELECT * sollte die erste Ausgabezeile ungefähr so ​​aussehen:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

Sie können den rows=(\d+)Wert als grobe Schätzung der Anzahl der zurückgegebenen Zeilen verwenden und dann nur dann die tatsächliche SELECT COUNT(*)Schätzung vornehmen, wenn die Schätzung beispielsweise weniger als das 1,5-fache Ihres Schwellenwerts beträgt (oder die Anzahl, die Sie für Ihre Anwendung für sinnvoll halten).

Abhängig von der Komplexität Ihrer Abfrage wird diese Zahl möglicherweise immer ungenauer. Tatsächlich wurde es in meiner Anwendung, als wir Verknüpfungen und komplexe Bedingungen hinzufügten, so ungenau, dass es völlig wertlos war, selbst zu wissen, wie viele Zeilen wir innerhalb einer Potenz von 100 zurückgegeben hätten, sodass wir diese Strategie aufgeben mussten.

Wenn Ihre Abfrage jedoch so einfach ist, dass Pg innerhalb eines angemessenen Fehlerbereichs vorhersagen kann, wie viele Zeilen zurückgegeben werden, funktioniert sie möglicherweise für Sie.


2

Referenz aus diesem Blog.

Sie können unten nachfragen, um die Zeilenanzahl zu ermitteln.

Verwenden von pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

Verwenden von pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;

Beachten Sie nur kurz, dass Sie Ihre Tabellen VAKUUMANALYSIEREN müssen, damit diese Methode funktioniert.
William Abma

1

In Oracle können Sie rownumdie Anzahl der zurückgegebenen Zeilen begrenzen. Ich vermute, dass ein ähnliches Konstrukt auch in anderen SQLs existiert. In dem von Ihnen angegebenen Beispiel können Sie die Anzahl der zurückgegebenen Zeilen auf 500001 begrenzen und count(*)dann ein:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)

1
Die Tabelle SELECT count (*) cnt FROM gibt immer eine einzelne Zeile zurück. Ich bin mir nicht sicher, wie LIMIT dort einen Nutzen bringen wird.
Chris Bednarski

@ ChrisBednarski: Ich habe die Oracle-Version meiner Antwort auf einer Oracle-Datenbank überprüft. Es funktioniert großartig und löst das, was ich für das Problem von OP hielt (0,05 s count(*)mit Rownum, 1 s ohne Rownum). Ja, es SELECT count(*) cnt FROM tablewird immer 1 Zeile zurückgegeben, aber mit der LIMIT-Bedingung wird "500001" zurückgegeben, wenn die Tabellengröße über 500000 liegt, und <Größe>, wenn die Tabellengröße <= 500000 ist.
Ritesh

2
Ihre PostgreSQL-Abfrage ist völliger Unsinn. Syntaktisch und logisch falsch. Bitte korrigieren oder entfernen Sie es.
Erwin Brandstetter

@ErwinBrandstetter: Entfernt, wusste nicht, dass PostgreSQL so anders ist.
Ritesh

@allrite: Ohne Zweifel funktioniert Ihre Oracle-Abfrage einwandfrei. LIMIT funktioniert jedoch anders. Grundsätzlich wird die Anzahl der an den Client zurückgegebenen Zeilen begrenzt, nicht die Anzahl der vom Datenbankmodul abgefragten Zeilen.
Chris Bednarski

0

Wie breit ist die Textspalte?

Mit einem GROUP BY können Sie nicht viel tun, um einen Datenscan (zumindest einen Index-Scan) zu vermeiden.

Ich würde empfehlen:

  1. Ändern Sie nach Möglichkeit das Schema, um doppelte Textdaten zu entfernen. Auf diese Weise erfolgt die Zählung in einem engen Fremdschlüsselfeld in der Tabelle "Viele".

  2. Alternativ können Sie eine generierte Spalte mit einem HASH des Texts und dann GROUP BY mit der Hash-Spalte erstellen. Dies dient wiederum dazu, die Arbeitslast zu verringern (Durchsuchen eines engen Spaltenindex).

Bearbeiten:

Ihre ursprüngliche Frage stimmte nicht ganz mit Ihrer Bearbeitung überein. Ich bin mir nicht sicher, ob Sie wissen, dass COUNT bei Verwendung mit GROUP BY die Anzahl der Elemente pro Gruppe und nicht die Anzahl der Elemente in der gesamten Tabelle zurückgibt.


0

Sie können die Anzahl durch die folgende Abfrage erhalten (ohne * oder Spaltennamen).

select from table_name;

2
Dies scheint nicht schneller zu sein als count(*).
Sonniger

-3

Für SQL Server (2005 oder höher) ist eine schnelle und zuverlässige Methode:

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

Details zu sys.dm_db_partition_stats werden in MSDN erläutert

Die Abfrage fügt Zeilen aus allen Teilen einer (möglicherweise) partitionierten Tabelle hinzu.

index_id = 0 ist eine ungeordnete Tabelle (Heap) und index_id = 1 ist eine geordnete Tabelle (Clustered Index)

Hier werden noch schnellere (aber unzuverlässige) Methoden beschrieben .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.