Prestandaoptimering för mssql-python-applikationer

mssql-python-drivrutinen erbjuder flera funktioner och mönster för att optimera SQL Server-applikationsprestanda, inklusive anslutningspooling, frågeoptimering och bulkoperationer.

Anslutningshantering

Använd anslutningspoolning

Anslutningspoolning är inbyggt. När du anropar conn.close() returneras anslutningen till poolen för att återanvändas i stället för att förstöras, så att efterföljande connect()-anrop hoppar över den kostsamma handskakningen:

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

Konfigurera poolstorlek för arbetsbelastning

Justera poolstorleken baserat på dina samtidighetskrav. Om din applikation hanterar många samtidiga användare, öka poolen. För lättare arbetsbelastningar sparar en mindre pool serverresurser:

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
)

Återanvända anslutningar inom operationer

Att öppna en ny anslutning för varje fråga medför extra overhead även med anslutningspoolning. Håll istället en enda anslutning under hela en logisk operation:

# 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()

Håll anslutningarna öppna i långvariga tjänster

Webbservrar, köarbetare och schemalagda jobb som körs kontinuerligt bör hålla anslutningarna öppna istället för att ansluta och koppla bort vid varje operation. Att öppna en anslutning innebär ett TCP-handshake, TLS-förhandling och autentisering, vilket kan ta 50–200 ms beroende på nätverksavstånd och autentiseringsmetod. För en köarbetare som bearbetar tusentals meddelanden per timme blir den överbelastningen snabb.

Håll anslutningen öppen under arbetarens livstid och återanslut när anslutningen bryts. Vila mellan iterationer för att undvika att servern blir överbelastad när kön är tom:

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

Med anslutningspooling aktiverad (standard) hanterar poolen lediga anslutningar åt dig. Men om du inaktiverar pooling eller använder en enda dedikerad anslutning ska du ange Connection Timeout och Command Timeout i din anslutningssträng för att upptäcka inaktuella anslutningar tidigt i stället för att anslutningen låser sig.

Frågeoptimering

Hämta bara nödvändiga data

Att endast välja kolumner som din applikation använder minskar nätverksöverföring, minnesförbrukning och exekveringstid för frågor.

# 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})

Använd lämpliga hämtningsmetoder

Drivrutinen erbjuder flera hämtningsmetoder. Använd den som matchar din resultatstorlek:

  • fetchval() returnerar ett enda skalärt värde med minimal overhead.
  • fetchall() laddar hela resultatuppsättningen i minnet, vilket fungerar bra för små tabeller.
  • fetchmany(n) hämtar rader i batcher och håller minnesanvändningen konstant för stora resultatmängder.

Den rätta satsstorleken för fetchmany() beror på radbredden. För smala rader (några små kolumner, ungefär 1 KB vardera) håller 1 000 rader varje batch runt 1 MB minne. För bredare rader med stora strängar eller binära kolumner, använd en mindre batchstorlek. Börja med 1 000 och justera baserat på dina 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)

Använd serverbaserad sidindelning

Istället för att hämta alla rader och dela upp dem i Python använder du OFFSET/FETCH NEXT för att bara hämta den sida du behöver.

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

Använd SET NOCOUNT ON

Som standard skickar SQL Server ett meddelande om "rader påverkade" efter varje DML-sats. SET NOCOUNT ON undertrycker dessa meddelanden och minskar nätverkstrafiken. Det är en inställning på sessionsnivå, så ställ in den en gång efter anslutning i stället för att inkludera den i varje fråga.

# 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"}
)

Välj rätt insättningsmetod

Drivrutinen erbjuder tre sätt att infoga data, var och en anpassad för olika skalor:

Metod Antal rader Varför
execute() 1 rad per anrop Använd för enradsoperationer som formulärinlämningar eller API-hanterare där du behöver det insatta ID:t omedelbart.
executemany() ~10–1 000 rader Använder kolumnvis parameterbindning för bättre genomströmning än en loop. Skickar varje rad som ett parameteriserat uttalande.
bulkcopy() Hundratals rader eller fler Använder TDS-protokollet för massinfogning, vilket är betydligt effektivare än infogningar rad för rad. Bäst för datainläsningar, migreringar och batchbearbetning.

Mer information och exempel finns i Mönster för datainläsning och dataförflyttning.

Enskilda infogningar med execute()

Använd för enstaka infogningar där du behöver resultatet omedelbart. Production.Product har flera INTE NULL-kolumner utan standardinställningar, så infogningen listar alla:

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

Satsinfogningar med executemany()

executemany() binder parametrar kolumnvis och skickar dem effektivt. Använd den för måttliga batcher istället för att kalla execute() i en loop. Observera att executemany() kräver positionsmarkörer ? med en lista av tupler, medan execute() stöder både ? och namngivna %(name)s parametrar med dicts. Se Parameteriserade frågor för detaljer om varje stil.

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

