Le prestazioni di una tabella in memoria sono peggiori di una tabella basata su disco


10

Ho una tabella in SQL Server 2014 che assomiglia al seguente:

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

con (id1, id2) come PK. Fondamentalmente, id1 è un identificatore per raggruppare un insieme di risultati (id2, col1, col2), il cui pk è id2.

Sto cercando di utilizzare una tabella in memoria per eliminare una tabella basata su disco esistente che è il mio collo di bottiglia.

  • I dati nella tabella vengono scritti -> letti -> eliminati una volta.
  • Ogni valore id1 ha diverse (decine / centinaia di) migliaia di id2.
  • I dati vengono memorizzati nella tabella per un periodo di tempo molto breve, ad es. 20 secondi.

Le query eseguite su questa tabella sono le seguenti:

-- 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

Ecco la definizione corrente che ho usato per la tabella:

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)

Sfortunatamente, questa definizione comporta un peggioramento delle prestazioni rispetto alla situazione precedente con una tabella basata su disco. L'ordine di grandezza è più o meno superiore del 10% (che in alcuni casi raggiunge il 100%, quindi doppio tempo).

Soprattutto, mi aspettavo di ottenere un super-vantaggio in scenari ad alta concorrenza, data l'architettura senza blocco pubblicizzata da Microsoft. Invece, le prestazioni peggiori sono esattamente quando ci sono molti utenti simultanei che eseguono diverse query sul tavolo.

Domande:

  • qual è il BUCKET_COUNT corretto da impostare?
  • che tipo di indice dovrei usare?
  • perché le prestazioni sono peggiori rispetto alla tabella basata su disco?

Una query di sys.dm_db_xtp_hash_index_stats restituisce:

total_bucket_count = 131072
empty_bucket_count = 0
avg_chain_len = 873
max_chain_length = 1009

Ho modificato il conteggio dei bucket in modo che l'output di sys.dm_db_xtp_hash_index_stats sia:

total_bucket_count = 134217728
empty_bucket_count = 131664087
avg_chain_len = 1
max_chain_length = 3

Tuttavia, i risultati sono quasi gli stessi, se non peggio.


Sei sicuro di non avere problemi con lo sniffing dei parametri? Hai provato a eseguire le query con OPTION(OPTIMIZE FOR UNKNOWN)(vedi Suggerimenti per la tabella )?
TT.

La mia ipotesi è che stai incontrando problemi con la catena di file. Puoi darci l'output di select * from sys.dm_db_xtp_hash_index_stats ? Inoltre, questo link dovrebbe rispondere alla maggior parte / a tutte le tue domande: msdn.microsoft.com/en-us/library/…
Sean Gallardy

4
L'indice hash è utile solo per predicati su entrambe le colonne incluse. Hai provato senza un indice hash sul tavolo?
Mikael Eriksson

Ho scoperto che i migliori miglioramenti delle prestazioni con la tecnologia in-memory possono essere raggiunti solo utilizzando procedure memorizzate compilate in modo nativo .
Daniel Hutmacher,

@DanielHutmacher FWIW Ho visto dei contro-esempi in cui tutti i vantaggi derivanti dalla rimozione del blocco e dall'aggiunta di procedure compilate in modo nativo hanno dato miglioramenti nulli o trascurabili. Non credo che ci sia spazio per una dichiarazione generale (anche se potresti avere ragione in questo caso, non ho nemmeno guardato i dettagli).
Aaron Bertrand

Risposte:


7

Sebbene questo post non sia una risposta completa a causa della mancanza di informazioni, dovrebbe essere in grado di indirizzarti nella direzione corretta o altrimenti ottenere informazioni che puoi condividere in seguito con la community.

Sfortunatamente, questa definizione comporta un peggioramento delle prestazioni rispetto alla situazione precedente con una tabella basata su disco. L'ordine di grandezza è più o meno superiore del 10% (che in alcuni casi raggiunge il 100%, quindi doppio tempo).

Soprattutto, mi aspettavo di ottenere un super-vantaggio in scenari ad alta concorrenza, data l'architettura senza blocco pubblicizzata da Microsoft. Invece, le prestazioni peggiori sono esattamente quando ci sono molti utenti simultanei che eseguono diverse query sul tavolo.

Questo è preoccupante in quanto non dovrebbe assolutamente essere il caso. Alcuni carichi di lavoro non sono inclusi nelle tabelle di memoria (SQL 2014) e alcuni carichi di lavoro si prestano ad esso. Nella maggior parte dei casi può esserci un minimo aumento delle prestazioni semplicemente migrando e scegliendo gli indici corretti.

Inizialmente pensavo in modo molto limitato alle tue domande in merito a questo:

Domande:

  • qual è il BUCKET_COUNT corretto da impostare?
  • che tipo di indice dovrei usare?
  • perché le prestazioni sono peggiori rispetto alla tabella basata su disco?

Inizialmente credevo che ci fosse un problema con l'effettivo nella tabella di memoria e gli indici non fossero ottimali. Mentre ci sono alcuni problemi con la definizione dell'indice di hash ottimizzata per la memoria, credo che il vero problema sia con le query utilizzate.

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

