Widerstandsfähigkeit inaktiver Verbindungen

PHP-Treiber herunterladen

Verbindungsresilienz ermöglicht es dem Microsoft ODBC-Treiber für SQL Server, eine unterbrochene Leerlaufverbindung wiederherzustellen, wenn Server und Sitzungszustand die Wiederherstellung unterstützen. Die PHP-Erweiterungen geben ConnectRetryInterval und ConnectRetryCount an ODBC weiter; sie implementieren das Reconnection-Protokoll nicht. Die ODBC-Dokumentation listet Unterstützung für SQL Server 2014 und spätere Versionen, Azure SQL-Datenbank und SQL-Datenbank in Microsoft Fabric auf. Leiten Sie die Warehouse-Unterstützung nicht aus der Unterstützung für SQL-Datenbanken in Fabric ab.

Schlüsselwort Werte Standard Beschreibung
ConnectRetryCount Ganzzahl zwischen 0 und 255 (einschließlich) 1 Die maximale Anzahl von Versuchen zum Wiederherstellen einer unterbrochenen Verbindung, bevor aufgegeben wird. Ein Wert von 0 deaktiviert die Wiederverbindung.
ConnectRetryInterval Ganze Zahl zwischen 1 und 60 (einschließlich) 10 Die Anzahl der Sekunden, die zwischen den Versuchen gewartet wird. Wird ignoriert, wenn ConnectRetryCount0 ist.

Der Abfrage-Timeout der Anweisung begrenzt, wie lange ODBC während der Ausführung eine inaktive Verbindung wiederherstellen kann. Legen Sie PDO::SQLSRV_ATTR_QUERY_TIMEOUT in den SQLSRV-Anweisungsoptionen oder QueryTimeout für PDO_SQLSRV fest. LoginTimeout steuert den Verbindungsaufbau; eine Erhöhung verlängert nicht das Wiederherstellungsbudget der Anweisung.

Setze dieselben Retry-Schlüsselwörter in einem SQLSRV-Verbindungsoptions-Array, wie ["ConnectRetryCount" => 2, "ConnectRetryInterval" => 5], oder in einem PDO_SQLSRV Datenquellennamen (DSN), wie ConnectRetryCount=2;ConnectRetryInterval=5. Diese Einstellungen spielen eine fehlgeschlagene Transaktion nicht erneut ab und versuchen auch nicht, den initialen Verbindungsaufruf der Anwendung erneut.

Für Wiederholungsversuche auf Anwendungsebene bei transienten Fehlern, die während der Ausführung einer Abfrage auftreten (was die Resilienz bei Leerlaufverbindungen nicht abdeckt), siehe das Wiederholungsmuster in Microsoft Drivers for PHP for SQL Server.

Wählen Sie die Wiederherstellungsrichtlinie

Die Wiederherstellung inaktiver Verbindungen hält eine wiederherstellbare Sitzung aufrecht, sodass die Anwendung weiterhin ihren PHP-Verbindungshandle verwenden kann. Es ist keine allgemeine Anweisung, einen Befehl erneut auszuführen.

Fehler Bewältigt die Resilienz inaktiver Verbindungen das? Aktion der Anwendung
Die anfängliche Verbindung lässt sich nicht herstellen No. Es gibt keine bestehende Sitzung zur Wiederherstellung. Untersuchen Sie den Verbindungsfehler. Versuchen Sie nur bei einem vorübergehenden Fehler mit einer begrenzten Richtlinie erneut.
Eine inaktive, wiederherstellbare Verbindung wird getrennt ODBC kann eine Wiederherstellung versuchen, wenn die Verbindung erneut genutzt wird. Setze den Statement-Timeout für die beabsichtigten Wiederherstellungsversuche ausreichend hoch; behandle trotzdem einen letzten Fehlschlag.
Eine Anweisung schlägt während der Ausführung fehl Gehe nicht von Wiederherstellung oder Wiedergabe aus. Inspizieren Sie den Fehler und stellen Sie fest, ob die Operation abgeschlossen ist. Wiederholen Sie einen Lesevorgang nur dann, wenn es für Ihre Anwendung akzeptabel ist, ihn zu wiederholen.
Eine Verbindung wird während einer Transaktion unterbrochen Gehen Sie nicht davon aus, dass die Transaktion fortgesetzt werden kann. Brechen Sie die fehlerhafte Sitzung ab und stellen Sie fest, ob die Transaktion festgeschrieben wurde, bevor Sie einen weiteren Versuch planen.
Der Client schließt explizit seine PHP-Verbindung No. Das Schließen eines Handles ist eine Bereinigung durch die Anwendung, keine Leerlauftrennung. Öffne eine neue Verbindung, falls noch mehr Arbeit nötig ist.