Bulk-kopiering för stora laster

När genomströmningen är viktigare än kontroll per rad, byt till bulkcopy(). Den strömmar rader genom TDS bulk insert-protokollet och undviker den per-rad-överhead som parameteriserade satser innebär. Den exakta brytpunkten där bulkcopy() presterar bättre än executemany() beror på radens bredd och nätverkslatens, men den ligger vanligtvis någonstans i storleksordningen strax över hundra rader. För väldigt små batchar är executemany() enklare eftersom bulkcopy() skapar en separat intern anslutning och genomför en commit automatiskt.

Till skillnad från execute() och executemany() mappar bulkcopy() värden till kolumner efter position, inte med hjälp av en INSERT-kolumnlista. Passa column_mappings för att namnge destinationskolumnerna du laddar, så att källtuplerna linjerar med högra kolumnerna istället för tabellens ledande identitetskolumn:

result = cursor.bulkcopy(
    "Production.Product",
    rows,
    column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
                     "StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")

För mycket stora laster, använd en generator för att undvika att hela datamängden laddas in i minnet och ställ batch_size in att commita periodiskt:

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

Cachestrategier

För referensdata som sällan ändras (kategorier, uppslagstabeller, konfiguration), cacha resultaten i din applikation istället för att fråga på varje förfrågan.

Pythons functools.lru_cache erbjuder enkel memoisering, men cachelagrar utan tidsgräns tills processen har startats om. Om den underliggande datan kan ändras, använd cachetools.TTLCache den för att automatiskt uppdatera efter en tidsgräns:

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

Nätverksoptimering

Minimera tur-och-retur-resor

Varje fråga är en nätverkstur fram och retur till servern. Kombinera relaterade frågor till en enda batch och använd nextset() för att gå vidare genom resultatuppsättningarna:

# 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()

Använd serverbaserad bearbetning för komplex logik

Skicka aggregering och filtrering till SQL Server istället för att hämta råa rader och bearbeta dem i Python. Servern returnerar en enda sammanfattningsrad istället för potentiellt tusentals detaljrader:

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

Undvik sammanflätade marköroperationer

mssql-python-drivrutinen stöder inte Multiple Active Result Sets (MARS). Endast en markör kan ha en aktiv fråga per anslutning. Hämta den första resultatuppsättningen helt innan nästa fråga körs, eller använd en andra anslutning:

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

Minneshantering

Bearbeta stora resultat i bitar

Att ladda en tabell med flera miljoner rader i en lista förbrukar minne proportionellt mot hela resultatuppsättningen. Använd OFFSET och FETCH NEXT för att paginera genom datan på serversidan och bearbeta en bit i taget.

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
)

Använd generatorer för streaming

En Python-generatorwrapping fetchmany() håller minnesanvändningen konstant oavsett tabellstorlek. Anroparen itererar rad för rad utan att ladda hela resultatuppsättningen. För en extra stor källa, kombinera tabeller med UNION ALL och strömma det sammansatta resultatet på samma sätt.

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

Rensa resurser snabbt

Oavslutade anslutningar binder upp serverresurser och kan tömma anslutningspoolen. Använd en kontexthanterare för att garantera rensning även när undantag inträffar.

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

Övervaka minnesanvändning

Stora resultatmängder, långlivade cacher och anslutningsobjekt förbrukar alla minne. Om din applikation körs som en tjänst kan minnesläckor från oslutna markörer eller obegränsade cacher så småningom göra att processen dödas av operativsystemet eller containerns körtid.

Använd Python:s tracemalloc modul för att ta en snapshot av minnet och hitta de största allokeringarna.

import tracemalloc

tracemalloc.start()

# ... run your workload ...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
    print(stat)

Vanliga källor till oväntad minnestillväxt inkluderar:

  • Anropar fetchall() en fråga som returnerar miljontals rader. Använd fetchmany() eller en generator i stället.
  • Cachelagring av frågeresultat utan maxsize eller TTL. Cacher växer tills processen startas om.
  • Skapa markörer i en loop utan att stänga dem. Varje öppen markör lagrar sitt resultat i minnet.

Optimering av index och frågeplan

Kontrollera serversidans frågeprestanda

Använd SET STATISTICS TIME ON och SET STATISTICS IO ON se hur lång tid frågor tar på servern och hur mycket data de läser. Höga logiska läsningar indikerar vanligtvis ett saknat index. Kör dessa satser i SQL Server Management Studio eller MSSQL-tillägget för Visual Studio Code, där utdata visas i meddelandepanelen:

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;

SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;

Du bör se resultat som:

Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.

