Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O driver mssql-python fornece várias funcionalidades e padrões para otimizar o desempenho das aplicações SQL Server, incluindo pooling de ligações, otimização de consultas e operações em massa.
Gestão de ligações
Utilizar o pool de conexões
A gestão de ligações está integrada. Quando chamas conn.close(), a ligação regressa ao pool para reutilização em vez de ser destruída, por isso as chamadas subsequentes connect() saltam o aperto de mão caro:
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()
Configurar o tamanho do pool para a carga de trabalho
Ajusta o tamanho do grupo com base nos requisitos de concorrência. Se a sua aplicação gerir muitos utilizadores simultâneos, aumente o pool. Para cargas de trabalho mais leves, um pool mais pequeno poupa recursos do servidor:
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
)
Reutilize conexões nas operações
Abrir uma nova conexão para cada consulta adiciona sobrecarga, mesmo com pooling. Em vez disso, mantenha-se uma única ligação durante a duração de uma operação lógica:
# 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()
Mantenha as ligações abertas em serviços de longa duração
Servidores web, trabalhadores de fila e trabalhos agendados que correm continuamente devem manter as ligações abertas em vez de se ligar e desligar em todas as operações. Abrir uma ligação envolve um handshake TCP, negociação TLS e autenticação, que pode demorar entre 50 e 200 ms dependendo da distância da rede e do método de autenticação. Para um trabalhador de fila a processar milhares de mensagens por hora, essa sobrecarga acumula-se rapidamente.
Mantenha a ligação aberta durante toda a vida útil do trabalhador e volte a ligar quando a ligação falhar. Aguarde entre cada iteração para evitar sobrecarregar o servidor quando a fila estiver vazia:
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()
Com o agrupamento de ligações ativado (por predefinição), o agrupamento gere as ligações inativas por si. Mas, se desativares o pooling ou utilizares uma única ligação dedicada, define Connection Timeout e Command Timeout na tua cadeia de ligação para detetar ligações obsoletas atempadamente, em vez de ficar bloqueada.
Otimização de consultas
O Fetch só precisava de dados
Selecionar apenas as colunas que a sua aplicação utiliza reduz a transferência de rede, o consumo de memória e o tempo de execução da consulta.
# 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})
Use métodos adequados de busca
O controlador fornece vários métodos de obtenção. Usa o que corresponde ao tamanho do teu resultado:
-
fetchval()devolve um único valor escalar com sobrecarga mínima. -
fetchall()carrega todo o conjunto de resultados na memória, o que funciona bem para tabelas pequenas. -
fetchmany(n)recupera linhas em lotes, mantendo o uso de memória constante para grandes conjuntos de resultados.
O tamanho certo do fetchmany() lote depende da largura das filas. Para linhas estreitas (algumas colunas pequenas, cerca de 1 KB cada), 1.000 linhas mantêm cada lote cerca de 1 MB de memória. Para filas largas com cadeias grandes ou colunas binárias, use um lote de menor dimensão. Começa com 1.000 e ajusta com base nos teus dados.
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)
Usar paginação do lado do servidor
Em vez de recolher todas as linhas e fatiar em Python, usa OFFSET/FETCH NEXT para recuperar apenas a página de que precisas.
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()
Utilizar SET NOCOUNT ON
Por defeito, o SQL Server envia uma mensagem "linhas afetadas" após cada instrução DML.
SET NOCOUNT ON suprime estas mensagens e reduz o tráfego de rede. É uma definição ao nível da sessão, por isso define-a uma vez depois de ligar em vez de a incorporar em todas as consultas.
# 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"}
)
Escolha o método de inserção correto
O driver fornece três formas de inserir dados, cada uma adequada a uma escala diferente:
| Método | Contagem de linhas | Porquê |
|---|---|---|
execute() |
1 linha por chamada | Utilize para operações de uma única linha, como envios de formulários ou processadores de API, quando precisar do ID inserido imediatamente. |
executemany() |
~10-1.000 linhas | Usa a ligação de parâmetros coluna a coluna para melhor rendimento do que um loop. Envia cada linha como uma instrução parametrizada. |
bulkcopy() |
Centenas de linhas ou mais | Utiliza o protocolo TDS de inserção em massa, que é significativamente mais eficiente do que inserções feitas linha a linha. Ideal para cargas de dados, migrações e processamento em lote. |
Para mais detalhes e exemplos, veja Padrões de carregamento e movimento de dados.
Inserções individuais com execute()
Utilize para inserções pontuais quando precisar do resultado imediatamente.
Production.Product tem várias colunas NOT NULL sem predefinições, por isso o insert lista todas:
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()
Inserções em lote com executemany()
executemany() liga parâmetros por coluna e envia-os de forma eficiente. Utilize-o para lotes moderados em vez de chamar execute() num ciclo. Note-se que executemany() requer marcadores posicionais ? com uma lista de tuplas, enquanto execute() suporta ambos ? e parâmetros nomeados %(name)s com dicts. Consulte consultas parametrizadas para detalhes sobre cada estilo.
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()
Cópia em massa para grandes volumes de dados
Quando a taxa de processamento é mais importante do que o controlo por linha, passe para bulkcopy(). Ele transmite linhas através do protocolo TDS bulk insert e evita a sobrecarga por linha das instruções parametrizadas. O crossover exato em que bulkcopy() supera executemany() depende da largura das linhas e da latência da rede, mas normalmente está nas baixas centenas de linhas. Para lotes muito pequenos, executemany() é mais simples porque bulkcopy() cria uma conexão interna separada e confirma automaticamente.
Ao contrário de execute() e executemany(), bulkcopy() atribui valores às colunas por posição, não com base numa lista de colunas INSERT. Passe column_mappings para indicar as colunas de destino que está a carregar, para que as tuplas de origem se alinhem com as colunas corretas em vez da primeira coluna de identidade da tabela:
result = cursor.bulkcopy(
"Production.Product",
rows,
column_mappings=["Name", "ProductNumber", "SafetyStockLevel", "ReorderPoint",
"StandardCost", "ListPrice", "DaysToManufacture", "SellStartDate"],
)
print(f"Copied {result['rows_copied']} rows")
Para cargas muito grandes, use um gerador para evitar carregar todo o conjunto de dados para a memória e configure batch_size para efetuar commits periodicamente:
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,
)
Estratégias de cache
Para dados de referência que raramente mudam (categorias, tabelas de consulta, configuração), armazene em cache os resultados na sua aplicação em vez de consultar em cada pedido.
O functools.lru_cache do Python fornece memoização simples, mas mantém os resultados em cache indefinidamente até que o processo reinicie. Se os dados subjacentes puderem mudar, use cachetools.TTLCache para atualizar automaticamente após um limite de tempo:
from cachetools import TTLCache, cached
category_cache = TTLCache(maxsize=1, ttl=300) # Refresh every 5 minutes
@cached(category_cache)
def get_categories(connection_string: str) -> tuple:
conn = mssql_python.connect(connection_string)
try:
cursor = conn.cursor()
cursor.execute("SELECT ProductCategoryID, Name FROM Production.ProductCategory")
return cursor.fetchall()
finally:
conn.close()
Otimização de rede
Minimizar viagens de ida e volta
Cada consulta é uma viagem de ida e volta em rede até ao servidor. Combine consultas relacionadas num único lote e use nextset() para avançar pelos conjuntos de resultados:
# 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()
Usar processamento do lado do servidor para lógica complexa
Empurra a agregação e filtragem para o SQL Server em vez de buscar linhas brutas e processá-las em Python. O servidor devolve uma única linha de resumo em vez de potencialmente milhares de linhas de detalhe:
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})
Evite operações de cursor intercaladas
O driver mssql-python não suporta Múltiplos Conjuntos de Resultados Ativos (MARS). Só um cursor pode ter uma consulta ativa por ligação. Obtenha completamente o primeiro conjunto de resultados antes de executar a próxima consulta, ou use uma segunda ligação:
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()
Gestão da memória
Processar grandes resultados em blocos
Carregar uma tabela de vários milhões de linhas numa lista consome memória proporcional ao conjunto total de resultados. Utilize OFFSET e FETCH NEXT para paginar os dados no lado do servidor e processar um bloco de cada vez.
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
)
Usar geradores para streaming
Um gerador em Python que envolve fetchmany() mantém a utilização de memória constante, independentemente do tamanho da tabela. O chamador itera linha a linha sem carregar o conjunto completo de resultados. Para uma fonte extra-grande, combine tabelas com UNION ALL e transmita o resultado combinado da mesma forma.
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")
Limpe os recursos rapidamente
Ligações não fechadas ocupam os recursos do servidor e podem esgotar o pool de ligações. Use um gestor de contexto para garantir a limpeza mesmo quando ocorram exceções.
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()
Monitorar o uso de memória
Grandes conjuntos de resultados, caches de longa duração e objetos de ligação consomem memória. Se a sua aplicação for executada como um serviço, fugas de memória causadas por cursores não fechados ou caches sem limite podem, eventualmente, levar o processo a ser terminado pelo sistema operativo ou pelo ambiente de execução de contentores.
Utilize o módulo tracemalloc do Python para obter instantâneos da memória e encontrar as maiores alocações.
import tracemalloc
tracemalloc.start()
# ... run your workload ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
Fontes comuns de crescimento inesperado de memória incluem:
- Chamar a
fetchall()numa consulta que devolve milhões de linhas. Utilizefetchmany()ou um gerador em vez disso. - Armazenamento em cache dos resultados da consulta sem a
maxsizeou TTL. As caches crescem até o processo ser reiniciado. - Criar cursores num ciclo sem os fechar. Cada cursor aberto mantém o seu conjunto de resultados na memória.
Otimização do índice e do plano de consulta
Verifique o desempenho das consultas do lado do servidor
Use SET STATISTICS TIME ON e SET STATISTICS IO ON veja quanto tempo demoram as consultas no servidor e quantos dados leem. Leituras lógicas elevadas geralmente indicam um índice em falta. Execute estas instruções no SQL Server Management Studio ou na extensão MSSQL para Visual Studio Code, onde a saída aparece no painel de Mensagens:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT * FROM Production.Product WHERE ProductSubcategoryID = 1;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Você deve ver saídas como:
Table 'Product'. Scan count 1, logical reads 3
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Se vir leituras lógicas elevadas ou varreduras de tabelas, considere adicionar um índice.
Utilize sugestões de consulta como correção tática
As sugestões de consulta sobrepõem-se às escolhas do otimizador de consultas quanto ao índice e à estratégia de junção. Em produção, são valiosos como uma correção rápida e de baixo risco quando uma consulta sofre subitamente uma regressão. Podes implementar o hint no código da tua aplicação imediatamente para estabilizar a consulta enquanto investigas a causa raiz (índices em falta, estatísticas obsoletas ou alterações de esquema).
Evite deixar indicações de forma permanente. Quando a distribuição dos dados ou o esquema muda, uma dica codificada pode piorar as coisas. Trata-os como temporários e revisita depois de resolver o problema subjacente:
cursor.execute("""
SELECT * FROM Production.Product WITH (INDEX(AK_Product_Name))
WHERE ProductSubcategoryID = %(subcategory_id)s
""", {"subcategory_id": 1})
Use OPTION (RECOMPILE) para contornar planos em cache defeituosos
O SQL Server armazena em cache os planos de consulta com base no primeiro conjunto de valores de parâmetros que vê. Se a distribuição dos dados variar bastante entre chamadas, o plano em cache pode ter um desempenho fraco para alguns valores. Este problema, designado por sniffing de parâmetros, manifesta-se frequentemente quando uma consulta que "era rápida" passa subitamente a demorar segundos ou minutos.
OPTION (RECOMPILE)obriga o SQL Server a construir um plano novo para cada execução, o que é uma solução imediata e eficaz que pode implementar sem quaisquer alterações do lado do servidor. A contrapartida é um pequeno custo de compilação por chamada, mas, para consultas executadas com pouca frequência ou que devolvem conjuntos de resultados de dimensão variável, esse custo é negligenciável em comparação com a execução de um plano inadequado.
Depois de estabilizar o problema, pode dedicar o tempo necessário para aplicar uma correção permanente, como reescrever a consulta, adicionar índices filtrados ou usar guias de plano:
cursor.execute("""
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate > %(start_date)s
OPTION (RECOMPILE)
""", {"start_date": start_date})
Monitorização do desempenho
Cronometre as suas consultas
Para encontrar operações lentas, envolva as consultas com 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")
Para uma visão mais ampla de onde a sua aplicação passa tempo, use o módulo incorporado cProfile do Python:
python -m cProfile -s cumtime my_app.py
Esta vista mostra o tempo acumulado por chamada de função, o que ajuda a identificar se a lentidão está na execução da consulta, no processamento de dados ou na latência da rede.
Use a Query Store para análise do lado do servidor
O timing do lado do cliente indica-lhe quanto tempo demora uma consulta do ponto de vista da sua aplicação, mas combina latência de rede, tempo de execução do servidor e processamento do cliente. A Query Store captura planos de execução e estatísticas de tempo de execução no servidor, para que possa ver exatamente como o SQL Server executou cada consulta, com que frequência foi executada e como o seu desempenho mudou ao longo do tempo.
O Query Store é especialmente útil para identificar deteção de parâmetros, regressões de plano e consultas que consomem mais recursos do servidor. Pode consultar diretamente as vistas sys.query_store_runtime_stats e sys.query_store_plan, ou utilizar os relatórios incorporados do Query Store no SQL Server Management Studio.
Utilizar os Relatórios do Painel de Desempenho
Os Relatórios do Performance Dashboard no SQL Server Management Studio fornecem uma visão geral em tempo real do estado do SQL Server, incluindo tipos de espera atuais, consultas ativas e caras e tendências de CPU/IO. Use-os para identificar rapidamente gargalos sem apresentar consultas diretamente aos DMVs.
Performance checklist (Lista de verificação do desempenho)
Connection
- [ ] Ativar o agrupamento de ligações.
- [ ] Dimensiona a piscina para a tua carga de trabalho.
- [ ] Reutilizar ligações dentro das operações.
- [ ] Mantenham as ligações abertas em serviços de longa duração.
Queries
- [ ] Seleciona apenas as colunas que precisas.
- [ ] Use o método de busca apropriado para cada consulta.
- [ ] Implementar paginação do lado do servidor.
- [ ] Defina
SET NOCOUNT ONuma vez depois de ligar. - [ ] Minimizar as viagens de ida e volta agrupando as questões.
Inserções
- [ ] Usar
execute()para insertos de fila única. - [ ] Use
executemany()para lotes pequenos a médios (~10-1.000 linhas). - [ ] Utilize
bulkcopy()quando a taxa de transferência for mais importante do que o controlo por linha.
Caching
- [ ] Coloque os dados de referência em cache com um TTL para evitar servir resultados desatualizados.
Resources
- [ ] Processa grandes resultados em blocos ou com geradores.
- [ ] Limpar as conexões de imediato.
- [ ] Monitorizar a utilização de memória com
tracemallocem serviços de longa duração.