Halten Sie die Wiederherstellungsintervalle und Abfragefristen konsistent. Zum Beispiel verwenden die Programme zwei Wiederverbindungsversuche mit einem 5-Sekunden-Intervall und einem 30-sekündigen Statement-Timeout. Auch Verbindungsversuche brauchen Zeit; das Produkt aus Anzahl und Intervall ist keine garantierte Gesamterholungsdauer. Ein letzter Timeout erfordert weiterhin Fehlerbehandlung.

Bemerkungen

Die Verbindungsresilienz gilt nur, wenn die Verbindung inaktiv ist. Ein Fehlschlag während einer Transaktion löst keine Wiederverbindung aus. Die folgenden Sitzungszustände verhindern ebenfalls eine Neuverbindung:

  • Temporäre Tabellen.
  • Globale und lokale Cursor.
  • Transaktionskontext und Transaktionssperren auf Sitzungsebene.
  • Anwendungsblockierungen.
  • EXECUTE AS / REVERT Sicherheitskontext.
  • Object Linking and Embedding (OLE) Automatisierungs-Handles.
  • Vorbereitete XML-Handles.
  • Traceflags.

Beispiel

Jedes Programm öffnet zwei Verbindungen zu einer Test-SQL Server-Instanz. Die zweite Verbindung beendet die erste Sitzung mit KILL. Eine anschließende Abfrage bei der ersten Verbindung löst ODBCs Idle-Connection Recovery aus. Die Abfrage verwendet Inline-Zeilen, daher ist keine Tabelleneinrichtung erforderlich. Die Programme geben jedes Statement frei, bevor sie die Verbindung unterbrechen, und überprüfen sowohl die Werte der wiederhergestellten Abfrage als auch eine geänderte Server-Anmeldezeit. Eine Sitzungs-ID allein ist kein Wiederherstellungsnachweis, weil SQL Server sie wiederverwenden kann.

Caution

Führe dieses Beispiel nur gegen eine Testinstanz aus. Der Login benötigt die Berechtigung, eine Sitzung mit KILL zu beenden. Der Code beendet nur die gerade eröffnete Sitzung. Ersetzen Sie nicht die Sitzungs-ID einer anderen Anwendung.

Setze SQL_SERVER, SQL_DATABASE, SQL_USER, und SQL_PASSWORD in der Prozessumgebung. Konfigurieren Sie ein vertrauenswürdiges Serverzertifikat, dessen Name mit SQL_SERVER übereinstimmt. Dieses Beispiel verwendet SQL-Authentifizierung für eine SQL Server-Testinstanz. Es handelt sich nicht um ein Beispiel für Fabric- oder Managed-Identity-Authentifizierung.

Speichere den tatsächlichen Datenbanknamen, Benutzernamen und Passwort in den Umgebungsvariablen. Jedes Programm maskiert diese Werte einmal für die ODBC-ähnliche Verbindungsbehandlung des PHP-Treibers, indem schließende geschweifte Klammern verdoppelt und jeder Wert in geschweifte Klammern eingeschlossen wird. Beide Verbindungen verwenden die maskierten Werte wieder.

SQLSRV

Speichere das Programm als recover-sqlsrv.php, und führe php recover-sqlsrv.php aus. Der Helper wirft bei SQLSRV-Fehler; er versucht keine Anweisung erneut.

