Primo inquilino:Questo non è assolutamente selettivo. Potrebbero esserci variazioni minime su 1 milione di righe se si hanno solo 100 ID tenant. Ma le statistiche per queste query sono più accurate poiché SQL Server saprà che una query per il titolare A arretrerà di 500.000 righe ma quella stessa query per il titolare è di soli 50 righe. Questo è il punto principale del dolore. Questo metodo aumenta notevolmente le possibilità di avere problemi di sniffing dei parametri in cui la prima esecuzione di una Stored Procedure è per il Locatario A e agisce in modo appropriato in base allo Strumento per ottimizzare le query vedendo tali statistiche e sapendo che deve essere efficiente ottenendo 500k righe. Ma quando il Locatario B, con solo 50 righe, viene eseguito, quel piano di esecuzione non è più appropriato e, di fatto, è del tutto inappropriato. E, poiché i dati non vengono inseriti nell'ordine del campo principale,
Tuttavia, affinché il primo TenantID esegua una Stored procedure, le prestazioni dovrebbero essere migliori rispetto a quelle dell'altro approccio poiché i dati (almeno dopo aver effettuato la manutenzione dell'indice) saranno organizzati fisicamente e logicamente in modo tale che siano necessarie molte meno pagine di dati per soddisfare interrogazioni. Ciò significa meno I / O fisico, meno letture logiche, meno contese tra gli inquilini per le stesse pagine di dati, meno spazio sprecato occupato nel pool di buffer (quindi miglioramento dell'aspettativa di vita della pagina) ecc.
Ci sono due costi principali per ottenere questo miglioramento delle prestazioni. Il primo non è così difficile: è necessario eseguire una manutenzione periodica dell'indice per contrastare l'aumento della frammentazione. Il secondo è un po 'meno divertente.
Per contrastare i maggiori problemi di sniffing dei parametri, è necessario separare i piani di esecuzione tra gli inquilini. L'approccio semplicistico consiste nell'utilizzare WITH RECOMPILE
procs o il OPTION (RECOMPILE)
suggerimento per le query, ma si tratta di un successo nelle prestazioni che potrebbe spazzare via tutti i guadagni ottenuti mettendo al TenantID
primo posto. Il metodo che ho trovato ha funzionato meglio è usare Dynamic SQL parametrizzato tramite sp_executesql
. Il motivo per cui è necessario SQL dinamico è consentire la concatenazione di TenantID nel testo della query, mentre tutti gli altri predicati che normalmente sarebbero parametri sono ancora parametri. Ad esempio, se stavi cercando un particolare Ordine, faresti qualcosa del tipo:
DECLARE @GetOrderSQL NVARCHAR(MAX);
SET @GetOrderSQL = N'
SELECT ord.field1, ord.field2, etc.
FROM dbo.Orders ord
WHERE ord.TenantID = ' + CONVERT(NVARCHAR(10), @TenantID) + N'
AND ord.OrderID = @OrderID_dyn;
';
EXEC sp_executesql
@GetOrderSQL,
N'@OrderID_dyn INT',
@OrderID_dyn = @OrderID;
L'effetto che ciò ha è quello di creare un piano di query riutilizzabile solo per quel TenantID che corrisponderà al volume di dati di quel particolare Tenant. Se lo stesso inquilino A esegue nuovamente la procedura memorizzata per un'altra @OrderID
, riutilizzerà quel piano di query memorizzato nella cache. Un tenant diverso che esegue la stessa Stored procedure genererebbe un testo di query diverso solo nel valore di TenantID, ma qualsiasi differenza nel testo di query è sufficiente per generare un piano diverso. E il piano generato per il Locatario B non corrisponderà solo al volume di dati per il Locatario B, ma sarà anche riutilizzabile per il Locatario B per valori diversi di @OrderID
(poiché tale predicato è ancora parametrizzato).
Gli svantaggi di questo approccio sono:
- È un po 'più di lavoro che digitare una semplice query (ma non tutte le query devono essere Dynamic SQL, solo quelle che finiscono per avere il problema di sniffing dei parametri).
- A seconda del numero di titolari presenti su un sistema, aumenta la dimensione della cache del piano poiché ogni query ora richiede 1 piano per TenantID che lo chiama. Questo potrebbe non essere un problema, ma è almeno qualcosa di cui essere consapevoli.
SQL dinamico interrompe la catena di proprietà, il che significa che l'accesso in lettura / scrittura alle tabelle non può essere assunto avendo l' EXECUTE
autorizzazione sulla Stored Procedure. La soluzione semplice ma meno sicura è solo quella di dare all'utente l'accesso diretto alle tabelle. Questo non è certamente l'ideale, ma di solito è il compromesso per una rapida e facile. L'approccio più sicuro consiste nell'utilizzare la sicurezza basata su certificati. Significato, creare un certificato, quindi creare un utente da quel certificato, concedere a tale utente le autorizzazioni desiderate (un utente o un accesso basato su certificato non può connettersi a SQL Server da solo), quindi firmare le Stored procedure che utilizzano Dynamic SQL con quello stesso certificato tramite AGGIUNGI FIRMA .
Per ulteriori informazioni sulla firma del modulo e sui certificati, consultare: ModuleSigning.Info