Ich habe eine Tabelle in SQL Server 2014, die wie folgt aussieht:
CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)
wobei (id1, id2) die PK ist. Grundsätzlich ist id1 ein Bezeichner zum Gruppieren einer Reihe von Ergebnissen (id2, col1, col2), deren pk id2 ist.
Ich versuche, eine In-Memory-Tabelle zu verwenden, um eine vorhandene festplattenbasierte Tabelle zu entfernen, die mein Engpass ist.
- Die Daten in der Tabelle werden einmal geschrieben -> gelesen -> gelöscht.
- Jeder id1-Wert hat mehrere (Zehntausende / Hunderttausende) von id2.
- Die Daten werden für eine sehr kurze Zeit in der Tabelle gespeichert, z. B. 20 Sekunden.
Die in dieser Tabelle ausgeführten Abfragen lauten wie folgt:
-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
SELECT @fixedValue, id2, col1, col2 FROM AnotherTable
-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1
-- DELETE:
DELETE FROM MyTable WHERE id1 = @value
Hier ist die aktuelle Definition, die ich für die Tabelle verwendet habe:
CREATE TABLE dbo.SearchItems
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
Leider führt diese Definition zu einer Leistungsverschlechterung gegenüber der vorherigen Situation mit einer festplattenbasierten Tabelle. Die Größenordnung ist mehr oder weniger 10% höher (die in einigen Fällen 100% erreichen, also doppelte Zeit).
Vor allem hatte ich erwartet, in Szenarien mit hoher Parallelität angesichts der von Microsoft angekündigten sperrenfreien Architektur einen Supervorteil zu erzielen. Stattdessen sind die schlechtesten Leistungen genau dann zu verzeichnen, wenn mehrere Benutzer gleichzeitig mehrere Abfragen in der Tabelle ausführen.
Fragen:
- Was ist der richtige BUCKET_COUNT zum Einstellen?
- Welche Art von Index soll ich verwenden?
- Warum ist die Leistung schlechter als bei der festplattenbasierten Tabelle?
Eine Abfrage von sys.dm_db_xtp_hash_index_stats gibt Folgendes zurück:
total_bucket_count = 131072 empty_bucket_count = 0 avg_chain_len = 873 max_chain_length = 1009
Ich habe die Bucket-Anzahl so geändert, dass die Ausgabe von sys.dm_db_xtp_hash_index_stats wie folgt lautet:
total_bucket_count = 134217728 empty_bucket_count = 131664087 avg_chain_len = 1 max_chain_length = 3
Trotzdem sind die Ergebnisse fast gleich, wenn nicht sogar schlechter.
quelle
OPTION(OPTIMIZE FOR UNKNOWN)
(siehe Tabellenhinweise )?select * from sys.dm_db_xtp_hash_index_stats
? Außerdem sollte dieser Link die meisten / alle Ihre Fragen beantworten: msdn.microsoft.com/en-us/library/…Antworten:
Obwohl dieser Beitrag aufgrund fehlender Informationen keine vollständige Antwort darstellt, sollte er Sie in die richtige Richtung weisen oder auf andere Weise Einblicke gewinnen können, die Sie später mit der Community teilen können.
Dies ist beunruhigend, da dies definitiv nicht der Fall sein sollte. Bestimmte Workloads sind nicht für Speichertabellen vorgesehen (SQL 2014), und einige Workloads eignen sich dafür. In den meisten Situationen kann die Leistung nur durch Migration und Auswahl der richtigen Indizes minimal beeinträchtigt werden.
Ursprünglich habe ich sehr eng über Ihre diesbezüglichen Fragen nachgedacht:
Anfangs glaubte ich, dass es ein Problem mit der tatsächlichen In-Memory-Tabelle und den nicht optimalen Indizes gibt. Obwohl es einige Probleme mit der speicheroptimierten Hash-Index-Definition gibt, glaube ich, dass das eigentliche Problem bei den verwendeten Abfragen liegt.
Diese Einfügung sollte extrem schnell sein, wenn nur die In-Memory-Tabelle beteiligt ist. Es handelt sich jedoch auch um eine festplattenbasierte Tabelle, die allen damit verbundenen Sperren und Blockierungen unterliegt. Somit liegt die Echtzeitverschwendung hier in der festplattenbasierten Tabelle.
Als ich nach dem Laden der Daten in den Speicher einen Schnelltest gegen das Einfügen von 100.000 Zeilen aus der festplattenbasierten Tabelle durchführte, waren es Antwortzeiten von weniger als einer Sekunde. Die meisten Ihrer Daten werden jedoch nur für einen sehr kurzen Zeitraum von weniger als 20 Sekunden gespeichert. Dies gibt ihm nicht viel Zeit, um wirklich im Cache zu leben. Außerdem bin ich mir nicht sicher, wie groß das
AnotherTable
wirklich ist und weiß nicht, ob die Werte von der Festplatte gelesen werden oder nicht. Für diese Antworten müssen wir uns auf Sie verlassen.Mit der Select-Abfrage:
Auch hier sind wir der Interop + Disk-basierten Tabellenleistung ausgeliefert. Außerdem sind Sortierungen für HASH-Indizes nicht billig, und es sollte ein nicht gruppierter Index verwendet werden. Dies wird in der Indexanleitung erwähnt, die ich in den Kommentaren verlinkt habe.
Um einige aktuelle, auf Forschung basierende Fakten zu liefern, habe ich die
SearchItems
In-Memory-Tabelle mit 10 Millionen Zeilen undAnotherTable
mit 100.000 geladen, da ich die tatsächliche Größe oder Statistik nicht kannte. Ich habe dann die Auswahlabfrage oben verwendet, um auszuführen. Zusätzlich habe ich eine erweiterte Ereignissitzung auf wait_completed erstellt und in einen Ringpuffer gestellt. Es wurde nach jedem Lauf gereinigt. Ich habe auchDBCC DROPCLEANBUFFERS
eine Umgebung simuliert, in der möglicherweise nicht alle Daten im Speicher gespeichert sind.Die Ergebnisse waren nichts Spektakuläres, wenn man sie im luftleeren Raum betrachtete. Da der Laptop, auf dem ich dies teste, eine höherwertige SSD verwendet, habe ich die festplattenbasierte Leistung für die von mir verwendete VM künstlich verringert.
Die Ergebnisse kamen ohne Warteinformationen nach 5 Durchläufen der Abfrage nur für die speicherinterne Tabelle (Entfernen des Joins und keine Unterabfragen). Das ist so ziemlich wie erwartet.
Bei Verwendung der ursprünglichen Abfrage hatte ich jedoch Wartezeiten. In diesem Fall war es PAGEIOLATCH_SH, was Sinn macht, wenn die Daten von der Festplatte gelesen werden. Da ich der einzige Benutzer in diesem System bin und keine Zeit damit verbracht habe, eine umfangreiche Testumgebung für Einfügungen, Aktualisierungen und Löschvorgänge für die verknüpfte Tabelle zu erstellen, habe ich nicht erwartet, dass Sperren oder Blockieren wirksam werden.
In diesem Fall wurde erneut der erhebliche Teil der Zeit für die festplattenbasierte Tabelle aufgewendet.
Zum Schluss die Löschabfrage. Das Finden der Zeilen, die nur auf ID1 basieren, ist mit einem has-Index nicht besonders effizient. Während es stimmt, dass Gleichheitsprädikate das sind, wofür Hash-Indizes geeignet sind, basiert der Bucket, in den die Daten fallen, auf den gesamten Hash-Spalten. Somit wird id1, id2, wobei id1 = 1, id2 = 2 und id1 = 1, id2 = 3 in verschiedene Buckets gehasht, da der Hash über (1,2) und (1,3) liegt. Dies ist kein einfacher B-Tree-Bereichsscan, da Hash-Indizes nicht auf die gleiche Weise strukturiert sind. Ich würde dann erwarten, dass dies nicht der ideale Index für diese Operation ist, aber ich würde nicht erwarten, dass es, wie erlebt, Größenordnungen länger dauert. Ich würde gerne die wait_info dazu sehen.
Zwar werden Sperren aus logischen Gründen verwendet, die Operationen müssen jedoch atomar sein. Dies erfolgt über einen speziellen CPU-basierten Vergleichsoperator (weshalb In-Memory nur mit bestimmten [wenn auch fast allen in den letzten 4 Jahren hergestellten CPUs] Prozessoren funktioniert). Wir bekommen also nicht alles kostenlos, es bleibt noch etwas Zeit, um diese Operationen abzuschließen.
Ein weiterer Punkt, der angesprochen werden muss, ist die Tatsache, dass in fast allen Abfragen die verwendete Schnittstelle T-SQL (und nicht nativ kompilierte SPROCs) ist, die alle mindestens eine festplattenbasierte Tabelle berühren. Aus diesem Grund glaube ich, dass wir letztendlich keine Leistungssteigerung erzielen, da wir immer noch auf die Leistung der festplattenbasierten Tabellen beschränkt sind.
Nachverfolgen:
Erstellen Sie eine erweiterte Ereignissitzung für wait_completed und geben Sie eine Ihnen bekannte SPID an. Führen Sie die Abfrage aus und geben Sie uns die Ausgabe oder verbrauchen Sie sie intern.
Geben Sie uns ein Update zur Ausgabe von # 1.
Es gibt keine magische Zahl zum Bestimmen der Bucket-Anzahl für Hash-Indizes. Grundsätzlich sollte die Leistung akzeptabel bleiben, solange die Schaufeln nicht vollständig gefüllt sind und die Reihenketten unter 3 oder 4 bleiben. Dies ist wie die Frage: "Auf was soll ich meine Protokolldatei einstellen?" - Es wird pro Prozess, pro Datenbank, pro Nutzungstyp abhängen.
quelle