Om du ser höga logiska läsningar eller tabellskanningar, överväg att lägga till ett index.

Använd frågetips som en taktisk lösning

Frågeledtrådar åsidosätter frågeoptimerarens index och anslutningsstrategival. I produktion är de värdefulla som en snabb korrigering med låg risk när en fråga plötsligt försämras. Du kan omedelbart distribuera hinten i din applikationskod för att stabilisera frågan medan du undersöker grundorsaken (saknade index, föråldrad statistik eller schemaändringar).

Undvik att låta ledtrådar vara permanent aktiverade. När datadistributionen eller schemat ändras kan en hårdkodad ledtråd göra saken värre. Behandla dem som tillfälliga och ta upp dem igen efter att det underliggande problemet är löst:

cursor.execute("""
    SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
    WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})

Använd OPTION (REKOMPILERA) för att kringgå dåliga cachelagrade planer

SQL Server cachar frågeplaner baserat på den första uppsättningen parametervärden den ser. Om datafördelningen varierar mycket mellan samtal kan den cachade planen prestera dåligt för vissa värden. Detta problem, som kallas parameter sniffing, visar sig ofta i form av en fråga som "tidigare var snabb" men som plötsligt börjar ta sekunder eller minuter.

OPTION (RECOMPILE)tvingar SQL Server att bygga en ny plan för varje körning, vilket är en effektiv omedelbar lösning som du kan implementera utan några server-side-ändringar. Avvägningen är en liten kompileringskostnad per samtal, men för frågor som körs sällan eller returnerar variabelt stora resultatuppsättningar är den kostnaden försumbar jämfört med att köra en dålig plan.

När du har stabiliserat problemet kan du ta dig tid att tillämpa en permanent lösning, som att skriva om frågan, lägga till filtrerade index eller använda planguider:

cursor.execute("""
    SELECT * FROM Sales.SalesOrderHeader
    WHERE OrderDate > %(start_date)s
    OPTION (RECOMPILE)
""", {"start_date": start_date})

Prestandaövervakning

Tidsanpassa dina frågor

För att hitta långsamma operationer, slå in frågor med 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")

För en bredare bild av var din applikation spenderar tid, använd Python:s inbyggda cProfile modul:

python -m cProfile -s cumtime my_app.py

Denna vy visar kumulativ tid per funktionsanrop, vilket hjälper dig att identifiera om långsamheten ligger i frågeexekveringen, databehandlingen eller nätverkslatens.

Använd Query Store för serverbaserad analys

Klientsidans timing visar hur lång tid en fråga tar ur applikationens perspektiv, men den kombinerar nätverkslatens, serverexekveringstid och klientbearbetning. Query Store fångar exekveringsplaner och körningsstatistik på servern, så du kan se exakt hur SQL Server körde varje fråga, hur ofta den kördes och hur dess prestanda förändrades över tid.

Query Store är särskilt användbart för att identifiera parametersniffing, planregressioner och frågor som förbrukar mest serverresurser. Du kan fråga vyerna sys.query_store_runtime_stats och sys.query_store_plan direkt eller använda de inbyggda rapporterna för Query Store i SQL Server Management Studio.

Använd prestandadashboard-rapporter

Performance Dashboard Reports i SQL Server Management Studio ger en realtidsöversikt över SQL Server:s hälsa, inklusive aktuella väntetyper, aktiva dyra frågor och trender inom CPU/IO. Använd dem för att snabbt upptäcka flaskhalsar utan att behöva ställa frågor direkt mot DMV.

Checklista för prestanda

Anslutning

  • [ ] Aktivera anslutningspoolning.
  • [ ] Anpassa poolen efter din arbetsbörda.
  • [ ] Återanvänd anslutningar inom verksamheten.
  • [ ] Håll anslutningarna öppna i långvariga tjänster.

Queries

  • [ ] Välj bara de kolumner du behöver.
  • [ ] Använd lämplig hämtmetod för varje fråga.
  • [ ] Implementera paginering på serversidan.
  • [ ] Ställ SET NOCOUNT ON in en gång efter anslutning.
  • [ ] Minimera antalet turer genom att samla förfrågningar.

Infogningar

  • [ ] Använd execute() för infogningar på en rad.
  • [ ] Använd executemany() för små till måttliga batcher (~10–1 000 rader).
  • [ ] Använd bulkcopy() när genomströmningen är viktigare än kontroll per rad.

Cache

  • [ ] Cachereferera data med en TTL för att undvika att servera föråldrade resultat.

Resources

  • [ ] Bearbeta stora resultat i bitar eller med generatorer.
  • [ ] Städa upp anslutningarna snabbt.
  • [ ] Övervaka minnesanvändningen med tracemalloc i långvarigt körande tjänster.