<?php
function requireSqlsrv($result)
{
    if ($result === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    return $result;
}

$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $value = getenv($name);
    if ($value === false || $value === '') {
        throw new RuntimeException("Set $name before running this example.");
    }
    $config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$connectionInfo = [
    'Database' => $config['SQL_DATABASE'],
    'UID' => $config['SQL_USER'],
    'PWD' => $config['SQL_PASSWORD'],
    'Encrypt' => true,
    'TrustServerCertificate' => false,
    'LoginTimeout' => 30,
    'ConnectRetryCount' => 2,
    'ConnectRetryInterval' => 5,
];
$conn = requireSqlsrv(sqlsrv_connect($config['SQL_SERVER'], $connectionInfo));
$breaker = null;
try {
    $breaker = requireSqlsrv(sqlsrv_connect($config['SQL_SERVER'], $connectionInfo));
    $sessionQuery = "SELECT @@SPID AS SessionId, "
        . "CONVERT(varchar(33), login_time, 126) AS LoginTime "
        . "FROM sys.dm_exec_sessions WHERE session_id = @@SPID";
    $idStatement = requireSqlsrv(sqlsrv_query($conn, $sessionQuery));
    $before = requireSqlsrv(sqlsrv_fetch_array($idStatement, SQLSRV_FETCH_ASSOC));
    sqlsrv_free_stmt($idStatement);
    if (!is_array($before) || !is_int($before['SessionId']) || $before['SessionId'] <= 0) {
        throw new RuntimeException('Expected the current session identity.');
    }
    $spid = $before['SessionId'];

    $query = 'SELECT id FROM (VALUES (1), (2), (3)) AS Demo(id) ORDER BY id';
    $options = ['Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED, 'QueryTimeout' => 30];
    $stmt = requireSqlsrv(sqlsrv_query($conn, $query, [], $options));
    if (requireSqlsrv(sqlsrv_num_rows($stmt)) !== 3) {
        throw new RuntimeException('Expected three rows before the disconnect.');
    }
    sqlsrv_free_stmt($stmt);

    // KILL doesn't accept a parameter marker; $spid is a checked server-returned integer.
    $kill = requireSqlsrv(sqlsrv_query($breaker, 'KILL ' . $spid));
    sqlsrv_free_stmt($kill);
    // Allow the simulated disconnect to reach the client before the next query.
    sleep(1);

    $stmt = requireSqlsrv(sqlsrv_query($conn, $query, [], $options));
    $ids = [];
    while (($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) !== null) {
        $row = requireSqlsrv($row);
        $ids[] = $row['id'];
    }
    if ($ids !== [1, 2, 3]) {
        throw new RuntimeException('Unexpected recovered rows or PHP types.');
    }
    sqlsrv_free_stmt($stmt);

    $idStatement = requireSqlsrv(sqlsrv_query($conn, $sessionQuery));
    $after = requireSqlsrv(sqlsrv_fetch_array($idStatement, SQLSRV_FETCH_ASSOC));
    sqlsrv_free_stmt($idStatement);
    if (!is_array($after) || $after['LoginTime'] === $before['LoginTime']) {
        throw new RuntimeException('Expected a new physical session.');
    }
    echo "Recovered connection: 3 matching rows; login time changed.\n";
} finally {
    if (is_resource($breaker)) {
        sqlsrv_close($breaker);
    }
    sqlsrv_close($conn);
}

PDO_SQLSRV

Speichere dieses separate Programm als recover-pdo.php, und führe php recover-pdo.php mit derselben Umgebung und denselben Berechtigungen aus. Es setzt das numerische Abrufen explizit so, dass die Integer-Assertions mit dem SQLSRV-Programm übereinstimmen. PDO hat keinen sqlsrv_errors()-Aufruf; Operationsfehler lösen im Ausnahmemodus PDOException aus.

<?php
$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $value = getenv($name);
    if ($value === false || $value === '') {
        throw new RuntimeException("Set $name before running this example.");
    }
    $config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$server = $config['SQL_SERVER'];
$database = $config['SQL_DATABASE'];
$dsn = "sqlsrv:Server=$server;Database=$database;Encrypt=true;"
    . "TrustServerCertificate=false;LoginTimeout=30;"
    . "ConnectRetryCount=2;ConnectRetryInterval=5";
$attributes = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
    PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 30,
];
$pdo = new PDO($dsn, $config['SQL_USER'], $config['SQL_PASSWORD'], $attributes);
$breaker = null;
$stmt = null;
try {
    $breaker = new PDO($dsn, $config['SQL_USER'], $config['SQL_PASSWORD'], $attributes);
    $sessionQuery = "SELECT @@SPID AS SessionId, "
        . "CONVERT(varchar(33), login_time, 126) AS LoginTime "
        . "FROM sys.dm_exec_sessions WHERE session_id = @@SPID";
    $stmt = $pdo->query($sessionQuery);
    $before = $stmt->fetch(PDO::FETCH_ASSOC);
    $stmt = null;
    if (!is_array($before) || !is_int($before['SessionId']) || $before['SessionId'] <= 0) {
        throw new RuntimeException('Expected the current session identity.');
    }
    $spid = $before['SessionId'];
    $query = 'SELECT id FROM (VALUES (1), (2), (3)) AS Demo(id) ORDER BY id';
    $stmt = $pdo->query($query);
    if ($stmt->fetchAll(PDO::FETCH_COLUMN) !== [1, 2, 3]) {
        throw new RuntimeException('Unexpected initial rows or PHP types.');
    }
    $stmt = null;

    // KILL requires a checked integer, not a parameter marker.
    $stmt = $breaker->query('KILL ' . $spid);
    $stmt = null;
    sleep(1);

    $stmt = $pdo->query($query);
    if ($stmt->fetchAll(PDO::FETCH_COLUMN) !== [1, 2, 3]) {
        throw new RuntimeException('Unexpected recovered rows or PHP types.');
    }
    $stmt = null;
    $stmt = $pdo->query($sessionQuery);
    $after = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!is_array($after) || $after['LoginTime'] === $before['LoginTime']) {
        throw new RuntimeException('Expected a new physical session.');
    }
    echo "Recovered connection: 3 matching rows; login time changed.\n";
} finally {
    $stmt = null;
    $breaker = null;
    $pdo = null;
}

