Ottimizzazione delle prestazioni per i driver Microsoft per PHP per SQL Server

Download del driver PHP

Questo articolo spiega come scrivere codice PHP veloce su SQL Server, database SQL di Azure, Istanza gestita di SQL di Azure, Azure Synapse Analytics e database SQL in Microsoft Fabric. La guida si applica sia a SQLSRV che a PDO_SQLSRV, che avvolgono lo stesso driver Microsoft ODBC sottostante per SQL Server.

Inizia con i cambiamenti con il più grande impatto

Se puoi fare solo tre modifiche, fai queste modifiche:

  • Abilita il pooling delle connessioni. Stabilire una nuova connessione TLS a SQL Server richiede decine o centinaia di millisecondi a seconda del percorso di rete e della negoziazione TLS. Il riutilizzo delle connessioni aggregate elimina quel costo per richiesta. Vedi Gestire le connessioni in modo efficiente.
  • Prendi solo le colonne e le righe di cui hai bisogno. SELECT * e le query senza limiti sono le cause più comuni degli endpoint lenti. Consulta solo ciò di cui hai bisogno.
  • Usa parametri con valori di tabella per gli inserimenti in blocco. Per centinaia di righe o più, i parametri a valori di tabella (TVP) sono tipicamente molto più veloci delle istruzioni riga per INSERT riga e scalano linearmente con il numero di righe. Vedi Inserisci dati in modo efficiente.

Gestire le connessioni in modo efficiente

L'installazione di collegamento è l'operazione più costosa che il conducente svolge. Quasi ogni indagine sulle prestazioni di PHP termina con una correzione di gestione delle connessioni.

Abilitare il pool di connessioni

Il pooling riutilizza le connessioni ODBC tra le richieste PHP invece di smontarle alla fine della richiesta. L'oggetto di connessione viene eliminato quando lo script termina, ma l'handle ODBC sottostante rimane attivo nel pool del gestore dei driver ODBC e viene riutilizzato dalla richiesta successiva che usa la stessa stringa di connessione.

Windows: Il pool di connessione è attivato di default. Per confermare, lascia fuori l'opzione ConnectionPooling dal tuo DSN. Per disabilitare il pooling per il debug, imposta ConnectionPooling=0.

Linux e macOS: il pool di connessione non è un'opzione DSN su queste piattaforme. Abilitalo nel gestore dei driver impostando Pooling=Yes nella sezione [ODBC] di odbcinst.ini e imposta un valore positivo per CPTimeout nella stanza del driver. Per esempio:

[ODBC]
Pooling=Yes

[ODBC Driver 18 for SQL Server]
Description=Microsoft ODBC Driver 18 for SQL Server
Driver=/opt/microsoft/msodbcsql18/lib64/libmsodbcsql-18.<version>.so.1.1
CPTimeout=120

Trova il percorso effettivo della libreria con odbcinst -q -d -n "ODBC Driver 18 for SQL Server" o ls /opt/microsoft/msodbcsql18/lib64/. Il nome del file incorpora la versione installata del driver ODBC e cambia con ogni rilascio.

CPTimeout (in secondi) determina per quanto tempo le connessioni inattive restano nel pool prima di essere chiuse. Impostalo abbastanza alto da far sì che la maggior parte delle richieste trovi una connessione in pool, ma abbastanza basso da far sì che le connessioni obsolete a un server fallito vengano ritirate abbastanza rapidamente. 60-300 secondi funzionano bene per la maggior parte dei carichi di lavoro web.

Per i dettagli, consulta il pool di connessioni.

Comprendi il costo della prima query

Multiple Active Result Set (MARS) è abilitato per impostazione predefinita. Quando MARS e il pooling delle connessioni sono entrambi attivi, il driver reimposta la connessione nel pool in occasione della prima query e tale reimpostazione ignora qualsiasi timeout di query impostato per quella prima query. Le query successive sulla stessa connessione normalmente rispettano il timeout. Se imposti timeout aggressivi per la prima query su un carico di lavoro in pool, tieni conto di questo comportamento oppure disabilita MARS con MultipleActiveResultSets=false se non ti serve. Vedi la nota su MARS e il pooling in Connection pooling.

Le connessioni PDO persistenti non sono supportate

PDO_SQLSRV rifiuta PDO::ATTR_PERSISTENT. Impostarlo sui lanci costruttori:

SQLSTATE[IMSSP]: An unsupported attribute was designated on the PDO object.

