Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
mssql-python sürücüsü, bağlantı havuzu, sorgu optimizasyonu ve toplu işlemler dahil olmak üzere SQL Server uygulama performansını optimize etmek için çeşitli özellikler ve desenler sunar.
Bağlantı yönetimi
Bağlantı havuzunu kullanın
Bağlantı havuzu dahili olarak hazırlanmış. Çağırdığınızda conn.close(), bağlantı yok olmak yerine yeniden kullanılmak üzere havuza geri döner, bu yüzden sonraki connect() çağrılar pahalı el sıkışmayı atlar:
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()
Iş yükü için havuz boyutunu yapılandır
Havuz boyutunu eşzamanlı gereksinimlerinize göre ayarlayın. Uygulamanız birçok eşzamanlı kullanıcıyı yönetiyorsa, havuzu artırın. Daha hafif iş yükleri için, daha küçük bir havuz sunucu kaynaklarını tasarruf eder:
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
)
Bağlantıları operasyonlar içinde yeniden kullanın
Her sorgu için yeni bir bağlantı açmak, havuzlama yaparken bile ek yük artırıyor. Bunun yerine, mantıklı bir işlem süresi boyunca tek bir bağlantıyı tutun:
# 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()
Uzun süreli hizmetlerde bağlantıları açık tutun
Web sunucuları, kuyruk çalışanları ve sürekli çalışan planlı işler, her işlemde bağlantı kurup kopmak yerine bağlantıları açık tutmalı. Bağlantı açmak, TCP el sıkışması, TLS müzakere ve kimlik doğrulama gerektirir; bu da ağ mesafesi ve doğrulama yöntemine bağlı olarak 50-200 ms sürebilir. Saatte binlerce mesajı işleyen bir kuyruk çalışanı için bu yük hızla toplanır.
Bağlantıyı çalışanın ömrü boyunca açık tutun ve bağlantı kesildiğinde yeniden bağlayın. Kuyruk boşken sunucuya gereksiz yük bindirmemek için yinelemeler arasında bekleyin:
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()
Bağlantı havuzu etkinleştirildiğinde (varsayılan), havuz boşta bağlantıları sizin için yönetiyor. Ancak havuzlamayı devre dışı bırakırsanız veya tek bir özel bağlantı kullanırsanız, takılı kalmak yerine eskimiş bağlantıları erken algılamak için bağlantı dizenizde Connection Timeout ve Command Timeout değerlerini ayarlayın.
Sorgu iyileştirme
Sadece gerekli verileri getir
Sadece uygulamanızın kullandığı sütunları seçmek, ağ transferini, bellek tüketimini ve sorgu yürütme süresini azaltır.
# 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})
Uygun getirme yöntemlerini kullanın
Sürücü birkaç getirme yöntemi sunar. Sonuç boyutunuzla eşleşen olanı kullanın:
-
fetchval()minimum yükle tek bir skaler değer döndürür. -
fetchall()Tüm sonuç setini belleğe yükler, bu da küçük tablolar için iyi çalışır. -
fetchmany(n), büyük sonuç kümelerinde bellek kullanımını sabit tutarak satırları toplu olarak alır.
Doğru parti büyüklüğü fetchmany() sıra genişliğine bağlıdır. Dar satırlar için (birkaç küçük sütun, her biri yaklaşık 1 KB), 1.000 satır, her partide yaklaşık 1 MB bellek tutar. Uzun dizelere veya binary sütunlara sahip daha geniş satırlar için daha küçük bir parti boyutu kullanın. 1.000 ile başlayın ve verilerinize göre ayarlayı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)
Sunucu tarafı sayfalamayı kullanın
Tüm satırları getirip Python'da dilimlemek yerine, sadece ihtiyacınız olan sayfayı almak için kullanınOFFSET/FETCH NEXT.
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()
Kullan SET NOCOUNT AÇIK
Varsayılan olarak, SQL Server her DML ifadesinden sonra "satırlar etkilendi" mesajı gönderir.
SET NOCOUNT ON bu mesajları bastırır ve ağ trafiğini azaltır. Bu oturum düzeyinde bir ayar, bağlantı kurduktan sonra her sorguya gömmek yerine bir kez ayarlayın.
# 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"}
)
Doğru yerleştirme yöntemini seçin
Sürücü, veri eklemek için üç yol sunar ve her biri farklı ölçeklere uygundur:
| Method | Satır sayısı | Neden? |
|---|---|---|
execute() |
Her çağrı için 1 sıra | Form gönderimleri veya API işleyicileri gibi tek satırlı işlemlerde kullanın; burada hemen eklenmiş kimlik gerekiyor. |
executemany() |
~10-1.000 sıra | Döngüye göre daha iyi aktarım için sütun bazında parametre bağlaması kullanır. Her satırı parametreli ifade olarak gönderir. |
bulkcopy() |
Yüzlerce sıra ve daha fazla | Satır satır eklemelerden çok daha verimli olan TDS toplu ekleme protokolünü kullanır. Veri yüklemeleri, göçler ve toplu işleme için en iyisi. |
Daha fazla detay ve örnek için Veri yükleme ve hareket kalıpları sayfasına bakınız.
execute() kullanarak tekli ekleme işlemleri
Sonucu hemen almanız gereken tek seferlik insertler için kullanın.
Production.Product varsayılan olmayan birkaç NOT NULL sütunu vardır, bu yüzden ekleme hepsini listeler:
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()
executemany() ile toplu eklemeler
executemany() parametreleri sütun bazında bağlar ve verimli şekilde gönderir.
execute() öğesini, bir döngü içinde çağırmak yerine orta büyüklükteki toplu işlemler için kullanın.
executemany() için tuple listesinin yanı sıra konumsal ? işaretleyicilerin gerektiğini, buna karşılık execute() için hem ? hem de dict'lerle adlandırılmış %(name)s parametrelerin desteklendiğini unutmayın. Her stil hakkında ayrıntılı bilgi için Parametreli sorgular bölümüne bakın.
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()
Büyük veri yükleri için toplu kopyalama
İş hacmi satır başına kontrolden daha önemli olduğunda, bulkcopy() öğesine geçin. Satırları TDS toplu ekleme protokolü üzerinden akıtır ve parametreli ifadelerin satır başına ek yükünü ortadan kaldırır.
bulkcopy() öğesinin executemany() öğesinden daha iyi performans göstermeye başladığı tam eşik noktası, satır genişliğine ve ağ gecikmesine bağlıdır; ancak bu nokta genellikle birkaç yüz satırın alt düzeylerindedir. Çok küçük partiler için executemany(), daha basittir; çünkü bulkcopy() ayrı bir iç bağlantı oluşturur ve işlemi otomatik olarak kaydeder.
execute() ve executemany()'in aksine, bulkcopy() değerleri INSERT sütun listesine göre değil, konumlarına göre sütunlarla eşleştirir. Yüklediğiniz hedef sütunları adlandırmak için column_mappings ifadesini geçin; böylece kaynak tuple'ları, tablonun baştaki identity sütunu yerine doğru sütunlarla hizalanır:
result = cursor.bulkcopy(
"Production.Product",
rows,
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")
Çok büyük yüklemelerde, tüm veri kümesini belleğe yüklemekten kaçınmak için bir üreteç kullanın ve batch_size ayarını periyodik olarak işleme alacak şekilde yapılandırın:
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,
)
Önbellek stratejileri
Nadiren değişen başvuru verileri (kategoriler, arama tabloları, yapılandırma) için, her istekte sorgulamak yerine sonuçları uygulamanızda önbelleğe alın.
Python's functools.lru_cache basit bir hafıza sağlıyor, ancak süreç yeniden başlayana kadar süresiz önbellek yapıyor. Temel veri değişebiliyorsa, bir zaman sınırı sonrası otomatik olarak yenilemek için kullanın cachetools.TTLCache :
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()
Ağ iyileştirme
Gidiş-dönüş mesafelerini en aza indirin
Her sorgu, sunucuya ağ üzerinden bir gidiş-dönüş gerektirir. İlgili sorguları tek bir toplu halinde birleştirin ve sonuç kümelerinde ilerlemek için kullanın nextset() :
# 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()
Karmaşık mantık için sunucu tarafı işleme kullanın
Ham satırları getirip Python'da işlemek yerine toplama ve filtreleme işlemlerini SQL Server'a itin. Sunucu, potansiyel olarak binlerce detay satırı yerine tek bir özet satırı döner:
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})
İç içe geçmiş imleç işlemlerinden kaçının
mssql-python sürücüsü, Çoklu Aktif Sonuç Kümeleri (MARS) desteğini sağlamaz. Her bağlantı için sadece bir imleç aktif sorguya sahip olabilir. Bir sonraki sorguyu çalıştırmadan önce ilk sonuç setini tamamen getirin veya ikinci bir bağlantı kullanın:
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()
Bellek yönetimi
Büyük sonuçları parçalar halinde işlemek
Bir listeye milyonlarca satırlık bir tablo yüklemek, tam sonuç kümesiyle orantılı olarak bellek tüketir. Verileri sunucu tarafında sayfalamak ve her seferinde bir parçayı işlemek için OFFSET ve FETCH NEXT kullanın.
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
)
Yayın için jeneratörler kullanın
fetchmany() saran bir Python generator'ü, tablo boyutundan bağımsız olarak bellek kullanımını sabit tutar. Çağıran, tüm sonuç kümesini yüklemeden satır satır yineleme yapar. Çok büyük bir kaynak için, tabloları UNION ALL ile birleştirin ve birleştirilmiş sonucu aynı şekilde akış olarak iletin.
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")
Kaynakları hızlıca temizleyin
Kapatılmamış bağlantılar sunucu kaynaklarını meşgul eder ve bağlantı havuzunu tüketebilir. İstisnalar olsa bile temizliği garantilemek için bir bağlam yöneticisi kullanın.
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()
Bellek kullanımını izleyin
Büyük sonuç setleri, uzun ömürlü önbellekler ve bağlantı nesneleri hepsi belleği tüketir. Uygulamanız bir servis olarak çalışıyorsa, kapatılmamış imleçlerden veya sınırsız önbelleklerden gelen bellek sızıntıları sürecin işletim sistemi veya konteyner çalışma zamanı tarafından sona ermesine neden olabilir.
Python'un tracemalloc modülünü kullanarak belleği anlayabilir ve en büyük tahsisatları bulur.
import tracemalloc
tracemalloc.start()
# ... run your workload ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
Beklenmedik hafıza büyümesinin yaygın kaynakları şunlardır:
- Milyonlarca satır döndüren bir sorguda
fetchall()çağırmak. Bunun yerinefetchmany()veya bir jeneratör kullanın. -
maxsizeveya TTL olmadan sorgu sonuçlarını önbelleğe alma. Önbellekler, süreç yeniden başlatılana kadar büyümeye devam eder. - İmleci kapatmadan döngü halinde oluşturmak. Her açık imleç sonuç kümesini bellekte tutar.
İndeks ve sorgu planı optimizasyonu
Sunucu tarafı sorgu performansını kontrol et
Sunucuda sorguların ne kadar sürdüğünü ve ne kadar veri okuduklarını görmek için kullanınSET STATISTICS TIME ON.SET STATISTICS IO ON Yüksek mantıklı okumalar genellikle eksik bir indeks olduğunu gösterir. Bu ifadeleri SQL Server Management Studio'da veya MSSQL uzantısında Visual Studio Code için çalıştırın; çıktı Mesajlar panelinde görünür:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Aşağıdaki gibi bir çıkış görmeniz gerekir:
Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Yüksek mantıksal okumalar veya tablo taramaları görürseniz, bir indeks eklemeyi düşünün.
Taktik bir çözüm olarak sorgulama ipuçlarını kullanın
Sorgu ipuçları, sorgu optimizatorunun indeksini geçersiz kılır ve strateji seçimlerini birleştirir. Üretimde, bir sorgunun performansı aniden düştüğünde hızlı ve düşük riskli bir çözüm olarak değerli olabilirler. İpucunu, uygulama kodunuzda hemen yerleştirerek sorguyu stabilize edebilir ve temel nedeni (eksik indeksler, bayat istatistikler veya şema değişiklikleri) araştırabilirsiniz.
İpuçlarını kalıcı olarak yerinde bırakmaktan kaçının. Veri dağılımı veya şema değiştiğinde, sabit kodlanmış bir ipucu durumu daha da kötüleştirebilir. Bunları geçici olarak değerlendirin ve temel sorun çözüldükten sonra tekrar görüşün:
cursor.execute("""
SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})
Kötü önbellekli planları atlamak için OPTION (RECOMPILE) kullanın
SQL Server, gördüğü ilk parametre değer setine göre sorgu planlarını önbellekler. Çağrılar arasında veri dağılımı büyük farklılıklar gösterirse, önbelleğe alınan plan bazı değerler için kötü performans gösterebilir. Parameter sniffing adı verilen bu sorun, genellikle önceden hızlı çalışan bir sorgunun aniden saniyeler hatta dakikalar sürmeye başlamasıyla ortaya çıkar.
OPTION (RECOMPILE)SQL Server'ı her çalıştırma için taze bir plan oluşturmaya zorlar; bu, sunucu tarafında herhangi bir değişiklik olmadan hemen uygulanabilecek etkili bir çözümdür. Karşılığı, çağrı başına küçük bir derleme maliyeti olsa da, nadiren çalışan veya değişken boyutlu sonuç setleri getiren sorgularda bu maliyet kötü bir plan çalıştırmaya kıyasla önemsizdir.
Sorunu sabitledikten sonra, sorguyu yeniden yazmak, filtrelenmiş indeksler eklemek veya plan rehberleri kullanmak gibi kalıcı bir çözüm uygulamak için zamanınızı alabilirsiniz:
cursor.execute("""
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate > %(start_date)s
OPTION (RECOMPILE)
""", {"start_date": start_date})
Performans izleme
Sorularınızı zamanlayın
Yavaş işlemleri bulmak için sorguları time.perf_counter() ile sarmalayın:
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")
Uygulamanızın nerede zaman geçirdiğine dair daha geniş bir bakış için, Python'cProfileun yerleşik modülünü kullanın:
python -m cProfile -s cumtime my_app.py
Bu görünüm, işlev çağrısı başına birikimli zamanı gösterir; bu da yavaşlığın sorgu yürütme, veri işleme mi yoksa ağ gecikmesi mi olduğunu belirlemenize yardımcı olur.
Sunucu tarafı analizi için Query Store'u kullanın
İstemci tarafı zamanlama, uygulamanızın bakış açısından sorgu süresinin ne kadar sürdüğünü gösterir, ancak ağ gecikmesi, sunucu yürütme süresi ve istemci işlemesini birleştirir. Query Store, sunucudaki yürütme planlarını ve çalışma zamanı istatistiklerini yakalar, böylece SQL Server'ın her sorguyu nasıl çalıştırdığını, ne sıklıkla çalıştığını ve performansının zamanla nasıl değiştiğini tam olarak görebilirsiniz.
Query Store, özellikle parametre koklama, plan regresyonları ve en çok sunucu kaynaklarını tüketen sorguları tespit etmek için faydalıdır.
sys.query_store_runtime_stats ve sys.query_store_plan görünümlerini doğrudan sorgulayabilir veya SQL Server Management Studio içindeki yerleşik Query Store raporlarını kullanabilirsiniz.
Performans Gösterge Paneli Raporlarını Kullanın
SQL Server Management Studio'daki Performans Paneli Raporları, mevcut bekleme türleri, aktif pahalı sorgular ve CPU/IO trendleri dahil olmak üzere SQL Server sağlığına gerçek zamanlı bir bakış sunar. DMV'lere doğrudan sorgu yazmadan darboğazları hızlıca tespit etmek için kullanın.
Performans denetim listesi
Bağlantı
- [ ] Bağlantı havuzunu etkinleştir.
- [ ] Havuzu iş yükünüze göre ölç.
- [ ] Operasyonlar içinde bağlantıları yeniden kullanın.
- [ ] Uzun süreli hizmetlerde bağlantıları açık tutun.
Queries
- [ ] Sadece ihtiyacınız olan sütunları seçin.
- [ ] Her sorgu için uygun getirme yöntemini kullanın.
- [ ] Sunucu tarafı sayfalamayı uygula.
- [ ] Bağlandıktan sonra bir kez ayarla
SET NOCOUNT ON. - [ ] Sorguları toplu işleyerek gidiş gelişleri en aza indirin.
Eklemeler
- [ ] Tek satırlı eklemeler için
execute()kullanın. - [ ] Küçük ila orta ölçekli partiler (~10-1.000 satır) için
executemany()kullanın. - [ ] Verim, satır başına kontrolden daha önemli olduğunda
bulkcopy()kullanın.
Caching
- [ ] Eski sonuçların sunulmaması için referans veriyi TTL ile önbellekleyin.
Resources
- [ ] Büyük sonuçları parçalar halinde veya jeneratörlerle işleyin.
- [ ] Bağlantıları hemen temizleyin.
- [ ] Uzun süre çalışan servislerde bellek kullanımını
tracemallocizleyin.