Erwartete Ausgabe von einem der beiden Programme, wenn ODBC die Verbindung wiederherstellt:

Recovered connection: 3 matching rows; login time changed.

Der Test beendet eine absichtlich inaktive Sitzung. Es testet keine Netzwerkpartition, Service-Failover, aktive Transaktionswiederherstellung, Schreibwiedergabe oder Azure-Throttling. Interpretiere ein erfolgreiches Ergebnis nicht als Beweis, dass jede unterbrochene Anwendungsoperation sicher wiederholt werden kann.

Fehlgeschlagene Wiederherstellung diagnostizieren

Symptom Prüfen
KILL wird abgelehnt Verwenden Sie eine zulässige Testidentität oder bitten Sie den Administrator, den kontrollierten Test durchzuführen. Ersetze keine Sitzungs-ID durch eine andere.
Die erste Abfrage schlägt bereits fehl Beheben Sie die Verbindung, das Zertifikat, die Abfrage und die Berechtigungen, bevor Sie die Wiederherstellung testen.
Die Abfrage nach KILL schlägt fehl Überprüfen ConnectRetryCountSie , das Abfrage-Timeout, ODBC-Diagnosen und ob der Sitzungszustand wiederherstellbar ist. Verstecke den Fehler nicht mit einem Retry ohne Bedingung.
Eine temporäre Tabelle verschwindet, nachdem eine neue Verbindung eröffnet wurde Eine von der Anwendung erstellte Ersatzverbindung ist nicht die alte Session. Sitzungsabhängige Arbeit neu gestalten, anstatt von automatischer Zustandsrekonstruktion auszugehen.
Ein Schreibvorgang liefert einen Verbindungsfehler zurück Bestimmen Sie dessen Ausgang, bevor Sie es erneut versuchen. Der Client-Fehler allein beweist nicht, dass der Server es zurückgesetzt hat.

Bewahren Sie Diagnosen in geschützten Protokollen auf. SQLSRV stellt eine Fehlersammlung bereit, die mehr als einen Datensatz enthalten kann; PDO liefert SQLSTATE- und Treiberdetails über die Ausnahme. Zeichnen Sie die Operation und deren Timeout-Einstellungen auf, ohne Passwörter oder Token zu notieren. Weitere Informationen finden Sie unter Protokollierungsaktivität.

Anwendbarkeit des Ziels

ODBC und das SQL-Ziel führen die automatische Wiederverbindung durch. PHP übergibt Einstellungen und meldet Erfolg oder Misserfolg. Es erstellt temporäre Objekte nicht erneut, stellt keine Variablen Ihrer Anwendung wieder her und spielt keine Transaktion erneut ab.

Die kontrollierten Programme verwenden SQL Server und benötigen die Erlaubnis, ihre eigene Testsitzung über eine zweite Verbindung zu beenden. Azure SQL-Datenbank und SQL Database in Fabric haben eigene Serviceberechtigungen und Authentifizierungsanforderungen. Behandeln Sie diese Programme nicht als Drop-in-Tests für Azure SQL Managed Instance, Azure Synapse Analytics oder Warehouse in Fabric und leiten Sie ihre Unterstützung für die Wiederherstellung auch nicht daraus ab, dass eine gewöhnliche Verbindung erfolgreich ist. Überprüfen Sie die Unterstützung für die Wiederherstellung des ausgewählten Dienstes, bevor Sie sich darauf verlassen. Warehouse und SQL-Datenbank in Fabric bleiben unterschiedliche Ziele.