Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieser Artikel behandelt, wie man schnellen PHP-Code gegen SQL Server, Azure SQL-Datenbank, Azure SQL Managed Instance, Azure Synapse Analytics und SQL Database in Microsoft Fabric schreibt. Die Leitlinien gelten sowohl für SQLSRV als auch für PDO_SQLSRV, die denselben zugrunde liegenden Microsoft ODBC-Treiber für SQL Server umwickeln.
Beginnen Sie mit den Veränderungen mit der größten Wirkung
Wenn Sie nur drei Änderungen vornehmen können, nehmen Sie diese vor:
- Aktiviere Connection Pooling. Die Einrichtung einer neuen TLS-Verbindung zum SQL Server dauert je nach Netzwerkpfad und TLS-Verhandlung Dutzende bis Hunderte von Millisekunden. Die Wiederverwendung von Pool-Verbindungen eliminiert diese Kosten pro Anfrage. Siehe Verbindungen effizient verwalten.
- Hol nur die Spalten und Zeilen, die du brauchst.
SELECT *und unbeschränkte Abfragen sind die häufigsten Ursachen für langsame Endpunkte. Siehe "Abfrage nur das, was du brauchst". - Verwenden Sie tabellenwertige Parameter für Bulk-Einsätze. Für Hunderte von Zeilen oder mehr sind tabellenwertige Parameter (TVPs) typischerweise deutlich schneller als zeilen-für-Zeilen-Anweisungen
INSERTund skalieren linear mit der Zeilenanzahl. Siehe Daten effizient einfügen.
Verwaltung von Verbindungen effizient
Die Verbindungseinrichtung ist die mit Abstand teuerste Operation, die der Fahrer ausführt. Fast jede PHP-Leistungsuntersuchung endet mit einer Lösung für die Verbindungsverwaltung.
Aktivieren von Verbindungspooling
Das Poolen von ODBC-Verbindungen verwendet die ODBC-Verbindungen über PHP-Anfragen wieder, anstatt sie am Request-Ende herunterzubauen. Das Connection-Objekt wird verworfen, wenn dein Skript endet, aber der zugrunde liegende ODBC-Handle bleibt im ODBC-Treibermanager-Pool aktiv und wird von der nächsten Anfrage wiederverwendet, die nach derselben Verbindungszeichenfolge fragt.
Windows: Connection Pooling ist standardmäßig aktiviert. Zur Bestätigung solltest du die ConnectionPooling Option in deinem DSN weglassen. Um das Pooling für Debugging zu deaktivieren, setze ConnectionPooling=0.
Linux und macOS: Connection Pooling ist auf diesen Plattformen keine DSN-Option. Aktivieren Sie es im Driver Manager, indem Sie in den [ODBC] Abschnitt von odbcinst.inisetzenPooling=Yes, und setzen Sie ein Plus CPTimeout unter der Strofe des Fahrers. Beispiel:
[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
Finde den tatsächlichen Bibliothekspfad mit odbcinst -q -d -n "ODBC Driver 18 for SQL Server" oder ls /opt/microsoft/msodbcsql18/lib64/. Der Dateiname bettet die installierte ODBC-Treiberversion ein und ändert sich mit jeder Version.
CPTimeout (in Sekunden) steuert, wie lange die Leerlaufverbindungen im Pool bleiben, bevor sie geschlossen werden. Stelle sie so hoch, dass die meisten Anfragen eine Pool-Verbindung finden, aber niedrig genug, dass veraltete Verbindungen zu einem failover-Server-Server relativ schnell eingestellt werden. 60 bis 300 Sekunden funktionieren für die meisten Web-Workloads gut.
Details finden Sie unter Connection Pooling.
Verstehen Sie die Kosten für die erste Abfrage
MARS (mehrere aktive Resultsets) ist standardmäßig aktiviert. Wenn sowohl MARS als auch Connection Pooling aktiv sind, setzt der Treiber die gepoolte Verbindung bei der ersten Abfrage zurück, und dieser Reset ignoriert alle von dir gesetzten Abfrage-Timeouts für diese erste Abfrage. Spätere Abfragen auf derselben Verbindung berücksichtigen die Auszeit normalerweise. Wenn du bei einer gepoolten Arbeitslast aggressive Erstanfrage-Timeouts einsetzt, berücksichtige dieses Verhalten oder deaktiviere MARS, MultipleActiveResultSets=false falls du es nicht brauchst. Siehe die MARS- und Pooling-Notiz unter Connection Pooling.
Persistente PDO-Verbindungen werden nicht unterstützt
PDO_SQLSRV lehnt PDO::ATTR_PERSISTENTab. Setzt man es auf die Konstruktor-Würfe:
SQLSTATE[IMSSP]: An unsupported attribute was designated on the PDO object.
Verwenden Sie ODBC Connection Pooling für Cross-Request-Wiederverwendung. Es ist der treiberbasierte Mechanismus, funktioniert sowohl für PDO_SQLSRV als auch für SQLSRV und schaltet Leerlaufverbindungen auf CPTimeout (was auch Microsoft Entra Token-Aktualisierung zuverlässig hält).
Verwenden Sie die Verbindung innerhalb einer Anfrage erneut
Selbst beim Pooling führt das Öffnen einer neuen PDO- oder SQLSRV-Verbindung zu einer ODBC-Rundreise, um einen gepoolten Handle abzurufen und zu validieren. Öffne eine Verbindung einmal pro Anfrage und übergebe sie an jede Funktion, die sie benötigt.
Tipp
Ein Abhängigkeitsinjektionsbehälter oder ein lazy accessor reicht aus. Der Punkt ist, mitten in einem Request-Handler zu vermeiden new PDO(...) .
Fragen Sie nur nach dem, was Sie brauchen
Netzwerk-Roundtrips und Materialisierung von Ergebnismengen dominieren die Abfragelatenz für die meisten PHP-Workloads. Die Korrekturen sind dieselben, die auf jede Datenbankzugriffsschicht angewendet werden.
Wählen Sie nur die Spalten aus, die Sie verwenden
SELECT * Zieht jede Spalte, einschließlich Varchar(max) und varbinary(max)- Spalten, die die Daten, die du tatsächlich konsumierst, bei weitem überragen. Benennen Sie die Spalten:
<?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");
Hol nur die Reihen, die du brauchst,
Schiebe Filterung auf SQL Server. Holen Sie niemals eine vollständige Tabelle in PHP, nur um in einer Schleife foreach zu filtern.
<?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);
Paginierung großer Ergebnismengen
Für eine Listenansicht, die ein paar hundert Zeilen aus Millionen anzeigt, gib nicht alle Zeilen zurück und lass den Client das regeln. Verwenden Sie serverseitige Paginierung mit 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);
}
Wählen Sie die richtige Fetch-Methode
- Nutze
fetch(PDO::FETCH_ASSOC)in einer Schleife für Streaming-Iterationen, wenn du nicht alle Zeilen gleichzeitig im Speicher brauchst. - Verwenden
fetchAll(PDO::FETCH_ASSOC)Sie, wenn der Anrufer tatsächlich das gesamte Set benötigt (zum Beispiel beim Rendern einer vollständigen JSON-Antwort). - Benutze
fetchColumn(), wenn dir nur ein einzelner Skalar wichtig ist (einCOUNT,SUM, oderMAX). - Nutze
PDO::FETCH_KEY_PAIRoderPDO::FETCH_UNIQUEbaue Suchwörterbücher ohne einen zweiten Durchgang.
Numerische Abrufmodi (PDO::FETCH_NUM) sind geringfügig schneller als assoziative Abrufmodi, da sie das Erstellen der Spaltennamensabbildung überspringen. Bevorzuge Klarheit; Wechsle nur, wenn ein Profiler den Fetch-Overhead als signifikant markiert.
Bevorzuge SET NOCOUNT ON in gespeicherten Prozeduren und Chargen
Jede INSERT, , and-Anweisung UPDATEDELETE liefert ein DONE_IN_PROC Token mit der betroffenen Zeilenanzahl, die PHP typischerweise verworfen hat. Der Token fügt keine Rundreise hinzu, aber jeder Token kostet trotzdem Bytes auf der Leitung und eine kleine Menge Treiberarbeit. In einem Multi-Statement-Verfahren oder Batch, das Hunderte von Statements pro Anruf ausführt, summieren sich die Einsparungen. Schalten Sie es aus:
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;
Daten effizient einfügen
Wählen Sie die richtige Einführungsmethode basierend darauf, wie viele Reihen Sie bewegen. Die falsche Wahl kann hundertmal langsamer sein.
Weniger als etwa 100 Zeilen: vorbereitete Aussage in einer Schleife
Für kleine Chargen führe eine einzelne vorbereitete Anweisung in einer Schleife aus:
<?php
$stmt = $conn->prepare("INSERT INTO dbo.Products (Name, Price) VALUES (?, ?)");
foreach ($products as $p) {
$stmt->execute([$p["name"], $p["price"]]);
}
Wickle die Schleife in eine Transaktion, sodass alle Inserts als eine Einheit committen und das Log nicht nach jeder Zeile ausgefüllt werden muss:
<?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;
}
Hunderte bis Millionen von Zeilen: tabellenwertige Parameter
Tabellenwerte Parameter (TVPs) senden den gesamten Batch in einer Rundenrunde-Runde an SQL Server und lassen SQL Server die Menge als eine einzige Anweisung verarbeiten. Bei Chargen von Hunderten von Zeilen oder mehr sind TVPs typischerweise viel schneller als eine vorbereitete Statement-Schleife und skalieren linear mit der Zeilenanzahl.
Zuerst erstellen Sie einen Tabellentyp auf dem Server:
CREATE TYPE dbo.ProductTableType AS TABLE (
Name NVARCHAR(100),
Price DECIMAL(10, 2)
);
PDO_SQLSRV übergibt das TVP als assoziatives Array, dessen Schlüssel der Typname ist und dessen Wert die Zeilenmenge ist. Binde es mit 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();
Für ein nicht-standardmäßiges Schema gib das Schema als nächstes Element des Arrays durch: ["ProductTableType" => $rows, "Sales"]. Für die SQLSRV-Prozedursyntax und Beispiele für gespeicherte Prozeduren siehe Tabellenwertige Parameter verwenden.
Millionen von Reihen: bcp oder BULK INSERT
Für wirklich Massenabläufe (Data Warehouse-Loads, erste Migrationen) verwende BCP oder BULK INSERT stattdessen PHP. Schreibe deine Daten in eine deseparate oder native-formatige Datei und führe dann BCP oder BULK INSERT aus einem geplanten Job, einem ETL-Schritt oder einem Admin-Skript aus.
Caution
Wenn du von PHP mit shell_exec() oder proc_open()zu BCP ausgibst, interpoliere niemals unzuverlässige Eingaben in die Kommandozeile. Verwenden escapeshellarg() Sie bei jedem Argument und bevorzugen es, den Load Out-of-Band auszuführen, statt in einem Web-Request-Pfad.
Reduziere Hin- und Rückflüge
Jede Netzwerk-Rundreise zwischen PHP und SQL Server hat eine feste Kosten. Wenn Sie fünf Kontoauszüge als eine Charge senden, zahlen Sie diese Kosten einmal statt fünfmal.
Zusammenhängende Anweisungen zu einer einzigen Charge kombinieren
Für verwandte Arbeiten, die zusammen ausgeführt werden, packen Sie die Anweisungen in eine Charge und verbrauchen Sie jede Ergebnismenge:
<?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);
Für SQLSRV verwenden Sie die Option sqlsrv_next_result , um zwischen Ergebnissätzen vorzugehen.
Aktiviere mehrere aktive Ergebnissätze, wenn du sie brauchst
Multiple Active Result Sets (MARS) ermöglicht es einer einzelnen Verbindung, mehrere aktive Anweisungen zu haben. Ohne MARS kann man keine neue Abfrage auf eine Verbindung stellen, die noch ein offenes Ergebnisset hat. Beide Treiber aktivieren MARS standardmäßig. Um es auszuschalten, setze MultipleActiveResultSets=false deinen Verbindungszeichenfolge ein.
Siehe Deaktivieren mehrerer aktiver Ergebnissätze (MARS).
MARS ist bequem, aber nicht kostenlos. Jede aktive Ergebnismenge verbraucht serverseitige Ressourcen. Bevorzugen Sie es, ein Ergebnisset vollständig zu konsumieren, bevor Sie mit einem neuen beginnen. Nutze MARS, um wirklich verschachtelte Cursormuster zu entblocken.
Tun Sie vorbereitete Aussagen
Prepared Statements speichern den Treiber vor dem erneuten Parsen von SQL auf dem Server und ermöglichen es Ihnen, nicht vertrauenswürdige Eingaben sicher als Parameter zu binden.
Bevorzuge einheimische Zubereitungen
PDO_SQLSRV können Anweisungen in zwei Modi vorbereiten.
Native Prepares senden den SQL-Text einmal an den Server und verwenden die geparste Anweisung für jede Ausführung wieder, wobei nur die Parameterwerte auf jedem execute()gesendet werden.
Emulierte Vorbereitungen speichern den SQL-Text im Client und erstellen eine vollständige SQL-Zeichenkette mit bei jeder Ausführung interpolierten Parametern.
Stelle es so ein, PDO::ATTR_EMULATE_PREPARES => false dass der Fahrer native Vorbereitungen verwendet. Native Prepares ermöglichen es SQL Server, den Abfrageplan zu cachen und wiederzuverwenden, und sie vermeiden es, SQL-Text bei jeder Ausführung neu zu parsen.
<?php
$conn = new PDO($dsn, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
Wiederverwenden vorbereiteter Aussagen
Bereite dich einmal vor, führe viele aus. Jeder Aufruf prepare() kostet eine ODBC-Handle-Zuweisung und eine serverseitige Parse. In einer Hot Loop halten Sie das $stmt Objekt lebendig und rufen execute() Sie innerhalb der Schleife auf:
<?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"]]);
}
Achte auf TOP (?) und IN (?, ?, ...)
TOPbenötigt Klammern um einen Parametermarker, SELECT TOP (?) ..., sodass SQL Server die Zeilenanzahl als Parameter analysieren kann.
IN (?, ?, ?, ?) erfordert eine feste Platzhalterzahl zur Vorbereitungszeit. Für dynamische IN Listengrößen baut man entweder die Platzhalter-Zeichenkette aus einer validierten Ganzzahlzahl oder übergibt die Liste als tabellenwertigen Parameter.
Caution
Interpolieren Sie niemals die rohe Benutzereingabe in den SQL-Text (einschließlich der Platzhalterzahl). Wirke den Count mit aus (int) , bevor du die Platzhalter-String baust, und gib die tatsächlichen Werte immer als Parameter durch execute() .
Cursor und Speicher verwalten
Der Standardcursortyp ist PDO::CURSOR_FWDONLY, ein nur vorwärtsgerichteter Feuerschlauch. Es streamt Zeilen einzeln nach PHP und puffert nicht, sodass ein großer Ergebnissatz durch den Zeilenpufferspeicher statt durch die gesamte Zeilenanzahl begrenzt ist. Das ist normalerweise das, was du willst.
Verwende gepufferte Cursor nur, wenn du rückwärts gehen oder Zeilen zählen musst.
PDO::SQLSRV_CURSOR_BUFFERED (ein clientseitiger gepufferter statischer Cursor) das gesamte Ergebnisset sofort in den PHP-Speicher abruft. Dieser Ansatz erlaubt es, die Aussage aufzurufen rowCount(), rückwärts zu suchen und wiederzuverwenden. Standardmäßig ist der Puffer auf 10.240 KB (10 MB) PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZEbegrenzt, und eine Abfrage, deren Ergebnismenge die Obergrenze überschreitet, gibt anstelle von überlaufendem PHP-Speicher zurück.false Man kann die Obergrenze auf das PHP-Speicherlimit erhöhen, aber das tauscht eine false Rendite gegen einen wirklich Allowed memory size exhausted fatalen Fehler ein, wenn eine Abfrage das neue Limit überschreitet. Stimmt absichtlich. Siehe Cursortypen (PDO_SQLSRV).
Serverseitige scrollbare Cursor (PDO::SQLSRV_CURSOR_STATIC, PDO::SQLSRV_CURSOR_DYNAMIC, ) PDO::SQLSRV_CURSOR_KEYSETpuffern auf dem Server statt auf dem Client, sodass sie keinen PHP-Speicher verbrauchen. Sie speichern jedoch serverseitige Ressourcen für die Dauer des Cursors und sind pro Reihe langsamer als nur vorwärtsgesteuerte Ressourcen.
Verwende die Standard-Forward-Only für das Streamen von Reads. Nutze gepufferte clientseitige Systeme für kleine Ergebnismengen, wenn du sie brauchst rowCount() , oder rückwärts scrollen. Vermeide serverseitige scrollbare Cursors, es sei denn, du machst etwas Bestimmtes.
<?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();
Für eine vollständige Aufschlüsselung siehe Cursor-Typen (PDO_SQLSRV) und Cursor-Typen (SQLSRV).
Ströme große Binär- und Zeichenwerte
Für varbinary(max),varchar(max),nvarchar(max),xml und andere große Typen verwenden Sie PHP-Streams, anstatt den gesamten Wert im Speicher zu materialisieren:
<?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);
Für das Einfügen oder Aktualisieren großer Werte verwenden Sie in SQLSRV, SendStreamParamsAtExec=false um Stromdaten in Chunks nach sqlsrv_execute()zu senden. Für Details siehe Daten als Strom senden.
Einstellen geeigneter Timeouts
Timeouts sind genauso sehr Leistungseinstellungen wie Zuverlässigkeitseinstellungen. Lange hängende Anfragen halten Pool-Verbindungen und verhungern andere Anfragen.
Anweisungs-Timeout
Setze ein Zeitlimit pro Anweisung, damit eine Runaway-Abfrage eine Pool-Verbindung nicht unbegrenzt hält. Für PDO_SQLSRV:
<?php
$stmt = $conn->prepare("SELECT ... FROM dbo.HugeTable ...");
$stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, 30); // seconds
$stmt->execute();
Für SQLSRV gib das Options-Array an sqlsrv_query oder sqlsrv_prepareein."QueryTimeout" => 30
Setze einen Wert, der zu deiner Arbeitsbelastung passt. Für eine synchrone Webanfrage sind typischerweise 15 bis 30 Sekunden. Für einen Hintergrund-Batch-Job könnten mehrere Minuten sinnvoll sein. Setze den Timeout bei einer Webanfrage niemals auf null (unbegrenzt).
Anmeldungstimeout
LoginTimeoutIn der Verbindungszeichenfolge steuert er, wie lange der Treiber wartet, um eine Verbindung herzustellen. Setze einen expliziten Wert beim Verbinden mit Azure SQL-Datenbank oder Azure SQL Managed Instance, damit Cold Starts und Failover-Group-Failovers den Client nicht unbegrenzt blockieren. Werte von 30 bis 90 Sekunden funktionieren für die meisten Cloud-Workloads gut. Details zur Größenbestimmung LoginTimeout gegen ConnectRetryCount * ConnectRetryInterval und den daraus resultierenden Fehlermodi finden Sie unter Verbindungszeit. Für die Optionsreferenz siehe Verbindungsoptionen.
Nur-lese-Workloads an eine Replik routen
Für schreibgeschützte Abfragen gegen eine Datenbank in einer Always On-Verfügbarkeitsgruppe, Azure SQL Managed Instance oder Azure SQL-Datenbank mit Read-Scale-out oder Geo-Replica fügen Sie Ihrer Verbindungszeichenfolge hinzuApplicationIntent=ReadOnly:
<?php
$dsn = "sqlsrv:Server=<listener>;Database=<database>;" .
"Encrypt=true;ApplicationIntent=ReadOnly";
Nur-Lese-Routing sendet die Verbindung an eine synchronisierte sekundäre Replik und entlastet die Arbeit von der primären. Kombinieren Sie dies mit MultiSubnetFailover=true der schnellsten Verbindung zu Multi-Subnet-Verfügbarkeitsgruppen-Listenern.
Beobachte die Leistung vom Server aus
Das clientseitige Timing sagt nur, wie lange eine Anfrage von Anfang zu Ende gedauert hat. Um herauszufinden, warum es langsam war, nutzen Sie die integrierten Diagnosen von SQL Server.
Abfragespeicher
Abfragespeicher erfasst Ausführungspläne, Laufzeitstatistiken und Wartestatistiken für jede Abfrage in der Datenbank. Sie ist standardmäßig in Azure SQL-Datenbank, Azure SQL Managed Instance und SQL Database in Fabric aktiviert. Auf SQL Server aktivieren Sie es pro Datenbank:
ALTER DATABASE <database_name> SET QUERY_STORE = ON;
Dann nutzen Sie die Abfragespeicher-Berichte von SQL Server Management Studio, um Ihre langsamsten und am häufigsten ausgeführten Abfragen zu finden. Siehe Überwachung der Leistung mit dem Abfragespeicher.
Azure SQL Query Performance Insight
Für die Azure SQL-Datenbank zeigt das Query Performance Insight des Azure-Portals automatisch die ressourcenintensivsten Anfragen ohne Konfiguration an. Weitere Informationen finden Sie unter Query Performance Insight für Azure SQL-Datenbank.
SET STATISTICS für einmalige Untersuchung
Für eine einzelne Abfrage, die Sie profilieren möchten, führen Sie sie in SQL Server Management Studio mit aktivierten Statistiken aus:
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
-- your query here
Hohe logische Lesungen bedeuten fast immer einen fehlenden oder unbrauchbaren Index. Hohe CPU-Zeit bei niedrigen logischen Lesevorgängen bedeutet meist einen schlechten Plan (Parameter-Sniffing, eine implizite Konvertierung, die die Verwendung von Indexen verhindert, oder eine skalare Funktion, die Parallelität verhindert).
Erweiterte Events für Fahrerverfolgung auf Fahrerebene
Um genau zu sehen, was der Treiber an SQL Server sendet (einschließlich der tatsächlichen Parameterwerte, die er interpoliert), erfassen Sie eine Extended Events-Sitzung mit den und sql_batch_completed Eventsrpc_completed.
Prüfliste für die Leistung
Verwenden Sie diese Checkliste als Pre-Deployment-Review jeder PHP-Anwendung, die sich mit dem SQL Server verbindet:
| Area | Prüfen | Reference |
|---|---|---|
| Connection | Connection Pooling ist für die Plattform aktiviert und konfiguriert | Verwaltung von Verbindungen effizient |
| Connection | Die Anwendung verwendet Verbindungen innerhalb einer Anfrage erneut und öffnet keine Verbindungen pro Abfrage | Verwenden Sie die Verbindung innerhalb einer Anfrage erneut |
| Connection |
LoginTimeoutbehandelt Cold Starts und Failover für Azure SQL |
Login-Timeout |
| Query | Abfragen wählen nur die benötigten Spalten aus, nein SELECT * |
Wählen Sie nur die Spalten aus, die Sie verwenden |
| Query | Das Filtern erfolgt in SQL, nicht in PHP mit array_filter |
Hol nur die Reihen, die du brauchst, |
| Query | Große Ergebnismengen sind paginiert mit OFFSET ... FETCH |
Paginate-große Ergebnismengen |
| Query | Gespeicherte Prozeduren-Menge SET NOCOUNT ON |
Bevorzugen SET NOCOUNT ON |
| Einfügungen | Bulk-Inserts verwenden tabellenwertige Parameter, keine pro-Zeilen-Schleifen | Daten effizient einfügen |
| Erklärungen |
PDO::ATTR_EMULATE_PREPARES ist auf false festgelegt. |
Bevorzuge einheimische Zubereitungen |
| Erklärungen | Die Anwendung verwendet vorbereitete Anweisungen über Ausführungen hinweg erneut | Wiederverwenden vorbereiteter Aussagen |
| Cursors | Die Anwendung verwendet den standardmäßig nur vorwärtsgerichteten Cursor, es sei denn, es ist ein Puffern erforderlich. | Cursor und Speicher verwalten |
| Gedächtnis | Große Binär- und Zeichenwerte werden gestreamt, nicht materialisiert | Ströme große Binär- und Zeichenwerte |
| Timeouts | Für alle benutzerorientierten Abfragen ist ein Statement-Timeout gesetzt | Aussage-Timeout |
| Routing | Schreibgeschützte Workloads werden dort gesetzt ApplicationIntent=ReadOnly , wo eine Replik existiert |
Routen nur lesebezogene Workloads |
| Beobachtbarkeit | Abfragespeicher ist aktiviert und wird regelmäßig überprüft | Abfragespeicher |