Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az mssql-python illezser számos funkciót és mintát kínál az SQL Server alkalmazás teljesítményének optimalizálására, beleértve a kapcsolati poolinget, lekérdezések optimalizálását és tömeges műveleteket.
Kapcsolatkezelés
Használjon kapcsolat-kötegelést
A csatlakozási csoportosítás beépített állapotban van. Amikor meghívod a conn.close() függvényt, a kapcsolat ahelyett, hogy megszűnne, visszakerül a készletbe, hogy újra fel lehessen használni, így a későbbi connect() hívásoknál nem kell végrehajtani a költséges kézfogást:
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()
A medence méretének konfigurálása a munkaterheléshez
Állítsd be a medence méretét a párhuzamos követelmények alapján. Ha az alkalmazásod egyszerre sok felhasználót kezel, növeld a készletet. Könnyebb munkaterheléseknél a kisebb pool spórolja a szerver erőforrásait:
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
)
Kapcsolatok újrahasznosítása műveleteken belül
Minden lekérdezéshez új kapcsolat megnyitása még a pooling mellett is növeli a terhelést. Ehelyett egyetlen kapcsolatot tartsunk egy logikus művelet időtartamára:
# 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()
Tartsd nyitva a kapcsolatokat a hosszú távú szolgáltatásokban
A webszervereknek, sorba kezelőknek és folyamatosan futó ütemezett feladatoknak nyitva kell tartaniuk a kapcsolatokat, nem pedig minden műveletnél csatlakozni vagy megszakítani. A kapcsolat megnyitása TCP kézfogást, TLS tárgyalást és hitelesítést igényel, ami 50-200 ms időt igényelhet a hálózati távolságtól és hitelesítési módszertől függően. Egy sorkezelő számára, aki óránként több ezer üzenetet dolgoz fel, ez a többletköltség gyorsan összeadódik.
Tartsd nyitva a kapcsolatot a dolgozó életének végéig, és akkor kapcsold újra, ha a kapcsolat megszűnik. Alvás az iterációk között, hogy elkerüld a szerver elnyomását, amikor a sor üres:
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()
Ha a kapcsolatkészlet használata engedélyezve van (ez az alapértelmezett beállítás), a készlet kezeli Ön helyett az inaktív kapcsolatokat. De ha letiltod a kapcsolat-összevonást, vagy egyetlen dedikált kapcsolatot használsz, a kapcsolati karakterláncban állítsd be a Connection Timeout és Command Timeout értékét, hogy időben észlelhesd az elavult kapcsolatokat ahelyett, hogy a kapcsolat megakadna.
Lekérdezésoptimalizálás
Csak a szükséges adatok lekérése
Ha csak az alkalmazás által használt oszlopokat választod, az csökkenti a hálózati átvitelt, a memóriafogyasztást és a lekérdezések végrehajtásának idejét.
# 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})
Használj megfelelő fetch módszereket
Az illesztőprogram több lekérési metódust biztosít. Használd azt, ami megfelel az eredményméretednek:
-
fetchval()egyetlen skaláris értéket ad vissza, minimális túlterheléssel. -
fetchall()betölti az egész eredményhalmazt memóriába, ami jól működik kis tábláknál. -
fetchmany(n)csomagokban kéri meg a sorokat, így a memóriahasználat állandó marad nagy eredményhalmazok esetén.
A megfelelő fetchmany() kötegméret a sorszélességtől függ. Szűk sorok esetén (néhány kis oszlop, nagyjából 1 KB darabonként) 1 000 sor minden adag körülbelül 1 MB memóriát tart. Szélesebb soroknál, nagy húrokkal vagy bináris oszlopokkal kisebb tételméretet használj. Kezdj 1000-vel, és igazítsd az adataid alapján.
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)
Szerveroldali lapozás használata
Ahelyett, hogy minden sort lekérnél és Pythonban szeletelnéd, használd a(z) OFFSET/FETCH NEXT elemet, hogy csak a szükséges oldalt kérd le.
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()
Használja a SET NOCOUNT ON opciót
Alapértelmezés szerint az SQL Server minden DML utasítás után "sorok érintett" üzenetet küld.
SET NOCOUNT ON elnyomja ezeket az üzeneteket és csökkenti a hálózati forgalmat. Ez egy session szintű beállítás, tehát egyszer állítsd be a csatlakozás után, ahelyett, hogy minden lekérdezésbe beágyaznád.
# 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álaszd a megfelelő beszúrási módszert
A meghajtó három módot kínál az adatok beillesztésére, mindegyik más-más méretarányhoz igazodva:
| Módszer | Sorok száma | Miért |
|---|---|---|
execute() |
1 sor hívásonként | Használja egysoros műveletekhez, például űrlapbeküldésekhez vagy API-kezelőkhöz, ahol azonnal szüksége van a beszúrt azonosítóra. |
executemany() |
~10-1000 sor | Oszloponkénti paraméterkötést használ jobb áteresztőképesség érdekében, mint egy hurok. Minden sort paraméterezett állításként küld. |
bulkcopy() |
Több száz sor vagy annál több | A TDS tömeges beszúrási protokollt használja, amely jelentősen hatékonyabb, mint a soronként történő beszitelések. A legjobb adatterheléshez, migrációkhoz és kötött feldolgozáshoz. |
További részletekért és példákért lásd: Data loading and movement patterns.
Egyes beszúrások az execute() használatával
Használja ezt egyszeri beszúrásokhoz, amikor azonnali eredményre van szükség. A(z) Production.Product több alapértelmezett érték nélküli NOT NULL oszloppal rendelkezik, így az INSERT utasítás mindegyiket felsorolja:
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()
Kötegelt beszúrások az executemany() használatával
executemany() oszloponként köti meg a paramétereket, és hatékonyan küldi őket. Használd közepes méretű kötegekhez ahelyett, hogy egy ciklusban hívnád meg a execute() elemet. Fontos megjegyezni, hogy a(z) executemany() pozicionális ? jelölőket igényel egy tuple-öket tartalmazó listával, míg a(z) execute() a(z) ? és a névvel ellátott %(name)s paramétereket is támogatja szótárakkal. Részletekért lásd a Paraméterezett lekérdezéseket az egyes stílusokról.
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()
Tömeges másolás nagy terheléshez
Ha az áteresztőképesség fontosabb, mint a soronkénti vezérlés, váltson a(z) bulkcopy() használatára. A sorokat a TDS tömeges beszúrási protokollon keresztül továbbítja, és elkerüli a paraméterezett állítások soronkénti túlterhelését. Az a pontos határ, ahol a bulkcopy() jobban teljesít, mint a executemany(), a sorok szélességétől és a hálózati késleltetéstől függ, de jellemzően néhány száz sornál van. Nagyon kis kötegek esetén a executemany() egyszerűbb, mert a bulkcopy() külön belső kapcsolatot nyit, és automatikusan véglegesít.
Ellentétben execute() és executemany(), bulkcopy() az értékeket oszlopokhoz hely szerint képezi le, nem oszloplista INSERT alapján. Add meg a(z) column_mappings paramétert a betöltés céloszlopainak megadásához, hogy a forrásrekordok a megfelelő oszlopokhoz igazodjanak, ne pedig a tábla elején lévő identitásoszlophoz:
result = cursor.bulkcopy(
"Production.Product",
rows,
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")
Nagyon nagy terhelés esetén használjon generátort, hogy elkerülje a teljes adathalmaz memóriába töltését, és állítsa be a batch_size értékét úgy, hogy a véglegesítés időszakosan történjen:
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,
)
Gyorsítótározási stratégiák
Olyan referenciaadatoknál, amelyek ritkán változnak (kategóriák, keresőtáblák, konfigurációk), gyorstöltsd be az eredményeket az alkalmazásodban, ahelyett, hogy minden kérésre lekérdeznél.
A Python functools.lru_cache egyszerű memoizálást biztosít, de határozatlan ideig gyorsalogtárban tárolja, amíg a folyamat újraindul. Ha az alapul szolgáló adatok változhatnak, cachetools.TTLCache használd automatikus frissítést egy időkorlát után:
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()
Hálózatoptimalizálás
Minimalizáld a oda-vissza utakat
Minden lekérdezés hálózati vissza-vissza a szerverhez. Egyesítsd a kapcsolódó lekérdezéseket egyetlen adagba, és használd nextset() az eredményhalmazok előrehaladásához:
# 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()
Szerveroldali feldolgozás használata összetett logikához
Told az aggregálást és szűrést az SQL Server-re, ahelyett, hogy nyers sorokat keresnél és Python-ban dolgoznád fel. A szerver egyetlen összefoglaló sort ad vissza a potenciálisan több ezer részlet sor helyett:
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})
Kerüld az egymásba ágyazott kurzorműveleteket.
Az mssql-python illesztőprogram nem támogatja a Több Aktív Eredményhalmazt (MARS). Csak egy kurzor képes aktív lekérdezést biztosítani egy kapcsolatonként. Szerezd be teljesen az első eredményhalmazt, mielőtt a következő lekérdezést futtatnád, vagy használj egy második kapcsolatot:
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()
Memóriakezelés
Nagy eredmények feldolgozása részekben
Egy többmillió soros tábla betöltése egy listába memóriát fogyaszt, ami arányos a teljes eredményhalmazral. Használd a OFFSET és FETCH NEXT elemeket az adatok közötti szerveroldali lapozáshoz, és egyszerre egy adatrészletet dolgozz fel.
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
)
Generátorokat használj streaminghez
A fetchmany() köré épített Python-generátor állandó szinten tartja a memóriahasználatot, a tábla méretétől függetlenül. A hívó sorról sorra iterál anélkül, hogy betöltené a teljes eredményhalmazt. Különösen nagy forrás esetén kombináld a táblázatokat UNION ALL , és ugyanúgy streameld az egyesített eredményt.
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")
Gyorsan takarítsd ki az erőforrásokat
A záratlan kapcsolatok megkötik a szerver erőforrásait, és kimeríthetik a kapcsolati poolt. Használj egy kontextuskezelőt, hogy garantáld a takarítást még akkor is, ha kivételek előfordulnak.
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()
Memóriahasználat figyelése
Nagy eredményhalmazok, hosszú élettartamú gyorsítótárak és kapcsolati objektumok mind memóriát fogyasztanak. Ha az alkalmazásod szolgáltatásként fut, a nem lezárt kurzorok vagy a nem korlátozott méretű gyorsítótárak okozta memóriaszivárgások idővel azt eredményezhetik, hogy az operációs rendszer vagy a konténer-futtatókörnyezet leállítja a folyamatot.
Használd tracemalloc a Python modulját a memória pillanatképére és a legnagyobb allokációk megtalálására.
import tracemalloc
tracemalloc.start()
# ... run your workload ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
A váratlan memórianövekedés gyakori forrásai a következők:
- Egy
fetchall()lekérdezést hívok, amely milliónyi sorokat ad vissza. Használjfetchmany()vagy inkább generátort. - Lekérdezési eredmények gyorsítótárazása
maxsizevagy TTL nélkül. A gyorsítótárak egyre nagyobbak lesznek, amíg újra nem indítják a folyamatot. - A kurzorok létrehozása egy hurokban anélkül, hogy bezárnánk őket. Minden nyitott kurzor a memóriában tárolja az eredményhalmazát.
Index- és lekérdezésterv optimalizálása
Szerveroldali lekérdezés teljesítményének ellenőrzése
Használd SET STATISTICS TIME ON és SET STATISTICS IO ON nézd, mennyi ideig tartanak a lekérdezések a szerveren, és mennyi adatot olvasnak. A magas logikai olvasások általában hiányzó indexet jeleznek. Futtatjuk ezeket az utasításokat az SQL Server Management Studio-ban vagy a Visual Studio Code MSSQL kiterjesztésében, ahol a kimenet megjelenik az Üzenetek panelben:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Ilyen kimenetet kell látnod:
Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Ha magas logikai olvasásokat vagy táblázatszkennelést látsz, fontold meg indexet hozzáadni.
Használj lekérdezési tippeket taktikai megoldásként
A lekérdezési tippek felülírják a lekérdezésoptimalizáló indexét és csatlakoznak a stratégia választásokhoz. A gyártásban értékesek, mint gyors, alacsony kockázatú javítás, amikor egy lekérdezés hirtelen visszaesik. Azonnal telepítheted a tippet az alkalmazáskódodban, hogy stabilizáld a lekérdezést, miközben a gyökérokot (hiányzó indexek, elavult statisztikák vagy sémaváltozások) vizsgálod.
Kerüld, hogy a jeleket örökre hagyd a helyén. Amikor az adatelosztás vagy séma változik, egy kódolt utalás ronthatja a helyzetet. Kezeld őket ideiglenesen, és nézd vissza, miután a mögöttes probléma megoldódott:
cursor.execute("""
SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})
Használd az OPTION (RECOMPILE) funkciót a rossz gyorsítótáros tervek megkerülésére
Az SQL Server a lekérdezési terveket az általa elsőként látott paraméterérték-készlet alapján gyorsítótárban tárolja. Ha az adateloszlás jelentősen eltér a hívások között, a gyorsítótárazott végrehajtási terv bizonyos értékek esetén gyengén teljesíthet. Ezt a problémát, amelyet paraméterleszimatolásnak neveznek, gyakran az jelzi, hogy egy korábban gyors lekérdezés hirtelen másodpercekig vagy percekig fut.
OPTION (RECOMPILE)Ez arra kényszeríti az SQL Server-et, hogy minden végrehajtáshoz új tervet készítsen, ami hatékony, azonnali javítás, amit szerveroldali változtatások nélkül is telepíthetsz. Ennek ára hívásonként egy csekély fordítási költség, de a ritkán futó, illetve változó méretű eredményhalmazt visszaadó lekérdezések esetén ez a költség elhanyagolható egy rossz végrehajtási terv futtatásának költségéhez képest.
Miután stabilizáltad a problémát, időt szánhatsz egy végleges megoldás alkalmazására, például újraírd a lekérdezést, szűrt indexeket vagy használhatsz tervútmutatókat:
cursor.execute("""
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate > %(start_date)s
OPTION (RECOMPILE)
""", {"start_date": start_date})
Teljesítményfigyelés
Időzítsd a lekérdezéseidet
A lassú műveletek megtalálásához csomagoljuk a lekérdezéseket a következőkkel 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")
Ha szélesebb képet kapsz arról, hol tölti az időt, használd a Python beépített cProfile modulját:
python -m cProfile -s cumtime my_app.py
Ez a nézet függvényhívásonként összesített időt mutat, ami segít azonosítani, hogy a lassulás lekérdezés végrehajtásában, adatfeldolgozásban vagy hálózati késleltetésben van-e.
Használd a Query Store-t szerveroldali elemzéshez
Az ügyféloldali időzítés megmutatja, mennyi ideig tart egy lekérdezés az alkalmazásod szemszögéből, de kombinálja a hálózati késleltetést, a szerver végrehajtási idejét és az ügyfélfeldolgozást. A Query Store rögzíti a végrehajtási terveket és a futtatóidejű statisztikákat a szerveren, így pontosan láthatod, hogyan hajtotta végre az SQL Server az egyes lekérdezéseket, milyen gyakran futott, és hogyan változott a teljesítménye az idők során.
A Query Store különösen hasznos a paraméter szimatolása, a tervregressziók és a legtöbb szervererőforrást fogyasztó lekérdezések azonosítására. Közvetlenül is lekérdezheted a sys.query_store_runtime_stats és sys.query_store_plan nézeteket, vagy használhatod az SQL Server Management Studio beépített Query Store jelentéseit.
Használd a Performance Dashboard jelentéseket
Az SQL Server Management Studio Performance Dashboard Reports valós idejű áttekintést nyújt az SQL Server állapotáról, beleértve a jelenlegi várakozási típusokat, az aktív drága lekérdezéseket és a CPU/IO trendeket. Használatukkal gyorsan azonosíthatók a szűk keresztmetszetek anélkül, hogy közvetlenül a DMV-kre kellene lekérdezéseket írni.
Teljesítmény-ellenőrzőlista
Kapcsolat
- [ ] Kapcsold be a kapcsolat poolingát.
- [ ] Méretezd a medencét a munkaterhelésedhez.
- [ ] A kapcsolatok újrafelhasználása a műveleteken belül.
- [ ] Tartsd nyitva a kapcsolatokat a hosszú távú szolgáltatásokban.
Queries
- [ ] Csak azokat az oszlopokat válaszd ki, amire szükséged van.
- [ ] Használd a megfelelő fetch módszert minden lekérdezéshez.
- [ ] Szerveroldali oldali lapozás bevezetése.
- [ ] Egyszer állítsd
SET NOCOUNT ONbe a csatlakozás után. - [ ] Minimalizáld a oda-vissza utakat a lekérdezések csoportos sorolásával.
Beillesztés
- [ ] Használja a(z)
execute()elemet egysoros beszúrásokhoz. - [ ] Kis vagy közepes adagokhoz (~10-1000 sor) használjuk
executemany(). - [ ] Használd
bulkcopy(), amikor az áteresztőképesség fontosabb, mint a soronkénti vezérlés.
Gyorsítótár
- [ ] Tárold gyorsítótárban a referenciaadatokat TTL-lel, hogy elkerüld az elavult eredmények kiszolgálását.
Resources
- [ ] Nagy eredményeket dolgozz fel darabokban vagy generátorokkal.
- [ ] Gyorsan takarítsd meg a kapcsolatokat.
- [ ] Kísérje figyelemmel a memóriahasználatot a(z)
tracemallochasználatával hosszú ideig futó szolgáltatásokban.