Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De mssql-python-driver biedt verschillende functies en patronen om de prestaties van SQL Server-applicaties te optimaliseren, waaronder verbindingspooling, queryoptimalisatie en bulkoperaties.
Verbindingsbeheer
Groepsgewijze verbinding gebruiken
Verbindingspooling is ingebouwd. Wanneer je belt conn.close(), keert de verbinding terug naar de pool voor hergebruik in plaats van vernietigd te worden, dus latere connect() oproepen slaan de dure handdruk over:
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()
Poolgrootte configureren voor workload
Pas de poolgrootte aan op basis van je concurrency-eisen. Als je applicatie veel gelijktijdige gebruikers verwerkt, vergroot dan de pool. Voor lichtere workloads bespaart een kleinere pool serverresources:
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
)
Verbindingen in bewerkingen hergebruiken
Het openen van een nieuwe verbinding voor elke query voegt overhead toe, zelfs met pooling. Houd in plaats daarvan één verbinding vast gedurende de duur van een logische bewerking:
# 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()
Houd verbindingen open in langdurige diensten
Webservers, wachtrijprocessen en geplande taken die continu draaien, zouden de verbindingen open moeten houden in plaats van bij elke bewerking opnieuw verbinding te maken en te verbreken. Het openen van een verbinding vereist een TCP-handshake, TLS-onderhandeling en authenticatie, wat 50-200 ms kan duren afhankelijk van netwerkafstand en authenticatiemethode. Voor een wachtrijmedewerker die duizenden berichten per uur verwerkt, loopt die overhead snel op.
Houd de verbinding open gedurende de levensduur van de werknemer en maak opnieuw verbinding wanneer de verbinding uitvalt. Slaap tussen iteraties om te voorkomen dat de server wordt overbelast als de wachtrij leeg is:
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()
Met connection pooling ingeschakeld (de standaard) regelt de pool de idle verbindingen voor je. Maar als je pooling uitschakelt of één speciale verbinding gebruikt, stel Connection TimeoutCommand Timeout dan in je verbindingsreeks in om verouderde verbindingen vroeg te detecteren in plaats van te hangen.
Queryoptimalisatie
Haal alleen de benodigde gegevens op
Het selecteren van alleen de kolommen die je applicatie gebruikt, vermindert netwerkoverdracht, geheugenverbruik en query-uitvoeringstijd.
# 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})
Gebruik geschikte fetch-methoden
De driver biedt verschillende fetch-methoden. Gebruik degene die overeenkomt met de grootte van je resultaat:
-
fetchval()geeft één scalaire waarde terug met minimale overhead. -
fetchall()laadt de volledige resultaatset in het geheugen, wat goed werkt voor kleine tabellen. -
fetchmany(n)haalt rijen in batches op, waarbij het geheugengebruik constant blijft voor grote resultaatsets.
De juiste batchgrootte voor fetchmany() hangt af van de rijbreedte. Voor smalle rijen (een paar kleine kolommen, ongeveer 1 KB elk) houdt 1.000 rijen elke batch rond de 1 MB geheugen. Voor bredere rijen met grote strings of binaire kolommen gebruik een kleinere batchgrootte. Begin met 1.000 en pas aan op basis van je data.
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)
Gebruik server-side paginering
In plaats van alle rijen op te halen en in Python op te delen, gebruik OFFSET/FETCH NEXT om alleen de pagina op te halen die je nodig hebt.
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()
Gebruik SET NOCOUNT ON
Standaard stuurt SQL Server na elke DML-instructie een bericht "rows affected".
SET NOCOUNT ON onderdrukt deze berichten en vermindert het netwerkverkeer. Het is een sessie-niveau setting, dus stel het één keer in na het verbinden in plaats van het in elke query in te embeddingen.
# 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"}
)
Kies de juiste invoegmethode
De driver biedt drie manieren om data in te voegen, elk geschikt voor een andere schaal:
| Methode | Aantal rijen | Waarom |
|---|---|---|
execute() |
1 rij per oproep | Gebruik deze voor single-row operaties zoals formulierindiening of API-handlers waarbij je de ingevoerde ID direct nodig hebt. |
executemany() |
~10-1.000 rijen | Gebruikt kolomgewijze parameterbinding voor betere doorvoer dan een lus. Stuurt elke rij als een geparametriseerde instructie. |
bulkcopy() |
Honderden rijen of meer | Gebruikt het TDS bulk insert-protocol, dat aanzienlijk efficiënter is dan row-by-row inserts. Het beste voor dataloads, migraties en batchverwerking. |
Voor meer details en voorbeelden, zie Data loading and movement patterns.
Afzonderlijke invoegingen met execute()
Gebruik het voor eenmalige inzetstukken waarbij je het resultaat direct nodig hebt.
Production.Product heeft meerdere NIET NULL-kolommen zonder standaardinstellingen, dus de insert vermeldt ze allemaal:
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()
Batchinvoegingen met executemany()
executemany() bindt parameters kolomgewijs en stuurt deze efficiënt. Gebruik dit voor middelgrote batches in plaats van execute() in een lus aan te roepen. Let op dat executemany() positionele ? markers vereist met een lijst van tuples, terwijl execute() zowel ? als benoemde %(name)s parameters met dicts ondersteunt. Zie Geparametriseerde queries voor details over elke stijl.
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()
In bulk kopiëren voor grote hoeveelheden
Wanneer doorvoer belangrijker is dan controle per rij, schakel dan over naar bulkcopy(). Het streamt rijen door het TDS bulk insert-protocol en voorkomt de per-rij overhead van geparametriseerde statements. Het exacte omslagpunt waarop bulkcopy() beter presteert dan executemany() hangt af van de rijbreedte en netwerklatentie, maar ligt doorgaans in de lage honderden. Voor zeer kleine batches is executemany() eenvoudiger, omdat bulkcopy() een afzonderlijke interne verbinding maakt en automatisch vastlegt.
In tegenstelling tot execute() en executemany(), bulkcopy() kaarten waarden af aan kolommen op basis van positie, niet op basis van een INSERT kolomlijst. Geef column_mappings op om de doelkolommen op te geven waarin je laadt, zodat de brontuples overeenkomen met de juiste kolommen in plaats van met de eerste identiteitskolom van de tabel:
result = cursor.bulkcopy(
"Production.Product",
rows,
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")
Voor zeer grote belastingen gebruik je een generator om te voorkomen dat je de hele dataset in het geheugen laadt en stel je batch_size in om periodiek te committen:
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,
)
Cache-strategieën
Voor referentiegegevens die zelden veranderen (categorieën, opzoektabellen, configuratie), cache de resultaten in je applicatie in plaats van bij elke aanvraag te queryen.
Python functools.lru_cache biedt eenvoudige memoisatie, maar het wordt onbeperkt gecached totdat het proces opnieuw wordt gestart. Als de onderliggende gegevens kunnen veranderen, gebruik cachetools.TTLCache om na een bepaalde tijd automatisch te vernieuwen:
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()
Netwerkoptimalisatie
Minimaliseer retourreizen
Elke aanvraag is een heen-en-terugreis via het netwerk naar de server. Combineer gerelateerde queries tot één batch en gebruik nextset() deze om door de resultaatsets te gaan:
# 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()
Gebruik server-side verwerking voor complexe logica
Stuur aggregatie en filtering naar SQL Server in plaats van ruwe rijen op te halen en ze in Python te verwerken. De server geeft een enkele samenvattingsrij terug in plaats van mogelijk duizenden detailrijen:
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})
Vermijd door elkaar lopende cursorbewerkingen
De mssql-python-driver ondersteunt geen Multiple Active Result Sets (MARS). Er kan per verbinding slechts één cursor een actieve query hebben. Haal de eerste resultaatset volledig op voordat je de volgende query uitvoert, of gebruik een tweede verbinding:
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()
Geheugenbeheer
Verwerk grote resultaten in blokken
Het laden van een tabel met meerdere miljoenen rijen in een lijst verbruikt geheugen evenredig aan de volledige resultaatset. Gebruik OFFSET en FETCH NEXT om de gegevens aan de serverzijde te pagineren en steeds één deel tegelijk te verwerken.
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
)
Gebruik generatoren voor streaming
Een Python-generatorwrapping fetchmany() houdt het geheugengebruik constant ongeacht de tabelgrootte. De beller herhaalt rij voor rij zonder de volledige resultaatset te laden. Voor een extra grote bron combineer je tabellen met UNION ALL en stream je het gecombineerde resultaat op dezelfde manier.
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")
Ruim de middelen snel op
Ongesloten verbindingen binden serverbronnen en kunnen de verbindingspool uitputten. Gebruik een contextmanager om opruiming te garanderen, zelfs als er uitzonderingen optreden.
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()
Geheugengebruik bewaken
Grote resultaatsets, langlevende caches en verbindingsobjecten verbruiken allemaal geheugen. Als je applicatie als service draait, kunnen geheugenlekken van ongesloten cursors of onbegrensde caches uiteindelijk ertoe leiden dat het proces wordt gedood door het besturingssysteem of de containerruntime.
Gebruik de tracemalloc module van Python om geheugen te snapshoten en de grootste allocaties te vinden.
import tracemalloc
tracemalloc.start()
# ... run your workload ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
Veelvoorkomende bronnen van onverwachte geheugengroei zijn:
-
fetchall()aanroepen op een query die miljoenen rijen retourneert. Gebruikfetchmany()of in plaats daarvan een generator. - Caching van zoekresultaten zonder een
maxsizeof TTL. Caches groeien totdat het proces opnieuw wordt gestart. - Cursoren aanmaken in een lus zonder ze te sluiten. Elke open cursor bewaart zijn resultaatset in het geheugen.
Optimalisatie van indexen en queryplannen
Controleer de prestaties van query's aan de serverzijde
Gebruik SET STATISTICS TIME ON en SET STATISTICS IO ON om te zien hoe lang queries op de server duren en hoeveel data ze lezen. Hoge aantallen logische leesbewerkingen wijzen meestal op een ontbrekende index. Voer deze instructies uit in SQL Server Management Studio of de MSSQL-extensie voor Visual Studio Code, waar de output verschijnt in het Berichten-paneel:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
U zou uitvoer moeten zien zoals:
Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Als je hoge logische reads of tabelscans ziet, overweeg dan een index toe te voegen.
Gebruik query hints als een tactische oplossing
Query hints overschrijven de keuzes van de query-optimizer voor indexen en joinstrategieën. In een productieomgeving zijn ze waardevol als een snelle oplossing met weinig risico wanneer een query opeens slechter presteert. Je kunt de hint direct in je applicatiecode inzetten om de query te stabiliseren terwijl je de oorzaak onderzoekt (ontbrekende indexen, verouderde statistieken of schemawijzigingen).
Vermijd om hints permanent te laten staan. Wanneer de dataverdeling of het schema verandert, kan een hardcoded hint het probleem verergeren. Behandel ze als tijdelijk en bekijk ze opnieuw nadat het onderliggende probleem is opgelost:
cursor.execute("""
SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})
Gebruik OPTION (RECOMPILE) om slechte gecachede plannen te omzeilen
SQL Server cachet queryplannen op basis van de eerste set parameters die het ziet. Als de gegevensverdeling sterk varieert tussen aanroepen, kan het gecachte plan voor sommige waarden slecht presteren. Dit probleem, parameter sniffing genoemd, duikt vaak op als een query die "vroeger snel was" en plotseling seconden of minuten duurt.
OPTION (RECOMPILE)dwingt SQL Server om voor elke uitvoering een nieuw plan te bouwen, wat een effectieve directe oplossing is die je kunt implementeren zonder server-side wijzigingen. Het nadeel is dat er per aanroep enige compilatie-overhead is, maar voor query’s die zelden worden uitgevoerd of resultaatsets van wisselende grootte retourneren, zijn die kosten verwaarloosbaar vergeleken met de gevolgen van een slecht uitvoeringsplan.
Zodra je het probleem hebt gestabiliseerd, kun je de tijd nemen om een permanente oplossing toe te passen, zoals het herschrijven van de query, het toevoegen van gefilterde indexen of het gebruik van plangidsen:
cursor.execute("""
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate > %(start_date)s
OPTION (RECOMPILE)
""", {"start_date": start_date})
Prestatiebewaking
Tijd je zoekopdrachten
Om langzame operaties te vinden, wikkel je queries in met 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")
Voor een breder beeld van waar je applicatie tijd doorbrengt, gebruik de ingebouwde cProfile module van Python:
python -m cProfile -s cumtime my_app.py
Deze weergave toont de cumulatieve tijd per functieaanroep, wat helpt te bepalen of traagheid zit in query-uitvoering, dataverwerking of netwerklatentie.
Gebruik Query Store voor server-side analyse
Client-side timing geeft aan hoe lang een query duurt vanuit het perspectief van je applicatie, maar combineert netwerklatentie, serveruitvoeringstijd en clientverwerking. Query Store legt uitvoeringsplannen en runtime-statistieken op de server vast, zodat je precies kunt zien hoe SQL Server elke query uitvoerde, hoe vaak deze werd uitgevoerd en hoe de prestaties in de loop van de tijd veranderden.
Query Store is vooral nuttig voor het identificeren van parametersniffing, planregressies en queries die de meeste serverbronnen verbruiken. Je kunt direct de sys.query_store_runtime_stats en sys.query_store_plan weergaven opvragen, of de ingebouwde Query Store-rapporten in SQL Server Management Studio gebruiken.
Gebruik Performance Dashboard-rapporten
De Performance Dashboard-rapporten in SQL Server Management Studio bieden een realtime overzicht van de gezondheid van SQL Server, inclusief huidige wachttypen, actieve dure queries en CPU/IO-trends. Gebruik ze om snel knelpunten te ontdekken zonder rechtstreeks query's op de DMV's te schrijven.
Controlelijst voor prestaties
Verbinding
- [ ] Schakel verbindingspooling in.
- [ ] Stem de grootte van de pool af op je werklast.
- [ ] Hergebruik verbindingen binnen operaties.
- [ ] Houd verbindingen open bij langlopende diensten.
Queries
- [ ] Selecteer alleen de kolommen die je nodig hebt.
- [ ] Gebruik de juiste fetch-methode voor elke query.
- [ ] Implementeer server-side paginering.
- [ ] Stel
SET NOCOUNT ONeenmaal in nadat u verbinding hebt gemaakt. - [ ] Beperk het aantal heen-en-weeraanvragen door query’s te bundelen.
Invoegingen
- [ ] Gebruik
execute()voor inzetstukken met één rij. - [ ] Gebruik
executemany()voor kleine tot matige batches (~10-1.000 rijen). - [ ] Gebruik
bulkcopy()wanneer doorvoer belangrijker is dan controle per rij.
Cachebeheer
- [ ] Cache referentiegegevens met een TTL om te voorkomen dat verouderde resultaten worden geleverd.
Resources
- [ ] Verwerk grote resultaten in stukken of met generatoren.
- [ ] Maak de verbindingen snel schoon.
- [ ] Monitor het geheugengebruik met
tracemallocin langlopende diensten.