Usa il pooling di connessioni ODBC per il riutilizzo tra richieste incrociate. È il meccanismo nativo del driver, funziona sia per PDO_SQLSRV che per SQLSRV e dismette le connessioni inattive su CPTimeout (il che garantisce anche il corretto aggiornamento del token Microsoft Entra).

Riutilizza la connessione all'interno di una richiesta

Anche con il pooling, aprire una nuova connessione PDO o SQLSRV comporta un viaggio di andata e ritorno ODBC per recuperare e validare un handle poolato. Apri una connessione una volta per richiesta e passala a ogni funzione che ne ha bisogno.

Tip

Un contenitore per l'iniezione di dipendenza o un accessor pigro sono sufficienti. L'obiettivo è evitare new PDO(...) nel mezzo di un handler di richiesta.

Interroga solo i dati necessari

I viaggi di andata e ritorno di rete e la materializzazione dei set di risultati dominano la latenza delle query per la maggior parte dei carichi di lavoro PHP. Le correzioni sono le stesse che si applicano a ogni livello di accesso al database.

Seleziona solo le colonne che usi

SELECT * recupera ogni colonna, comprese varchar(max) e varbinary(max), che sono di gran lunga più grandi dei dati che effettivamente utilizzi. Assegna un nome alle colonne:

<?php
// Slow: fetches all columns, including a 2 MB LOB column
$stmt = $conn->query("SELECT * FROM dbo.Products");

// Fast: fetches only the two columns the caller uses
$stmt = $conn->query("SELECT ProductID, Name FROM dbo.Products");

Prendi solo le righe di cui hai bisogno

Applica il filtro in SQL Server. Non prendere mai una tabella intera in PHP solo per filtrare in un foreach loop.

<?php
// Slow: transfers every row to PHP, then filters
$rows = $conn->query("SELECT * FROM dbo.Orders")->fetchAll(PDO::FETCH_ASSOC);
$recent = array_filter($rows, fn($r) => $r["OrderDate"] > "2026-01-01");

// Fast: filters on the server
$stmt = $conn->prepare("SELECT OrderID, CustomerID, Total FROM dbo.Orders WHERE OrderDate > ?");
$stmt->execute(["2026-01-01"]);
$recent = $stmt->fetchAll(PDO::FETCH_ASSOC);

Impaginare set di risultati di grandi dimensioni

In una vista elenco che mostra alcune centinaia di righe su milioni, non restituire tutte le righe e lasciare che sia il client a ordinarle. Usa la paginazione lato server con OFFSET ... FETCH:

