Der kostengünstigste Weg, um durch eine schlecht geordnete Tabelle zu blättern?

7

Ich habe eine Tabelle mit drei Spalten: HashUID1, HashUID2, Address_Name (eine Text-E-Mail-Adresse, und die beiden vorherigen Hash-Spalten sind eine verrückte Kreation, um Ereignisteilnehmertabellen mit E-Mail-Adressen zu verknüpfen. Es ist hässlich, es funktioniert kaum Fokus auf den Adressnamen-Index)

Es hat 78 Millionen Zeilen. Nicht richtig sortiert. Unabhängig davon ist dieser Index auf viele schnelle LUNs aufgeteilt und führt WIRKLICH schnelle Indexsuchen durch.

Ich muss eine Reihe von Abfragen erstellen, um jeweils nur 20.000 "Zeilen pro Seite" zu extrahieren, aber Konflikte oder Dupes zu vermeiden. Gibt es eine einfache Möglichkeit, alle auszuwählen und darin zu blättern, da es keine Identitätsspalte oder leicht zu ordnende Spalte gibt?

Stimmt es, dass ich, wenn ich eine Auswahl * aus hugetablewithemails in eine temporäre Tabelle mache, durch row_number auswähle, dass die Tabelle für die Dauer der Transaktion im Speicher bleibt, was für mich eine übermäßige Menge an Speicherressourcen darstellt ? Dies scheint die bevorzugte Methode zum Paging zu sein. Ich würde lieber nach statistischen Prozentsätzen blättern. :((

Es gibt einen Index, der die E-Mail-Adresse address_name in der richtigen Reihenfolge verwaltet und gut gepflegt ist. In der vergangenen Woche wollte ich diesem anderen Entwickler helfen, indem ich einige Zeit damit verbrachte, einen Prozess zu erstellen, der Bereiche basierend auf Fensterfunktionen basierend auf Statistiken ausspuckt (was ich nicht besonders gut kann, aber diese Abfrage hat mich wirklich interessiert) Geben Sie einen Zeichenbereich von 1 bis (variabel) LINKS WIE Zeichen des Index an, der 20.000 Zeilen entspricht. Aber ich hatte noch nicht einmal Zeit, die Abfrage zu starten ...

Paar Fragen:

  1. Irgendwelche Vorschläge? Nicht auf der Suche nach aktuellem Code, nur einige Hinweise oder Vorschläge, die auf Erfahrungen basieren, möglicherweise Vorbehalte. Ich möchte zusätzliche Index-Scans nach dem ersten Scan vermeiden.

  2. Ist das der richtige Ansatz?

  3. Ich denke daran, die Summe des Index aller E-Mail-Adressen zu brechen, die Anzahl der Zeilen (*) / 20.000 zu erfassen und diese als Fensterfunktion zu verwenden, um die Werte für die minimale / maximale Teilzeichenfolge (1,5) basierend auf den Prozentsätzen der gesamten Zeilenanzahl zu gruppieren Gruppierungsbereiche erstellen. Gedanken?

Dies gilt für einen ETL-Prozess, der keine Quelldatenbanken ändern kann.

Ich hoffe mit einem vollständigen Index-Scan kann ich Folgendes tun:

  • Abfrage, um einen Histographen basierend auf der Indexverwendung (alphabetisch sortiert) zu erhalten und ihn mit min / max aufzuteilen (mit Fenstern), um einige Bereiche wie diesen zu erstellen, um den benötigten Index leicht zu finden:

  • A-> AAAX, (z. B. 20.000 Zeilen) AAA-Z, B-> (weitere 20.000), B-> BAAR -> BAAR-> CDEFG -> CDEFH> FAAH usw.

In diesen Datenbanken wird für diesen ETL-Prozess ein Lese-Commit ausgeführt. Wir versuchen nur, es in 20.000 Zeilen zu stapeln, weil die Datenbankadministratoren sagen, dass wir zu viele Netzwerkressourcen verwenden, indem wir Tabellen vollständig abrufen. Wenn sich die Daten geändert haben (was ein Problem darstellt), aktualisieren wir unsere DW- und Staging-Tabellen im laufenden Betrieb.

Ich würde gerne temporäre Tabellen verwenden, aber wenn ich das tun würde, würde ich in tempdb übergehen und von den Datenbankadministratoren per E-Mail Peitschenhiebe erhalten, und dass die Datenbank zu groß ist.

beeks
quelle

Antworten:

9

Im Wesentlichen fragen Sie, ob Sie einen einzelnen geordneten Scan der Daten insgesamt durchführen können, während Sie keine Kopien der Daten erstellen und bei jedem Aufruf 'x' disjunkte Zeilensätze aus dem vollständigen Satz zurückgeben. Dies ist genau das Verhalten eines entsprechend konfigurierten API-Cursors.

Verwenden Sie beispielsweise die AdventureWorks-Tabelle Person.EmailAddress, um Sätze mit 1.000 Zeilen zurückzugeben:

DECLARE 
    @cur integer,
    -- FAST_FORWARD | AUTO_FETCH | AUTO_CLOSE
    @scrollopt integer = 16 | 8192 | 16384,
    -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE
    @ccopt integer = 1 | 32768 | 65536, 
    @rowcount integer = 1000,
    @rc integer;

-- Open the cursor and return the first 1,000 rows
EXECUTE @rc = sys.sp_cursoropen
    @cur OUTPUT,
    N'
    SELECT *
    FROM AdventureWorks2012.Person.EmailAddress
        WITH (INDEX([IX_EmailAddress_EmailAddress]))
    ORDER BY EmailAddress;
    ',
    @scrollopt OUTPUT,
    @ccopt OUTPUT,
    @rowcount OUTPUT;

IF @rc <> 16 -- FastForward cursor automatically closed
BEGIN
    -- Name the cursor so we can use CURSOR_STATUS
    EXECUTE sys.sp_cursoroption
        @cur, 
        2, 
        'MyCursorName';

    -- Until the cursor auto-closes
    WHILE CURSOR_STATUS('global', 'MyCursorName') = 1
    BEGIN
        EXECUTE sys.sp_cursorfetch
            @cur,
            2,
            0,
            1000;
    END;
END;

Jeder Abrufvorgang gibt maximal 1.000 Zeilen zurück und speichert die Position des Scans aus dem vorherigen Aufruf.

Paul White 9
quelle
2

Ohne den Zweck hinter dem Fenster zu kennen, wird es schwierig sein, genau zu sein. Wenn man bedenkt, dass Sie zwanzigtausend Zeilen gleichzeitig betrachten, ist dies vermutlich ein Stapelprozess und nicht für die menschliche Betrachtung.

Wenn es einen Index für die E-Mail-Adresse gibt, wird dieser sortiert. Indizes sind BTrees und sie pflegen eine interne Reihenfolge. Dies ist die Sortierreihenfolge der Sortierung dieser Spalte (die wahrscheinlich, aber nicht unbedingt die Standard-Sortierung der Datenbank ist).

Temporäre Tabellen - sowohl #table als auch @table - sind in tempdb vorhanden. Auch große Ergebnismengen werden nicht mehr in Tempdb gespeichert.

Wenn mit "Statistik" die internen Statistiken von SQL Server gemeint sind, die in Indizes oder über die create statistics..Anweisung verwaltet werden, dann denke ich nicht, dass dies funktionieren wird. Diese Statistiken haben nur ein paar hundert Eimer (haben gerade das richtige Limit vergessen), in denen Sie 39.000 "Fenster" zum Lesen benötigen, um Ihre vollständige Tabelle zu lesen. Wenn Sie beabsichtigen, Ihre eigene Zuordnung von Zeile zu Fenster über Trigger beizubehalten, ist dies erreichbar, der Overhead kann jedoch erheblich sein.

Die traditionelle Methode zum Blättern durch einen großen Datensatz besteht darin, sich den größten Schlüsselwert aus jeder Gruppe zu merken und von dort aus zu lesen. Wenn die E-Mail-Adressspalte nicht eindeutig ist, dh eine Adresse kann mehrmals vorkommen, wenn Sie mehrere Optionen haben. A) Verarbeiten Sie jeden Stapel zeilenweise in der Anwendung und überspringen Sie Duplikate oder b) filtern Sie sie in SQL heraus. "B" erfordert eine Sortierung, aber wenn die Daten in Schlüsselfolge gelesen werden, kann diese Sortierung wegoptimiert werden:

declare @MaxKey varchar(255) = '';  -- email size

while exists (select 1 from mytable where address_name > @MyKey)
begin
    ;with NewBatch as
    (
    select top 20000  -- whatever size a "window" must be
        address_name
    from mytable
    where address_name > @MaxKey
    order by address_name
    )
    select distinct
        address_name
    from NewBatch;

    --process and then
    select @MaxKey = max(address_name) -- from this batch of rows
end

Die Iteration kann in SQL oder in Ihrer Anwendung erfolgen, abhängig von Ihrer Architektur.

Wenn neben der E-Mail-Adresse viele Spalten erforderlich sind, können Sie einen Cursor mit dem Schlüsselwort KEYSET oder STATIC in Betracht ziehen. Dadurch werden jedoch weiterhin Ressourcen in Tempdb verwendet.

SSIS macht einen Schritt zurück und wurde speziell entwickelt, um große Rowsets effizient zu verarbeiten. Die Definition eines Pakets, das Ihren Anforderungen entspricht, ist möglicherweise die beste langfristige Antwort.

Michael Green
quelle
1

Wenn Sie sich bei Vorhandensein von DML lediglich mit der Stabilität der Sortierreihenfolge im Zeitverlauf befassen, sollten Sie die Abfrage der Tabelle mithilfe der Snapshot-Isolation abfragen. Sie können eine SNAPSHOTTransaktion offen lassen, bis Sie mit dem Extrahieren der Seiten fertig sind. Dies hat die üblichen Nachteile, die mit der Snapshot-Isolierung verbunden sind.

