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 Masseneinfügungen. 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.
Verwalten Sie Verbindungen effizient
Der Verbindungsaufbau ist die mit Abstand aufwendigste Operation, die der Treiber ausführt. Fast jede PHP-Leistungsuntersuchung endet mit einer Lösung für die Verbindungsverwaltung.
Aktivieren von Verbindungspooling
Beim Pooling werden ODBC-Verbindungen über mehrere PHP-Anfragen hinweg wiederverwendet, anstatt sie am Ende der Anfrage zu schließen. Das Verbindungsobjekt wird verworfen, wenn Ihr Skript endet, aber das zugrunde liegende ODBC-Handle bleibt im vom ODBC-Treibermanager verwalteten Pool bestehen und wird für die nächste Anfrage wiederverwendet, die dieselbe Verbindungszeichenfolge anfordert.
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 Treiber-Manager, indem Sie Pooling=Yes im Abschnitt [ODBC] von odbcinst.ini festlegen und einen positiven Wert für CPTimeout unter der Zeilengruppe des Treibers festlegen. 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 ein, dass die meisten Anfragen eine gepoolte Verbindung finden, aber niedrig genug, dass veraltete Verbindungen zu einem Failoverserver hinreichend schnell ausgemustert 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 Timeouts für die erste Abfrage festlegst, berücksichtige dieses Verhalten, oder deaktiviere MARS mit MultipleActiveResultSets=false, wenn du es nicht benötigst. Beachten Sie den Hinweis zu MARS und Pooling unter Verbindungspooling.
Persistente PDO-Verbindungen werden nicht unterstützt
PDO_SQLSRV lehnt PDO::ATTR_PERSISTENTab. Das Festlegen für den Konstruktor löst Folgendes aus:
SQLSTATE[IMSSP]: An unsupported attribute was designated on the PDO object.
Verwenden Sie ODBC-Verbindungspooling für die anfrageübergreifende Wiederverwendung. Es ist der treibereigene Mechanismus, funktioniert sowohl für PDO_SQLSRV als auch für SQLSRV und beendet Leerlaufverbindungen bei CPTimeout (wodurch auch die Erneuerung von Microsoft-Entra-Token zuverlässig funktioniert).
Verwenden Sie die Verbindung innerhalb einer Anfrage erneut
Auch bei aktiviertem Pooling verursacht das Öffnen einer neuen PDO- oder SQLSRV-Verbindung einen ODBC-Roundtrip, um ein gepooltes Handle abzurufen und zu validieren. Öffne eine Verbindung einmal pro Anfrage und übergebe sie an jede Funktion, die sie benötigt.
Tipp
Ein Container für die Abhängigkeitsinjektion oder ein Lazy Accessor ist ausreichend. Es geht darum, new PDO(...) mitten in einem Request-Handler zu vermeiden.
Fragen Sie nur nach dem, was Sie brauchen
Netzwerkroundtrips und die Materialisierung von Resultsets 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 * ruft jede Spalte ab, einschließlich varchar(max)- und varbinary(max)-Spalten, die deutlich größer sind als die Daten, die du tatsächlich verwendest. 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");
Rufen Sie nur die Zeilen ab, die Sie benötigen
Verlagern Sie das Filtern an SQL Server. Rufen Sie niemals eine vollständige Tabelle in PHP ab, nur um sie in einer foreach-Schleife 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). - Verwende
PDO::FETCH_KEY_PAIRoderPDO::FETCH_UNIQUEzum Erstellen von Suchwörterbüchern ohne einen zweiten Durchlauf.
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, UPDATE und DELETE Anweisung gibt ein DONE_IN_PROC-Token mit der Anzahl der betroffenen Zeilen zurück, das PHP typischerweise verwirft. Das Token fügt keinen Roundtrip hinzu, aber jedes Token kostet dennoch Bytes bei der Übertragung und einen geringen Arbeitsaufwand für den Treiber. In einer Prozedur oder einem Batch mit mehreren Anweisungen, die bzw. der Hunderte von Anweisungen pro Aufruf 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 je nachdem, wie viele Zeilen Sie verschieben, die richtige Einfügemethode. Die falsche Wahl kann hundertmal langsamer sein.
Weniger als etwa 100 Zeilen: vorbereitete Anweisung in einer Schleife
Führen Sie für kleine Batches 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"]]);
}
Umschließen Sie die Schleife in einer Transaktion, sodass für alle Einfügungen ein Commit als eine Einheit ausgeführt wird und das Protokoll nicht nach jeder Zeile geleert 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
Tabellenwertige Parameter (TVPs) senden den gesamten Batch in einem einzigen Roundtrip an SQL Server und ermöglichen es SQL Server, die Datenmenge als eine einzige Anweisung zu 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 einen TVP als assoziatives Array, dessen Schlüssel der Typname und dessen Wert der Zeilensatz 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. Schreiben Sie Ihre Daten in eine durch Trennzeichen getrennte Datei oder eine Datei im nativen Format, und führen Sie dann bcp oder BULK INSERT über einen geplanten Auftrag, einen ETL-Schritt oder ein Administratorskript aus.
Caution
Wenn Sie von PHP mit shell_exec() oder proc_open() bcp aufrufen, interpolieren Sie niemals nicht vertrauenswürdige Eingaben in die Befehlszeile. Verwenden Sie escapeshellarg() für jedes Argument, und führen Sie den Ladevorgang vorzugsweise außerhalb des Pfads einer Webanforderung aus.
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.
Kombinieren verwandter Anweisungen in einem einzelnen Batch
Fügen Sie für verwandte Aufgaben, die zusammen ausgeführt werden, die Anweisungen in einen Batch ein, und nutzen Sie jedes Resultset:
<?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 können Sie keine neue Abfrage für eine Verbindung ausgeben, die noch über ein offenes Resultset verfügt. Beide Treiber aktivieren MARS standardmäßig. Um dies zu deaktivieren, legen Sie in Ihrer Verbindungszeichenfolge MultipleActiveResultSets=false fest.
Siehe Deaktivieren mehrerer aktiver Ergebnissätze (MARS).
MARS ist bequem, aber nicht kostenlos. Jede aktive Ergebnismenge verbraucht serverseitige Ressourcen. Nutzen Sie ein Resultset vorzugsweise vollständig, bevor Sie ein anderes starten. Nutze MARS, um wirklich verschachtelte Cursormuster zu entblocken.
Optimieren vorbereiteter Anweisungen
Prepared Statements ersparen dem Treiber das erneute Parsen von SQL auf dem Server und ermöglichen Ihnen, nicht vertrauenswürdige Eingaben sicher als Parameter zu binden.
Bevorzugen nativer Vorbereitungen
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 behalten den SQL-Text im Client bei und erstellen eine vollständige SQL-Zeichenfolge neu, bei der Parameter bei jeder Ausführung interpoliert werden.
Legen Sie PDO::ATTR_EMULATE_PREPARES => false fest, damit der Treiber 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
Einmal vorbereiten, vielfach ausführen. Jeder Aufruf prepare() kostet die Zuweisung eines ODBC-Handles und einen serverseitigen Parse-Vorgang. Halten Sie in einer Hot Loop das $stmt-Objekt aktiv, und rufen Sie execute() 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 zum Zeitpunkt der Vorbereitung eine feste Anzahl an Platzhaltern. 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). Wandeln Sie die Anzahl mit (int) um, bevor Sie die Platzhalterzeichenfolge erstellen, und übergeben Sie die tatsächlichen Werte immer über execute() als Parameter.
Cursor und Speicher verwalten
Der Standardcursortyp ist PDO::CURSOR_FWDONLY, ein Firehose-Cursor (nur vorwärts). 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 statischer, clientseitig gepufferter Cursor) ruft die gesamte Ergebnismenge vorab in den PHP-Speicher ab. Dieser Ansatz ermöglicht es, rowCount() aufzurufen, zurückzuspringen und die Anweisung wiederzuverwenden. Standardmäßig ist der Puffer über PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE auf 10.240 KB (10 MB) begrenzt, und eine Abfrage, deren Ergebnismenge das Limit überschreitet, gibt false zurück, anstatt den PHP-Speicher zum Überlaufen zu bringen. Sie können die Obergrenze bis an das PHP-Speicherlimit anheben, aber damit tauschen Sie einen false Rückgabewert gegen einen echten Allowed memory size exhausted fatalen Fehler ein, wenn eine Abfrage die neue Obergrenze überschreitet. Nehmen Sie die Optimierung bewusst vor. 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 belegen jedoch serverseitige Ressourcen für die Dauer des Cursors und sind pro Zeile langsamer als reine Vorwärtscursor.
Verwende für Streaming-Lesevorgänge die standardmäßige Nur-Vorwärtsoption. Verwenden Sie gepufferte clientseitige Cursor für kleine Resultsets, wenn Sie rowCount() oder das Scrollen rückwärts benötigen. 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).
Streamen großer 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);
Beim Einfügen oder Aktualisieren großer Werte verwenden Sie in SQLSRV SendStreamParamsAtExec=false, um nach sqlsrv_execute() Streamdaten in Blöcken zu senden. Weitere Informationen finden Sie unter Daten streamen.
Einstellen geeigneter Timeouts
Timeouts sind sowohl Leistungseinstellungen als auch Zuverlässigkeitseinstellungen. Lang andauernde Abfragen belegen Poolverbindungen und blockieren andere Anforderungen.
Anweisungs-Timeout
Legen Sie ein Zeitlimit pro Anweisung fest, damit eine außer Kontrolle geratene Abfrage eine Poolverbindung nicht auf unbestimmte Zeit belegt. 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 übergib "QueryTimeout" => 30 im Options-Array an sqlsrv_query oder sqlsrv_prepare.
Setze einen Wert, der zu deiner Arbeitsbelastung passt. Für eine synchrone Webanfrage sind typischerweise 15 bis 30 Sekunden. Für einen Batchjob im Hintergrund könnten mehrere Minuten angemessen 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. Legen Sie beim Herstellen einer Verbindung mit Azure SQL-Datenbank oder Azure SQL Managed Instance einen expliziten Wert fest, damit Kaltstarts und Failover von Failovergruppen den Client nicht auf unbestimmte Zeit blockieren. Werte von 30 bis 90 Sekunden funktionieren für die meisten Cloud-Workloads gut. Ausführliche Informationen zur Größenanpassung von LoginTimeout im Vergleich zu ConnectRetryCount * ConnectRetryInterval und den resultierenden Fehlermodi finden Sie unter Verbindungstimeout. Für die Optionsreferenz siehe Verbindungsoptionen.
Weiterleiten schreibgeschützter Workloads an ein Replikat
Fügen Sie für schreibgeschützte Abfragen für eine Datenbank in einer Always On-Verfügbarkeitsgruppe, in Azure SQL Managed Instance oder in Azure SQL-Datenbank mit horizontaler Leseskalierung oder einem Georeplikat ApplicationIntent=ReadOnly zu Ihrer Verbindungszeichenfolge hinzu:
<?php
$dsn = "sqlsrv:Server=<listener>;Database=<database>;" .
"Encrypt=true;ApplicationIntent=ReadOnly";
Das schreibgeschützte Routing sendet die Verbindung an ein synchronisiertes sekundäres Replikat, wodurch das primäre Replikat entlastet wird. Kombinieren Sie dies mit MultiSubnetFailover=true für die schnellste Verbindung zu Listenern von Verfügbarkeitsgruppen mit mehreren Subnetzen.
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 Lesevorgänge deuten fast immer auf einen fehlenden oder nicht verwendbaren Index hin. 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 Ereignisse für die Ablaufverfolgung auf Treiberebene
Um genau zu sehen, was der Treiber an SQL Server sendet (einschließlich der tatsächlichen Parameterwerte, die er interpoliert), erfassen Sie mithilfe der Ereignisse rpc_completed und sql_batch_completed eine Extended Events-Sitzung.
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 | Die Verbindungspoolung ist für die Plattform aktiviert und konfiguriert | Verbindungen effizient verwalten |
| Connection | Die Anwendung verwendet Verbindungen innerhalb einer Anfrage erneut und öffnet keine Verbindungen pro Abfrage | Verwenden Sie die Verbindung innerhalb einer Anfrage erneut |
| Connection |
LoginTimeoutdeckt Kaltstarts und Failover für Azure SQL ab |
Anmeldungstimeout |
| 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 |
Rufe nur die Zeilen ab, die du benötigst |
| Query | Große Ergebnismengen sind paginiert mit OFFSET ... FETCH |
Große Ergebnismengen paginieren |
| Query | Gespeicherte Prozeduren legen SET NOCOUNT ON fest |
Bevorzugt SET NOCOUNT ON |
| Einfügungen | Masseneinfügungen verwenden Tabellenwertparameter, keine zeilenweisen 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 | Anweisungstimeout |
| Routing | Schreibgeschützte Workloads legen ApplicationIntent=ReadOnly fest, wenn ein Replikat vorhanden ist |
Weiterleiten schreibgeschützter Workloads |
| Beobachtbarkeit | Abfragespeicher ist aktiviert und wird regelmäßig überprüft | Abfragespeicher |
Verwandte Inhalte
- Connection Pooling (Microsoft-Treiber für PHP für SQL Server)
- Verbindungsoptionen
- Cursortypen (PDO_SQLSRV-Treiber)
- Cursortypen (SQLSRV-Treiber)
- Verwenden Sie tabellenwertige Parameter (PHP)
- Fehlerbehebung der Microsoft-Treiber für PHP für SQL Server
- Überwachen Sie die Leistung mithilfe des Abfragespeichers