Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il driver mssql-python offre diverse funzionalità e modelli per ottimizzare le prestazioni delle applicazioni SQL Server, tra cui il pooling di connessioni, l'ottimizzazione delle query e le operazioni di massa.
Gestione delle connessioni
Utilizzare un pool di connessioni
Il pool di connessione è integrato. Quando chiami conn.close(), la connessione torna al pool per essere riutilizzata invece di essere distrutta, quindi le chiamate successive connect() saltano la costosa stretta di mano:
import mssql_python
def get_data():
conn = mssql_python.connect(
"Server=<server>.database.windows.net;Database=<database>;"
"Authentication=ActiveDirectoryDefault;Encrypt=yes"
)
try:
cursor = conn.cursor()
cursor.execute("SELECT TOP 10 Name, ListPrice FROM Production.Product")
return cursor.fetchall()
finally:
conn.close()
Configura la dimensione del pool per il carico di lavoro
Regola la dimensione del pool in base alle tue esigenze di concorrenza. Se la tua applicazione gestisce molti utenti simultanei, aumenta il pool. Per carichi di lavoro più leggeri, un pool più piccolo conserva risorse del server:
import mssql_python
mssql_python.pooling(
max_size=50, # Default is 100; reduce or increase for your workload
idle_timeout=600 # Seconds before idle connections are recycled
)
Riutilizzo delle connessioni all'interno delle operazioni
Aprire una nuova connessione per ogni query comporta un sovraccarico anche con il pooling. Invece, si mantiene una singola connessione per tutta la durata di un'operazione logica:
# Bad: New connection per query
def bad_pattern(product_ids):
for pid in product_ids:
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("SELECT Name, ListPrice FROM Production.Product WHERE ProductID = %(pid)s", {"pid": pid})
row = cursor.fetchone()
process(row)
conn.close()
# Good: Single connection for all queries
def good_pattern(product_ids):
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
try:
for pid in product_ids:
cursor.execute("SELECT Name, ListPrice FROM Production.Product WHERE ProductID = %(pid)s", {"pid": pid})
row = cursor.fetchone()
process(row)
finally:
conn.close()
Mantenere aperte le connessioni nei servizi di lunga durata
I server web, i lavoratori delle code e i lavori programmati che vengono eseguiti continuamente dovrebbero tenere aperte le connessioni invece di connettersi e disconnettersi ad ogni operazione. L'apertura di una connessione richiede una stretta TCP, una negoziazione TLS e un'autenticazione, che può richiedere 50-200 ms a seconda della distanza di rete e del metodo di autenticazione. Per un operatore in coda che elabora migliaia di messaggi all'ora, quel sovraccarico si accumula rapidamente.
Mantieni la connessione aperta per tutta la vita del lavoratore e riconnettiti quando la connessione fallisce. Attendere tra un'iterazione e l'altra per evitare di sovraccaricare il server quando la coda è vuota:
import mssql_python
import time
def run_worker(connection_string: str, poll_interval: float = 1.0):
conn = None
try:
while True:
try:
if conn is None:
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("SELECT TOP 1 * FROM dbo.JobQueue WHERE Status = 'Pending' ORDER BY CreatedDate")
job = cursor.fetchone()
if job:
try:
process_job(job)
cursor.execute("UPDATE dbo.JobQueue SET Status = 'Done' WHERE JobID = %(job_id)s", {"job_id": job[0]})
except Exception:
cursor.execute("UPDATE dbo.JobQueue SET Status = 'Failed' WHERE JobID = %(job_id)s", {"job_id": job[0]})
conn.commit()
else:
time.sleep(poll_interval) # No work available, wait before polling again
except mssql_python.OperationalError:
# Connection lost, reconnect on next iteration
conn = None
time.sleep(poll_interval)
finally:
if conn is not None:
conn.close()
Con il pool di connessione abilitato (predefinito), il pool gestisce le connessioni inattive per te. Ma se disabiliti il pooling o usi una singola connessione dedicata, imposta Connection Timeout e Command Timeout nella tua stringa di connessione per rilevare le connessioni obsolete in anticipo invece che si blocchino.
Ottimizzazione delle query
Fetch aveva bisogno solo di dati
Selezionare solo le colonne utilizzate dalla tua applicazione riduce il trasferimento di rete, il consumo di memoria e i tempi di esecuzione delle query.
# Bad: Select all columns
cursor.execute("SELECT * FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s", {"customer_id": 1})
# Good: Select specific columns
cursor.execute("""
SELECT SalesOrderID, OrderDate, TotalDue
FROM Sales.SalesOrderHeader
WHERE CustomerID = %(customer_id)s
""", {"customer_id": 1})
Usa metodi di recupero appropriati
Il driver fornisce diversi metodi di recupero dei dati. Usa quella che corrisponde alla dimensione del tuo risultato:
-
fetchval()restituisce un singolo valore scalare con un carico minimo. -
fetchall()carica l'intero set di risultati in memoria, il che funziona bene per tabelle piccole. -
fetchmany(n)recupera le righe in lotti, mantenendo costante l'uso della memoria per grandi set di risultati.
La corretta dimensione del batch fetchmany() dipende dalla larghezza della riga. Per righe strette (poche colonne piccole, circa 1 KB ciascuna), 1.000 righe mantengono ogni batch intorno a 1 MB di memoria. Per righe più larghe con stringhe grandi o colonne binarie, usa un lotto più piccolo. Inizia con 1.000 e aggiusta in base ai tuoi dati.
def process_batch(rows):
# Example: print each row. Replace with your own logic.
for row in rows:
print(row)
# Single value
cursor.execute("SELECT COUNT(*) FROM Production.Product")
count = cursor.fetchval()
# Small result set
cursor.execute("SELECT ProductCategoryID, Name FROM Production.ProductCategory")
categories = cursor.fetchall()
# Large result set - process in batches
cursor.execute("SELECT SalesOrderID, OrderDate, TotalDue FROM Sales.SalesOrderHeader")
while True:
batch = cursor.fetchmany(1000)
if not batch:
break
process_batch(batch)
Usa la paginazione lato server
Invece di recuperare tutte le righe e sliccare in Python, usa OFFSET/FETCH NEXT per recuperare solo la pagina di cui hai bisogno.
def get_page(cursor, page: int, page_size: int = 50) -> list:
"""Get paginated results efficiently."""
offset = (page - 1) * page_size
cursor.execute("""
SELECT ProductID, Name, ListPrice
FROM Production.Product
ORDER BY ProductID
OFFSET %(offset)s ROWS
FETCH NEXT %(page_size)s ROWS ONLY
""", {"offset": offset, "page_size": page_size})
return cursor.fetchall()
Usa SET NOCOUNT ON
Di default, SQL Server invia un messaggio "righe interessate" dopo ogni istruzione DML.
SET NOCOUNT ON sopprime questi messaggi e riduce il traffico di rete. È un'impostazione a livello di sessione, quindi impostala una volta dopo aver connesso invece di inserirla in ogni query.
# Set once after connecting
cursor.execute("SET NOCOUNT ON")
# All subsequent statements on this connection skip the row-count message
cursor.execute(
"INSERT INTO Log (Message) VALUES (%(message)s)",
{"message": "Log entry"}
)
Scegli il metodo di inserimento giusto
Il driver offre tre modi per inserire dati, ciascuno adatto a una scala diversa:
| metodo | Numero di righe | Perché |
|---|---|---|
execute() |
1 fila per chiamata | Usare per operazioni a riga singola come invii di moduli o gestori API dove serve immediatamente l'ID inserito. |
executemany() |
~10-1.000 righe | Utilizza il binding dei parametri colonna per colonna per una migliore produttività rispetto a un loop. Invia ogni riga come un'istruzione parametrizzata. |
bulkcopy() |
Centinaia di righe e oltre | Utilizza il protocollo TDS bulk insert, che è significativamente più efficiente rispetto agli inserti riga per riga. Ideale per carichi dati, migrazioni e elaborazione batch. |
Per maggiori dettagli ed esempi, vedi Caricamento e schemi di movimento dei dati.
Inserimenti singoli con execute()
Usa per inserti singoli dove hai bisogno del risultato immediato.
Production.Product ha diverse colonne NOT NULL senza default, quindi l'insert elenca tutte:
from datetime import datetime
cursor.execute(
"""
INSERT INTO Production.Product
(Name, ProductNumber, SafetyStockLevel, ReorderPoint,
StandardCost, ListPrice, DaysToManufacture, SellStartDate)
VALUES (%(name)s, %(number)s, %(safety)s, %(reorder)s,
%(cost)s, %(price)s, %(days)s, %(start)s)
""",
{
"name": "Widget", "number": "WG-1001",
"safety": 100, "reorder": 75,
"cost": 12.50, "price": 19.99,
"days": 1, "start": datetime(2024, 1, 1),
},
)
conn.commit()
Inserimenti batch con executemany()
executemany() lega parametri per colonne e li invia in modo efficiente. Usarlo per lotti moderati invece di chiamare execute() in un ciclo. Si noti che executemany() richiede marcatori posizionali ? con un elenco di tuple, mentre execute() supporta sia ? sia parametri denominati %(name)s con i dizionari. Vedi Query parametrizzate per dettagli su ogni stile.
rows = [
("Widget A", "WG-1001", 100, 75, 12.50, 19.99, 1, datetime(2024, 1, 1)),
("Widget B", "WG-1002", 100, 75, 15.00, 24.99, 1, datetime(2024, 1, 1)),
("Widget C", "WG-1003", 100, 75, 18.00, 29.99, 1, datetime(2024, 1, 1)),
]
cursor.executemany(
"""
INSERT INTO Production.Product
(Name, ProductNumber, SafetyStockLevel, ReorderPoint,
StandardCost, ListPrice, DaysToManufacture, SellStartDate)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
rows,
)
conn.commit()
Copia in blocco per carichi grandi
Quando la velocità di trasmissione conta più del controllo per riga, si passa a bulkcopy(). Trasmette le righe attraverso il protocollo TDS bulk insert ed evita il sovraccarico per riga delle istruzioni parametrizzate. Il crossover esatto in cui bulkcopy() supera executemany() dipende dalla larghezza delle righe e dalla latenza di rete, ma di solito si trova nelle poche centinaia di righe. Per lotti molto piccoli, executemany() è più semplice perché bulkcopy() crea una connessione interna separata e fa commit automaticamente.
A differenza di execute() e executemany(), bulkcopy() associa i valori alle colonne in base alla posizione, non tramite un elenco di colonne INSERT. Passa column_mappings per specificare i nomi delle colonne di destinazione in cui stai caricando i dati, così le tuple di origine si allineano alle colonne corrette invece che alla prima colonna identity della tabella:
result = cursor.bulkcopy(
"Production.Product",
rows,
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")
Per carichi molto grandi, usa un generatore per evitare di caricare l'intero dataset in memoria e imposta batch_size il commit periodico:
import csv
def csv_rows(path):
with open(path, newline="") as f:
reader = csv.reader(f)
next(reader) # Skip header
for row in reader:
yield tuple(row)
cursor.bulkcopy(
"Production.Product",
csv_rows("products.csv"),
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
batch_size=5000,
)
Strategie di memorizzazione nella cache
Per i dati di riferimento che raramente cambiano (categorie, tabelle di ricerca, configurazione), memorizza i risultati nella tua applicazione invece di interrogare ogni richiesta.
Python's functools.lru_cache fornisce una semplice memorizzazione, ma memorizza indefinitamente finché il processo non si riavvia. Se i dati sottostanti possono cambiare, si usa cachetools.TTLCache per aggiornare automaticamente dopo un limite di tempo:
from cachetools import TTLCache, cached
category_cache = TTLCache(maxsize=1, ttl=300) # Refresh every 5 minutes
@cached(category_cache)
def get_categories(connection_string: str) -> tuple:
conn = mssql_python.connect(connection_string)
try:
cursor = conn.cursor()
cursor.execute("SELECT ProductCategoryID, Name FROM Production.ProductCategory")
return cursor.fetchall()
finally:
conn.close()
Ottimizzazione della rete
Minimizzare i viaggi di andata e ritorno
Ogni query è un viaggio di andata e ritorno di rete verso il server. Combina query correlate in un unico batch e usa nextset() per passare tra i set di risultati:
# Bad: Multiple round trips
cursor.execute("SELECT CustomerID, AccountNumber FROM Sales.Customer WHERE CustomerID = %(customer_id)s", {"customer_id": 1})
customer = cursor.fetchone()
cursor.execute("SELECT SalesOrderID, OrderDate, TotalDue FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s", {"customer_id": 1})
orders = cursor.fetchall()
cursor.execute("SELECT COUNT(*) FROM Sales.SalesOrderDetail WHERE SalesOrderID IN (SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s)", {"customer_id": 1})
detail_count = cursor.fetchval()
# Good: Single round trip
cursor.execute("""
SELECT CustomerID, AccountNumber FROM Sales.Customer WHERE CustomerID = %(customer_id)s;
SELECT SalesOrderID, OrderDate, TotalDue FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s;
SELECT COUNT(*) FROM Sales.SalesOrderDetail WHERE SalesOrderID IN (SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s);
""", {"customer_id": 1})
customer = cursor.fetchone()
cursor.nextset()
orders = cursor.fetchall()
cursor.nextset()
detail_count = cursor.fetchval()
Usa l'elaborazione lato server per la logica complessa
Spingi aggregazione e filtraggio su SQL Server invece di prendere le righe grezze e processarle in Python. Il server restituisce una singola riga riassuntiva invece di potenzialmente migliaia di righe di dettaglio:
cursor.execute("""
SELECT p.Name, COUNT(sod.SalesOrderDetailID) AS OrderCount, SUM(sod.LineTotal) AS TotalSales
FROM Production.Product p
JOIN Sales.SalesOrderDetail sod ON p.ProductID = sod.ProductID
WHERE p.ProductID = %(product_id)s
GROUP BY p.Name
""", {"product_id": 707})
Evitare le operazioni con il cursore intercalato
Il driver mssql-python non supporta i Multiple Active Result Sets (MARS). Solo un cursore può avere una query attiva per ogni connessione. Recupera completamente il primo set di risultati prima di eseguire la query successiva, oppure usa una seconda connessione:
connection_string = (
"Server=<server>.database.windows.net;Database=<database>;"
"Authentication=ActiveDirectoryDefault;Encrypt=yes"
)
# Option 1: Fetch first, then query (single connection)
# Warning: This is an N+1 pattern. Each iteration is a round trip.
# Use this only when the JOIN in Option 2 is not possible.
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("SELECT ProductID FROM Production.Product WHERE ProductSubcategoryID = 1")
product_ids = [row[0] for row in cursor.fetchall()]
for pid in product_ids:
cursor.execute("SELECT ProductID, LocationID, Quantity FROM Production.ProductInventory WHERE ProductID = %(product_id)s", {"product_id": pid})
inventory = cursor.fetchone()
conn.close()
# Option 2: Use a JOIN instead of N+1 queries (preferred)
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("""
SELECT p.ProductID, p.Name, i.Quantity
FROM Production.Product p
LEFT JOIN Production.ProductInventory i ON p.ProductID = i.ProductID
WHERE p.ProductSubcategoryID = 1
""")
results = cursor.fetchall()
conn.close()
Gestione della memoria
Processare grandi risultati in blocchi
Caricare una tabella di milioni di righe in una lista consuma memoria proporzionale all'intero set di risultati. Usa OFFSET e FETCH NEXT per paginare i dati lato server ed elaborare un blocco alla volta.
def quote_id(identifier: str) -> str:
"""Quote a possibly schema-qualified SQL identifier to prevent SQL injection."""
return ".".join("[" + part.replace("]", "]]") + "]" for part in identifier.split("."))
def process_large_table(cursor, table: str, columns: list[str], key_column: str, processor, chunk_size: int = 10000):
"""Process large table without loading all data."""
safe_table = quote_id(table)
safe_key = quote_id(key_column)
col_list = ", ".join(quote_id(c) for c in columns)
cursor.execute(f"SELECT COUNT(*) FROM {safe_table}")
total = cursor.fetchval()
offset = 0
while offset < total:
cursor.execute(f"""
SELECT {col_list} FROM {safe_table}
ORDER BY {safe_key}
OFFSET ? ROWS
FETCH NEXT ? ROWS ONLY
""", (offset, chunk_size))
chunk = cursor.fetchall()
processor(chunk)
offset += chunk_size
print(f"Processed {min(offset, total)}/{total}")
# key_column must be unique, otherwise rows can be duplicated or skipped across pages
process_large_table(
cursor,
"Production.TransactionHistory",
["TransactionID", "ProductID", "Quantity", "ActualCost"],
"TransactionID",
lambda chunk: None, # replace with your row-processing logic
)
Usa i generatori per lo streaming
Un wrapping fetchmany() con generatore Python mantiene costante l'uso della memoria indipendentemente dalla dimensione della tabella. Il chiamante itera riga per riga senza caricare l'intero set di risultati. Per una sorgente extra-grande, combina le tabelle con UNION ALL e trasmetti il risultato combinato allo stesso modo.
def stream_query(cursor, query: str, params: dict = None, batch_size: int = 1000):
cursor.execute(query, params or {})
while True:
batch = cursor.fetchmany(batch_size)
if not batch:
break
for row in batch:
yield row
# Union the live and archive transaction tables into one extra-large result set
query = """
SELECT TransactionID, ProductID, Quantity, ActualCost FROM Production.TransactionHistory
UNION ALL
SELECT TransactionID, ProductID, Quantity, ActualCost FROM Production.TransactionHistoryArchive
"""
count = 0
for row in stream_query(cursor, query, batch_size=5000):
count += 1
print(f"Streamed {count} rows")
Pulire prontamente le risorse
Le connessioni non chiuse occupano le risorse del server e possono esaurire il pool di connessioni. Usa un gestore di contesto per garantire la pulizia anche quando si verificano eccezioni.
from contextlib import contextmanager
@contextmanager
def database_connection(connection_string: str):
conn = mssql_python.connect(connection_string)
try:
yield conn
finally:
conn.close()
with database_connection(connection_string) as conn:
cursor = conn.cursor()
cursor.execute("SELECT TOP 10 Name, ListPrice FROM Production.Product")
data = cursor.fetchall()
Monitorare l'uso della memoria
Grandi set di risultati, cache di lunga durata e oggetti di connessione consumano tutti memoria. Se la tua applicazione si attiva come servizio, perdite di memoria da cursori non chiusi o cache non limitate possono alla fine causare la morte del processo da parte del sistema operativo o del container.
Usa il modulo di tracemalloc Python per fare snapshot della memoria e trovare le allocazioni più grandi.
import tracemalloc
tracemalloc.start()
# ... run your workload ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
Le fonti comuni di crescita inaspettata della memoria includono:
- Chiamare
fetchall()su una query che restituisce milioni di righe. Usa invecefetchmany()o un generatore. - Memorizzazione nella cache dei risultati delle query senza un
maxsizeo un TTL. Le cache crescono fino a quando il processo non viene riavviato. - Creare cursori in un loop senza chiuderli. Ogni cursore aperto contiene il suo set di risultato in memoria.
Ottimizzazione di indici e piani di query
Controlla le prestazioni delle query lato server
Usa SET STATISTICS TIME ON e SET STATISTICS IO ON vede quanto tempo durano le query sul server e quanti dati leggono. Letture logiche elevate di solito indicano un indice mancante. Esegui queste istruzioni in SQL Server Management Studio o nell'estensione MSSQL per Visual Studio Code, dove l'output appare nel pannello Messaggi:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Verrà visualizzato un output simile al seguente:
Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Se vedi letture logiche elevate o scansioni di tabelle, considera di aggiungere un indice.
Usa gli hint di query come correzione tattica
Gli hint di query prevalgono sulle scelte dell'ottimizzatore di query in materia di indici e strategie di join. In produzione, sono preziose come correzione rapida e a basso rischio quando una query peggiora improvvisamente. Puoi implementare immediatamente l'hint nel codice dell'applicazione per stabilizzare la query mentre indaghi la causa principale (indici mancanti, statistiche obsolete o cambiamenti nello schema).
Evita di lasciare indizi in modo permanente. Quando la distribuzione dei dati o lo schema cambia, un suggerimento codificato in modo rigido può peggiorare le cose. Trattali come temporanei e rivalutali dopo che il problema di fondo è risolto:
cursor.execute("""
SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})
Usa OPTION (RECOMPILE) per bypassare i piani cacheati difettosi
SQL Server memorizza in cache i piani di query basandosi sul primo insieme di parametri che vede. Se la distribuzione dei dati varia molto tra le chiamate, il piano cacheato potrebbe avere scarse prestazioni per alcuni valori. Questo problema, chiamato parameter sniffing, si manifesta spesso con una query che "prima era veloce" e che improvvisamente impiega secondi o minuti.
OPTION (RECOMPILE)costringe SQL Server a costruire un piano nuovo per ogni esecuzione, che è una soluzione immediata efficace che puoi implementare senza modifiche lato server. Il compromesso è un piccolo costo di compilazione per chiamata, ma per query che vengono eseguite raramente o restituiscono set di risultati variabili, quel costo è trascurabile rispetto all'esecuzione di un piano scadente.
Una volta stabilizzato il problema, puoi prenderti il tempo necessario per applicare una soluzione definitiva, come riscrivere la query, aggiungere indici filtrati o utilizzare guide ai piani:
cursor.execute("""
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate > %(start_date)s
OPTION (RECOMPILE)
""", {"start_date": start_date})
Monitoraggio delle prestazioni
Sincronizza le tue domande
Per individuare le operazioni lente, racchiudi le query in time.perf_counter():
import time
start = time.perf_counter()
cursor.execute("SELECT SalesOrderID, OrderDate, TotalDue FROM Sales.SalesOrderHeader WHERE CustomerID = %(customer_id)s", {"customer_id": customer_id})
rows = cursor.fetchall()
elapsed = time.perf_counter() - start
print(f"Query returned {len(rows)} rows in {elapsed:.3f}s")
Per avere una visione più ampia di dove la tua applicazione impiega il tempo, usa il modulo integrato cProfile di Python:
python -m cProfile -s cumtime my_app.py
Questa visualizzazione mostra il tempo cumulativo per ogni chiamata di funzione, il che aiuta a identificare se la lentezza è nell'esecuzione delle query, nell'elaborazione dei dati o nella latenza di rete.
Usa Query Store per l'analisi lato server
La misurazione lato client indica quanto tempo impiega una query dal punto di vista dell'applicazione, ma combina la latenza di rete, il tempo di esecuzione del server e l'elaborazione sul client. Query Store cattura i piani di esecuzione e le statistiche di runtime sul server, così puoi vedere esattamente come SQL Server ha eseguito ogni query, quanto spesso è stata eseguita e come le sue prestazioni sono cambiate nel tempo.
Query Store è particolarmente utile per identificare il parameter sniffing, le regressioni del piano di esecuzione e le query che consumano la maggior quantità di risorse del server. Puoi interrogare direttamente le viste sys.query_store_runtime_stats e sys.query_store_plan, oppure utilizzare i report integrati di Query Store in SQL Server Management Studio.
Usa i report del Dashboard delle Prestazioni
I Performance Dashboard Report in SQL Server Management Studio forniscono una panoramica in tempo reale dello stato di SQL Server, inclusi i tipi di attesa attuali, le query attive costose e le tendenze CPU/IO. Usali per individuare rapidamente i colli di bottiglia senza scrivere richieste direttamente ai DMV.
Elenco di controllo delle prestazioni
Connection
- [ ] Abilita il pool di connessioni.
- [ ] Dimensiona il bacino in base al tuo carico di lavoro.
- [ ] Riutilizzare le connessioni all'interno delle operazioni.
- [ ] Mantenere aperte le connessioni nei servizi di lunga durata.
Queries
- [ ] Seleziona solo le colonne di cui hai bisogno.
- [ ] Usa il metodo di recupero appropriato per ogni query.
- [ ] Implementa la paginazione lato server.
- [ ] Imposta
SET NOCOUNT ONuna volta dopo la connessione. - [ ] Riduci al minimo le andate e ritorno raggruppando le query in batch.
Inserti
- [ ] Da usare
execute()per inserti a fila singola. - [ ] Da usare
executemany()per lotti piccoli o moderati (~10-1.000 righe). - [ ] Usa
bulkcopy()quando la velocità di trasmissione conta più del controllo per fila.
Caching
- [ ] Memorizza i dati di riferimento nella cache con un TTL per evitare di restituire risultati non aggiornati.
Resources
- [ ] Processa i risultati grandi in blocchi o con generatori.
- [ ] Pulire rapidamente le connessioni.
- [ ] Monitorare l'utilizzo della memoria con
tracemallocnei servizi a esecuzione prolungata.