Diese Technik erfordert jedoch das Sortieren der gesamten Tabelle für jede Seite, die Sie extrahieren. Das ist sehr teuer (quadratische asymptotische Leistung).

Erwägen Sie die Verwendung einer temporären Tabelle mit einem IDENTITYPrimärschlüssel. Auf diese Weise können Sie Seiten einfach über Bereichssuchen extrahieren.

Temp-Tabellen werden nicht im Speicher fixiert. Dies ist ein weit verbreitetes Missverständnis.

Bei 78 m Zeilen (jeweils 100 Byte => 7,8 GB Speicherplatz) sollte diese Technik einwandfrei funktionieren.

Beachten Sie, dass Sie durch das Extrahieren der Daten aus der Originaltabelle beispielsweise mit READ COMMITTEDmöglicherweise einen Datensatz erhalten, der zu keinem Zeitpunkt (aufgrund gleichzeitiger DML) vorhanden war. Verwenden Sie SNAPSHOTIsolation, wenn Sie können.

Sie können die temporäre Tabelle in Ihrer eigenen Datenbank oder in einem separaten SIMPLE-Modus erstellen, nicht in einer gesicherten Datenbank. Beachten Sie außerdem, dass beim Sortieren der gesamten Tabelle vorübergehend so viel Tempdb-Speicherplatz benötigt wird, wie zum Speichern aller benötigten Spalten erforderlich ist. Vielleicht müssen Sie die Zeilennummern aus dem bereits vorhandenen (eindeutigen) Index ableiten (und den Trick zur Größenreduzierung anwenden).


Eine andere Idee: Anstatt alle Zeilen in die temporäre Tabelle zu puffern, schreiben Sie nur einen Schlüssel für jede Zeile. Sie haben angegeben, dass die Suche in der Haupttabelle schnell sein würde.

Oder Sie schreiben nur jede 20.000ste Zeile, damit Sie wissen, wo Sie jede Paging-Abfrage starten müssen. Das Extrahieren einer Seite würde dann nicht nach Zeilennummer funktionieren, sondern mit SELECT TOP 20000 ... WHERE SomeKey >= PageStartKey ORDER BY SomeKey.

usr
quelle