Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Problemi con le prestazioni dei parametri dei valori di tabella

Se i TVP sono "notevolmente più lenti" delle altre opzioni, molto probabilmente non li stai implementando correttamente.

  1. Non dovresti usare una DataTable, a meno che la tua applicazione non ne abbia l'uso al di fuori dell'invio dei valori al TVP. Usando il IEnumerable<SqlDataRecord> l'interfaccia è più veloce e utilizza meno memoria poiché non si duplica la raccolta in memoria solo per inviarla al DB. Ho questo documentato nei seguenti luoghi:
  2. Non dovresti usare AddWithValue per SqlParameter, anche se questo non è probabilmente un problema di prestazioni. Tuttavia, dovrebbe essere:

    SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured);
    tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
    
  3. Le TVP sono variabili di tabella e come tali non mantengono statistiche. Ciò significa che segnalano di avere solo 1 riga per Query Optimizer. Quindi, nel tuo proc, o:
    • Utilizza la ricompilazione a livello di istruzione su qualsiasi query utilizzando il TVP per qualsiasi cosa diversa da un semplice SELECT:OPTION (RECOMPILE)
    • Crea una tabella temporanea locale (ovvero un singolo # ) e copia il contenuto del TVP nella tabella temporanea
    • Puoi provare ad aggiungere una chiave primaria in cluster al tipo di tabella definito dall'utente
    • Se utilizzi SQL Server 2014 o versioni successive, puoi provare a utilizzare OLTP in memoria/tabelle ottimizzate per la memoria. Consulta:Tabella temporanea e variabile di tabella più veloci utilizzando l'ottimizzazione della memoria

Riguardo al motivo per cui stai vedendo:

insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )

invece di:

insert into @data ( ... fields ... ) 
values ( ... values ... ),
       ( ... values ... ),

SE questo è effettivamente ciò che sta accadendo, allora:

  • Se gli inserimenti vengono eseguiti all'interno di una Transazione, non vi è alcuna reale differenza di prestazioni
  • La più recente sintassi dell'elenco di valori (ovvero VALUES (row1), (row2), (row3) ) è limitato a qualcosa come 1000 righe e quindi non è un'opzione praticabile per i TVP che non hanno tale limite. TUTTAVIA, questo non è probabilmente il motivo per cui vengono utilizzati singoli inserti, dato che non ci sono limiti quando si esegue INSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col]) , che ho documentato qui:Numero massimo di righe per il costruttore del valore della tabella . Invece...
  • Il motivo è molto probabile che l'esecuzione di singoli inserimenti consenta lo streaming dei valori dal codice dell'app in SQL Server:
    1. usando un iteratore (cioè IEnumerable<SqlDataRecord> indicato in n. 1 sopra), il codice dell'app invia ogni riga come viene restituita dal metodo e
    2. costruire i VALUES (), (), ... list, anche se si esegue INSERT INTO ... SELECT FROM (VALUES ...) approccio (che non è limitato a 1000 righe), che richiederebbe comunque la creazione dell'intero VALUES lista prima di inviare qualsiasi dei dati in SQL Server. Se sono presenti molti dati, la creazione della stringa super lunga richiederebbe più tempo e occuperebbe molta più memoria durante l'esecuzione.

Consulta anche questo whitepaper del team di consulenza clienti di SQL Server:Massimizzare il throughput con TVP