Questo inserto dovrebbe essere estremamente veloce se coinvolgesse solo la tabella in memoria. Tuttavia, coinvolge anche una tabella basata su disco ed è soggetta a tutti i blocchi e blocchi associati. Pertanto, la perdita di tempo reale qui è nella tabella basata su disco.

Quando ho eseguito un test rapido contro l'inserimento di 100.000 righe dalla tabella basata su disco dopo aver caricato i dati in memoria, si trattava di tempi di risposta inferiori al secondo. Tuttavia, la maggior parte dei dati viene conservata per un periodo di tempo molto breve, inferiore a 20 secondi. Questo non dà molto tempo per vivere davvero nella cache. Inoltre, non sono sicuro di quanto sia grande AnotherTablee non so se i valori vengono letti dal disco o meno. Dobbiamo fare affidamento su di te per queste risposte.

Con la query Seleziona:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

Ancora una volta, siamo in balia delle prestazioni della tabella basata su interop + disk. Inoltre, gli ordinamenti non sono economici sugli indici HASH e dovrebbe essere usato un indice non cluster. Questo è indicato nella guida all'Indice che ho collegato nei commenti.

Per fornire alcuni fatti concreti basati sulla ricerca, ho caricato la SearchItemstabella in memoria con 10 milioni di righe e AnotherTablecon 100.000 poiché non conoscevo le dimensioni o le statistiche effettive. Ho quindi usato la query di selezione sopra per eseguire. Inoltre ho creato una sessione di eventi estesa su wait_completed e l'ho inserita in un buffer ad anello. È stato pulito dopo ogni corsa. Ho anche corso DBCC DROPCLEANBUFFERSper simulare un ambiente in cui tutti i dati potrebbero non essere residenti in memoria.

I risultati non furono nulla di spettacolare quando li guardai nel vuoto. Poiché il laptop su cui sto testando sta utilizzando un SSD di livello superiore, ho abbassato artificialmente le prestazioni basate su disco per la VM che sto utilizzando.

I risultati sono arrivati ​​senza informazioni di attesa dopo 5 esecuzioni della query solo sulla tabella basata in memoria (rimuovendo il join e nessuna sottoquery). Questo è praticamente come previsto.

Quando ho usato la query originale, tuttavia, avevo attese. In questo caso è stato PAGEIOLATCH_SH che ha senso quando i dati vengono letti dal disco. Dato che sono l' unico utente in questo sistema e non ho impiegato molto tempo a creare un ambiente di test di massa per inserimenti, aggiornamenti, eliminazioni rispetto alla tabella unita, non mi aspettavo che il blocco o il blocco diventasse effettivo.

In questo caso, ancora una volta, la parte significativa del tempo è stata spesa sulla tabella basata su disco.

Finalmente la query di eliminazione. Trovare le righe basandosi solo su ID1 non è estremamente efficiente con un indice has. Mentre è vero che i predicati di uguaglianza sono ciò a cui gli indici di hash sono adatti, il bucket in cui cadono i dati si basa su tutte le colonne con hash. Quindi id1, id2 dove id1 = 1, id2 = 2 e id1 = 1, id2 = 3 verranno inseriti in diversi bucket poiché l'hash sarà tra (1,2) e (1,3). Questa non sarà una semplice scansione dell'intervallo B-Tree poiché gli indici hash non sono strutturati allo stesso modo. Mi aspetterei quindi che questo non sia l' indice ideale per questa operazione, tuttavia non mi aspetto che gli ordini di grandezza richiedano più tempo dell'esperienza. Sarei interessato a vedere wait_info su questo.

Soprattutto, mi aspettavo di ottenere un super-vantaggio in scenari ad alta concorrenza, data l'architettura senza blocco pubblicizzata da Microsoft. Invece, le prestazioni peggiori sono esattamente quando ci sono molti utenti simultanei che eseguono diverse query sul tavolo.

Mentre è vero che i blocchi vengono utilizzati per coerenza logica, le operazioni devono essere comunque atomiche. Questo viene fatto tramite uno speciale operatore di confronto basato su CPU (motivo per cui In-Memory funziona solo con alcuni processori [anche se quasi tutti i CPU realizzati negli ultimi 4 anni]). Quindi non otteniamo tutto gratuitamente, ci sarà ancora del tempo per completare queste operazioni.

Un altro punto da evidenziare è il fatto che in quasi tutte le query, l'interfaccia utilizzata è T-SQL (e non SPROC compilati in modo nativo) che toccano tutti almeno una tabella basata su disco. Questo è il motivo per cui credo che, alla fine, non stiamo effettivamente ottenendo alcun aumento delle prestazioni in quanto siamo ancora vincolati alle prestazioni delle tabelle basate su disco.

Azione supplementare:

  1. Creare una sessione evento estesa per wait_completed e specificare un SPID noto all'utente. Esegui la query e forniscici l'output o utilizzalo internamente.

  2. Dacci un aggiornamento sull'output dal numero 1.

  3. Non esiste un numero magico per determinare il conteggio dei bucket per gli indici hash. Fondamentalmente, fintanto che i secchi non si riempiono completamente e le catene a catena rimangono al di sotto di 3 o 4, le prestazioni dovrebbero rimanere accettabili. È un po 'come chiedere: "A cosa dovrei impostare il mio file di registro?" - dipenderà per processo, per database, per tipo di utilizzo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.