<?php
function fetchPage(PDO $conn, int $page, int $pageSize): array {
    $stmt = $conn->prepare(
        "SELECT OrderID, CustomerID, Total
         FROM dbo.Orders
         ORDER BY OrderID
         OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"
    );
    // With native prepares, execute([...]) binds values as strings.
    // OFFSET and FETCH NEXT require integer bindings; bind explicitly.
    $stmt->bindValue(1, ($page - 1) * $pageSize, PDO::PARAM_INT);
    $stmt->bindValue(2, $pageSize, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

Scegli il metodo di recupero giusto

  • Usa fetch(PDO::FETCH_ASSOC) all'interno di un ciclo per eseguire un'iterazione in streaming quando non hai bisogno di tutte le righe in memoria contemporaneamente.
  • Usalo fetchAll(PDO::FETCH_ASSOC) quando il chiamante ha davvero bisogno dell'intero set (ad esempio, per rendere una risposta JSON completa).
  • Usa fetchColumn() quando ti interessa solo un singolo valore scalare (un COUNT, SUM o MAX).
  • Usa PDO::FETCH_KEY_PAIR o PDO::FETCH_UNIQUE per costruire dizionari di ricerca senza un secondo passaggio.

I modi di recupero numerico (PDO::FETCH_NUM) sono leggermente più veloci dei modi di recupero associativo perché saltano la costruzione della mappa del nome delle colonne. Preferisci la chiarezza; Cambia solo quando un profiler segnala il carico di recupero come significativo.

Preferire SET NOCOUNT ON nelle stored procedure e nei batch

Ogni istruzione INSERT, UPDATE e DELETE restituisce un token DONE_IN_PROC con il conteggio delle righe interessate, che PHP in genere scarta. Il token non richiede un ulteriore round trip, ma ciascuno comporta comunque un certo numero di byte trasmessi sulla rete e un piccolo carico di elaborazione per il driver. In una procedura multiistruzione o in un batch che esegue centinaia di istruzioni per chiamata, i risparmi si accumulano. Spegnilo:

CREATE OR ALTER PROCEDURE dbo.ProcessOrder
    @OrderID INT
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.Inventory SET Stock = Stock - 1 WHERE ProductID IN (SELECT ProductID FROM dbo.OrderLines WHERE OrderID = @OrderID);
    UPDATE dbo.Orders SET Status = 'Processed' WHERE OrderID = @OrderID;
END;

Inserire dati in modo efficiente

Scegli il metodo giusto di inserimento in base a quante righe stai muovendo. La scelta sbagliata può essere 100 volte più lenta.

Meno di circa 100 righe: enunciato preparato in un ciclo

Per piccoli lotti, esegui un singolo prepared statement all'interno di un ciclo:

<?php
$stmt = $conn->prepare("INSERT INTO dbo.Products (Name, Price) VALUES (?, ?)");
foreach ($products as $p) {
    $stmt->execute([$p["name"], $p["price"]]);
}

Avvolgi il ciclo in una transazione in modo che tutti gli inserimenti vengano confermati come un'unica unità e il log non debba essere scaricato dopo ogni riga:

<?php
$conn->beginTransaction();
try {
    $stmt = $conn->prepare("INSERT INTO dbo.Products (Name, Price) VALUES (?, ?)");
    foreach ($products as $p) {
        $stmt->execute([$p["name"], $p["price"]]);
    }
    $conn->commit();
} catch (PDOException $e) {
    $conn->rollBack();
    throw $e;
}

Centinaia o milioni di righe: parametri a valori di tabella

I parametri a valore di tabella (TVP) inviano l'intero lotto a SQL Server in un unico viaggio di andata e ritorno e lasciano che SQL Server elabori il set come un'unica istruzione. Per lotti di centinaia di righe o più, i TVP sono tipicamente molto più veloci di un ciclo di enunciato preparato e scalano linearmente con il numero di righe.

Per prima cosa, crea un tipo di tabella sul server:

CREATE TYPE dbo.ProductTableType AS TABLE (
    Name  NVARCHAR(100),
    Price DECIMAL(10, 2)
);

PDO_SQLSRV passa il TVP come un array associativo la cui chiave è il nome del tipo e il cui valore è l'insieme di righe. Collegalo con PDO::PARAM_LOB:

<?php
$rows = [];
foreach ($products as $p) {
    $rows[] = [$p["name"], $p["price"]];
}
$tvpInput = ["ProductTableType" => $rows];

$stmt = $conn->prepare(
    "INSERT INTO dbo.Products (Name, Price) SELECT Name, Price FROM ?"
);
$stmt->bindParam(1, $tvpInput, PDO::PARAM_LOB);
$stmt->execute();

Per uno schema non predefinito, si passa lo schema come elemento successivo dell'array: ["ProductTableType" => $rows, "Sales"]. Per gli esempi di sintassi procedurale SQLSRV e stored procedure, vedi Usa parametri a valori di tabella.

Milioni di righe: bcp o BULK INSERT

Per operazioni veramente in blocco (carichi di data warehouse, migrazioni iniziali), usa BCP o BULK INSERT invece di PHP. Scrivi i tuoi dati in un file delimitato o in formato nativo, poi esegui bcp o BULK INSERT da un job programmato, un passo ETL o uno script amministrativo.

Caution

Se richiami bcp da PHP con shell_exec() o proc_open(), non interpolare mai input non attendibile nella riga di comando. Usa escapeshellarg() per ogni argomento e preferisci eseguire il caricamento fuori banda piuttosto che nel percorso di una richiesta web.

Ridurre i viaggi di andata e ritorno

Ogni viaggio di andata e ritorno tra PHP e SQL Server ha un costo fisso. Quando invii cinque rendiconti in un solo lotto, paghi quel costo una sola volta invece di cinque volte.

Per operazioni correlate eseguite insieme, inserisci le istruzioni in un unico batch ed elabora tutti i set di risultati:

<?php
$sql = "
    SELECT * FROM dbo.Customers WHERE CustomerID = ?;
    SELECT * FROM dbo.Orders WHERE CustomerID = ?;
    SELECT * FROM dbo.Addresses WHERE CustomerID = ?;
";
$stmt = $conn->prepare($sql);
$stmt->execute([$id, $id, $id]);

$customer = $stmt->fetch(PDO::FETCH_ASSOC);

$stmt->nextRowset();
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);

$stmt->nextRowset();
$addresses = $stmt->fetchAll(PDO::FETCH_ASSOC);

Per SQLSRV, usa sqlsrv_next_result per avanzare tra i set di risultati.

Abilita più set di risultati attivi quando ne hai bisogno

Multiple Active Result Sets (MARS) consente a una singola connessione di gestire istruzioni attive multiple. Senza MARS, non puoi emettere una nuova query su una connessione che ha ancora un set di risultati aperto. Entrambi i driver abilitano MARS di default. Per disattivarlo, imposta MultipleActiveResultSets=false nella stringa di connessione. Vedi Disabilita Ensemble di Risultati Attivi Multipli (MARS).

MARS è comodo ma non gratuito. Ogni set di risultati attivo consuma risorse lato server. Preferisci consumare un set di risultati interamente prima di iniziarne un altro. Usa MARS per sbloccare i pattern di cursore genuinamente annidati.

Accorda dichiarazioni preparate

Le istruzioni preparate evitano al driver di analizzare nuovamente l’SQL sul server e consentono di passare in sicurezza input non attendibili come parametri.

Preferisci preparazioni native

PDO_SQLSRV può preparare le dichiarazioni in due modalità. Le istruzioni prepare native inviano il testo SQL al server una sola volta e riutilizzano l'istruzione analizzata per ogni esecuzione, inviando solo i valori dei parametri a ogni execute(). Le istruzioni preparate emulate mantengono il testo SQL nel client e ricostruiscono una stringa SQL completa con i parametri interpolati a ogni esecuzione.

Imposta PDO::ATTR_EMULATE_PREPARES => false in modo che il driver utilizzi i preparatori nativi. I preparatori nativi permettono a SQL Server di memorizzare e riutilizzare il piano di interrogazione, evitando di riparattare il testo SQL ad ogni esecuzione.

<?php
$conn = new PDO($dsn, null, null, [
    PDO::ATTR_ERRMODE          => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

Riutilizzare dichiarazioni preparate

Prepara una volta, esegui più volte. Ogni chiamata a prepare() comporta l'allocazione di un handle ODBC e un'analisi sintattica sul lato server. In un hot loop, mantenere vivo l'oggetto $stmt e chiamare execute() all'interno del loop:

<?php
// Fast: one prepare, many executes.
$stmt = $conn->prepare("UPDATE dbo.Inventory SET Stock = Stock - ? WHERE ProductID = ?");
foreach ($orderLines as $line) {
    $stmt->execute([$line["qty"], $line["productId"]]);
}

// Slow: re-prepares the same SQL on every iteration.
foreach ($orderLines as $line) {
    $stmt = $conn->prepare("UPDATE dbo.Inventory SET Stock = Stock - ? WHERE ProductID = ?");
    $stmt->execute([$line["qty"], $line["productId"]]);
}

Fai attenzione a TOP (?) e IN (?, ?, ...)

TOPrichiede parentesi attorno a un indicatore di parametro, SELECT TOP (?) ..., così SQL Server può analizzare il conteggio delle righe come parametro. IN (?, ?, ?, ?) richiede un numero fisso di segnaposto in fase di preparazione. Per dimensioni dinamiche degli elenchi IN, creare la stringa segnaposto a partire da un numero intero convalidato oppure passare l'elenco come parametro con valori di tabella.

Caution

Non interpolare mai l'input grezzo dell'utente nel testo SQL (incluso il conteggio segnaposto). Esegui il cast di count con (int) prima di costruire la stringa segnaposto e passa sempre i valori effettivi tramite execute() come parametri.

Gestire cursori e memoria

Il tipo di cursore predefinito è PDO::CURSOR_FWDONLY, un tubo antincendio solo in avanti. Trasmette le righe in PHP una alla volta e non fa buffer, quindi un grande set di risultati è limitato dalla memoria del riga buffer invece che dal conteggio totale delle righe. Di solito è quello che vuoi.

Usa cursori bufferizzati solo quando devi muoverti indietro o contare le righe

PDO::SQLSRV_CURSOR_BUFFERED (un cursore statico bufferizzato lato client) recupera l'intero set di risultati in memoria PHP all'inizio. Questo approccio ti permette di chiamare rowCount(), tornare indietro e riutilizzare l'istruzione. Per impostazione predefinita, il buffer è limitato a 10.240 KB (10 MB) tramite PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, e una query il cui set di risultati supera il limite restituisce false invece di causare un overflow della memoria di PHP. Puoi aumentare il limite fino al limite di memoria di PHP, ma così facendo sostituisci un valore restituito false con un vero errore fatale Allowed memory size exhausted quando una query supera il nuovo limite. Regola con precisione. Vedi Tipi di cursore (PDO_SQLSRV).

I cursori scorribili lato server (PDO::SQLSRV_CURSOR_STATIC, PDO::SQLSRV_CURSOR_DYNAMIC, PDO::SQLSRV_CURSOR_KEYSET) si posizionano sul server invece che sul client, così non consumano memoria PHP. Tuttavia, trattengono risorse lato server per tutta la durata del cursore e sono più lente per riga rispetto a quelle solo in avanti.

Usa l'impostazione predefinita di sola lettura in avanti per le letture in streaming. Usa il lato client bufferato per set di risultati piccoli quando serve rowCount() , oppure lo scorrimento all'indietro. Evita i cursori scorribili lato server a meno che tu non stia facendo qualcosa di specifico.

<?php
// Fast, low memory: default forward-only, one row at a time
$stmt = $conn->prepare("SELECT OrderID, Total FROM dbo.Orders");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // ...
}

// Buffered: only when you need rowCount() or seeking
$stmt = $conn->prepare("SELECT * FROM dbo.SmallLookup", [
    PDO::ATTR_CURSOR                    => PDO::CURSOR_SCROLL,
    PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,
]);
$stmt->execute();
$rowCount = $stmt->rowCount();

Per una spiegazione completa, vedi Tipi di cursore (PDO_SQLSRV) e Tipi di cursore (SQLSRV).

Trasmettere in streaming valori binari e di caratteri di grandi dimensioni

Per varbinary(max), varchar(max), nvarchar(max),xml e altri tipi di grandi dimensioni, usa flussi PHP invece di materializzare l'intero valore in memoria:

<?php
$stmt = $conn->prepare("SELECT Name, PhotoBlob FROM dbo.Products WHERE ProductID = ?");
$stmt->execute([$id]);
$stmt->bindColumn("PhotoBlob", $photo, PDO::PARAM_LOB);
$stmt->fetch(PDO::FETCH_BOUND);

// $photo is a stream resource; write it directly to disk without loading it all
$outFile = fopen("/tmp/photo.bin", "wb");
stream_copy_to_stream($photo, $outFile);
fclose($outFile);

Per inserire o aggiornare con valori grandi, usa SendStreamParamsAtExec=false in SQLSRV per inviare dati di flusso in blocchi dopo sqlsrv_execute(). Per dettagli, vedi Invia dati come flusso.

Impostare i timeout appropriati

I timeout sono impostazioni di prestazione tanto quanto quelle di affidabilità. Le query che rimangono in sospeso a lungo occupano le connessioni del pool e impediscono ad altre richieste di ottenerle.

Timeout rendiconto

Imposta un limite di tempo per singola istruzione in modo che una query in esecuzione incontrollata non mantenga occupata una connessione del pool indefinitamente. Per PDO_SQLSRV:

<?php
$stmt = $conn->prepare("SELECT ... FROM dbo.HugeTable ...");
$stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, 30); // seconds
$stmt->execute();

Per SQLSRV, passare "QueryTimeout" => 30 nell'array di opzioni a sqlsrv_query o sqlsrv_prepare.

Imposta un valore che corrisponda al tuo carico di lavoro. Per una richiesta web sincrona, 15-30 secondi sono normali. Per un lavoro a batch di background, potrebbero essere ragionevoli alcuni minuti. Non impostare mai il timeout a zero (illimitato) in una richiesta web.

Timeout di accesso

LoginTimeoutnella stringa di connessione controlla quanto tempo il driver aspetta per stabilire una connessione. Imposta un valore esplicito quando ti connetti ad database SQL di Azure o ad Istanza gestita di SQL di Azure, in modo che gli avvii a freddo e i failover dei gruppi di failover non blocchino il client indefinitamente. Valori da 30 a 90 secondi funzionano bene per la maggior parte dei carichi di lavoro cloud. Per dettagli sulle dimensioni LoginTimeout contro ConnectRetryCount * ConnectRetryInterval e sulle modalità di guasto risultanti, vedi Timeout di connessione. Per il riferimento alle opzioni, vedi Opzioni di connessione.

Indirizzare i carichi di lavoro di sola lettura verso una replica

Per le query di sola lettura eseguite su un database in un gruppo di disponibilità Always On, Istanza gestita di SQL di Azure o database SQL di Azure con scalabilità in lettura o una replica geografica, aggiungi ApplicationIntent=ReadOnly alla stringa di connessione:

<?php
$dsn = "sqlsrv:Server=<listener>;Database=<database>;" .
       "Encrypt=true;ApplicationIntent=ReadOnly";

Il routing di sola lettura invia la connessione a una replica secondaria sincronizzata, scaricando il lavoro dal primario. Combina con MultiSubnetFailover=true per ottenere la connessione più rapida ai listener del gruppo di disponibilità multi-subnet.

Osserva le prestazioni dal server

La temporizzazione lato client indica soltanto quanto tempo ha richiesto una query dall'inizio alla fine. Per capire perché era lento, usa la diagnostica integrata di SQL Server.

Archivio query

Query Store cattura piani di esecuzione, statistiche di runtime e statistiche di attesa per ogni query nel database. È abilitato di default su database SQL di Azure, Istanza gestita di SQL di Azure e database SQL in Fabric. In SQL Server, abilitalo per ogni database:

ALTER DATABASE <database_name> SET QUERY_STORE = ON;

Poi usa i report Query Store di SQL Server Management Studio per trovare le query più lente ed eseguite con più frequenti risultati. Vedi Monitoraggio delle prestazioni con il Query Store.

Informazioni dettagliate sulle prestazioni delle query di Azure SQL

Per database SQL di Azure, Query Performance Insight del portale Azure mostra automaticamente le query che richiedono più risorse senza alcuna configurazione. Per altre informazioni, vedere Informazioni dettagliate sulle prestazioni delle query per il database SQL di Azure.

SET STATISTICS per un'indagine una tantum

Per una singola query che vuoi profilare, eseguila in SQL Server Management Studio con le statistiche attivate:

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

-- your query here

Letture logiche elevate quasi sempre significano un indice mancante o inutilizzabile. Un tempo CPU elevato con basse letture logiche di solito significa un piano sbagliato (parameter sniffing, una conversione implicita che impedisce l'uso dell'indice, o una funzione scalare che impedisce il parallelismo).

Eventi estesi per il tracciamento a livello di pilota

Per vedere esattamente cosa invia il driver a SQL Server (inclusi i valori effettivi dei parametri che interpola), cattura una sessione di Eventi Estesi usando gli rpc_completed eventi andsql_batch_completed.

Elenco di controllo delle prestazioni

Usa questa lista di controllo come verifica pre-distribuzione per qualsiasi applicazione PHP che si connette a SQL Server:

Area Controlla Reference
Connection Il pool di connessioni è abilitato e configurato per la piattaforma Gestire le connessioni in modo efficiente
Connection L'applicazione riutilizza le connessioni nell'ambito di una richiesta e non apre una connessione per ogni query Riutilizza la connessione all'interno di una richiesta
Connection LoginTimeout copre gli avvii a freddo e il failover di Azure SQL Timeout per il login
Query Le query selezionano solo le colonne necessarie, no SELECT * Seleziona solo le colonne che usi
Query Il filtraggio avviene in SQL, non in PHP con array_filter Prendi solo le righe di cui hai bisogno
Query Grandi insiemi di risultati sono paginati con OFFSET ... FETCH Suddividere in pagine grandi insiemi di risultati
Query Insieme di stored procedure SET NOCOUNT ON Preferisci SET NOCOUNT ON
Inserti Gli inserimenti in blocco utilizzano parametri con valori di tabella, non cicli per riga Inserire dati in modo efficiente
Dichiarazioni PDO::ATTR_EMULATE_PREPARES è impostato su false Preferisco preparazioni native
Dichiarazioni L'applicazione riutilizza le istruzioni preparate tra le esecuzioni Riutilizzare dichiarazioni preparate
Cursors L'applicazione utilizza il cursore predefinito solo in avanti, a meno che non sia necessario il buffering Gestire cursori e memoria
Memory I valori binari e di caratteri di grandi dimensioni vengono trasmessi in streaming, non materializzati Trasmettere in streaming valori binari e valori di caratteri di grandi dimensioni
Tempi di attesa Il timeout delle istruzioni è impostato su tutte le query visibili all'utente Timeout dell'istruzione
Instradamento Carichi di lavoro di sola lettura impostati ApplicationIntent=ReadOnly dove esiste una replica Instradare i carichi di lavoro di sola lettura
Observability Query Store è abilitato e revisionato regolarmente Query Store