Tolerantie van niet-actieve verbindingen

PHP-stuurprogramma downloaden

Verbindingsveerkracht stelt de Microsoft ODBC-driver voor SQL Server in staat een gebroken idle-verbinding te herstellen wanneer de server en de sessietoestand herstel ondersteunen. De PHP-extensies geven ConnectRetryCount en ConnectRetryInterval door aan ODBC; zij implementeren het reconnection-protocol niet. De ODBC-documentatie vermeldt ondersteuning voor SQL Server 2014 en latere versies, Azure SQL Database en SQL-database in Microsoft Fabric. Leid Warehouse-ondersteuning niet af van ondersteuning voor SQL-databases in Fabric.

Keyword Waarden Verstek Description
ConnectRetryCount Geheel getal tussen 0 en 255 (inclusief) 1 Het maximum aantal pogingen om een verbroken verbinding te herstellen voordat u het opgeeft. Een waarde van 0 schakelt herverbinding uit.
ConnectRetryInterval Geheel getal tussen 1 en 60 (inclusief) 10 Het aantal seconden om te wachten tussen pogingen. Genegeerd als ConnectRetryCount0 is.

De querytimeout van de instructie beperkt hoe lang ODBC nodig heeft voor het herstellen van een idle-verbinding tijdens de uitvoering. Stel QueryTimeout in in SQLSRV-statementopties of PDO::SQLSRV_ATTR_QUERY_TIMEOUT voor PDO_SQLSRV. LoginTimeout controleert het tot stand brengen van een verbinding; het verhogen ervan verlengt het herstelbudget van de instructie niet.

Stel dezelfde opnieuw proberen-trefwoorden in in een SQLSRV-verbindingsoptiesarray, zoals ["ConnectRetryCount" => 2, "ConnectRetryInterval" => 5], of in een PDO_SQLSRV-databronnaam (DSN), zoals ConnectRetryCount=2;ConnectRetryInterval=5. Deze instellingen spelen een mislukte transactie niet opnieuw af en proberen de initiële verbindingsaanroep van de applicatie niet opnieuw.

Voor applicatieniveau herkansing van tijdelijke fouten die optreden terwijl een query wordt uitgevoerd (wat idle-verbindingsveerkracht niet dekt), zie het herkansingspatroon in Microsoft Drivers voor PHP voor SQL Server.

Kies het herstelbeleid

Idle connection recovery behoudt een herstelbare sessie zodat de applicatie zijn PHP-verbindingshandle kan blijven gebruiken. Het is geen algemene instructie om een commando opnieuw uit te voeren.

Failure Kan idle-verbindingsveerkracht dit afhandelen? Applicatieactie
De eerste verbinding kan niet worden gelegd No. Er is geen bestaande sessie om te herstellen. Controleer de verbindingsfout. Probeer alleen opnieuw bij een tijdelijke storing met een begrensd beleid.
Een inactieve, herstelbare verbinding verbreekt ODBC kan proberen te herstellen wanneer de verbinding opnieuw wordt gebruikt. Stel een voldoende lange statement-time-out in voor de beoogde herstelpogingen; handel een uiteindelijke fout nog steeds af.
Een statement faalt tijdens de uitvoering Ga er niet van uit dat het herstel of opnieuw afspelen is. Inspecteer de fout en bepaal of de bewerking is voltooid. Herhaal een leesbewerking alleen als het herhalen ervan acceptabel is voor je applicatie.
Een verbinding wordt verbroken tijdens een transactie Ga er niet van uit dat de transactie kan doorgaan. Laat de gebroken sessie vallen en bepaal of de transactie is doorgevoerd voordat je een nieuwe poging doet.
De client sluit expliciet zijn PHP-verbinding No. Het sluiten van een ingang is opschonen door de toepassing, geen ontkoppeling wegens inactiviteit. Open een nieuwe verbinding als er meer taken nodig zijn.

Houd herstelintervallen en querydeadlines consistent. Zo gebruiken de programma's twee reconnectpogingen met een interval van 5 seconden en een statement-time-out van 30 seconden. Verbindingspogingen kosten ook tijd; het vermenigvuldigen van het aantal met het interval is geen gegarandeerde totale herstelduur. Een laatste timeout vereist nog steeds foutafhandeling.

Opmerkingen

Verbindingsveerkracht geldt alleen wanneer de verbinding inactief is. Een storing tijdens een transactie veroorzaakt geen herverbinding. De volgende sessietoestanden voorkomen ook herverbinding:

  • Tijdelijke tabellen.
  • Globale en lokale cursoren.
  • Transactiecontext en sessie-niveau transactievergrendelingen.
  • Appvergrendelingen.
  • EXECUTE AS / REVERT Beveiligingscontext.
  • Object Linking and Embedding (OLE)-automatiseringsgrepen.
  • Voorbereide Extensible Markup Language (XML)-grepen.
  • Traceringsvlaggen.

Example

Elk programma opent twee verbindingen naar een test-SQL Server-instantie. De tweede verbinding beëindigt de eerste sessie met KILL. Een daaropvolgende query op de eerste verbinding maakt gebruik van ODBC's idle-connection recovery. De query gebruikt inline rijen, dus er is geen tabelconfiguratie nodig. De programma's geven elke instructie vrij voordat ze de verbinding verbreken, en verifiëren zowel de waarden van de herstelde query als de gewijzigde waarde van de aanmeldtijd van de server. Een sessie-ID alleen is geen bewijs van herstel omdat SQL Server het kan hergebruiken.

Caution

Voer dit voorbeeld alleen uit op een testinstantie. De login heeft toestemming nodig om een sessie met KILL te beëindigen. De code beëindigt alleen de sessie die hij net heeft geopend. Gebruik niet de sessie-ID van een andere applicatie ter vervanging.

Stel SQL_SERVER, SQL_DATABASE, SQL_USER, en SQL_PASSWORD in de procesomgeving. Configureer een vertrouwd servercertificaat waarvan de naam overeenkomt met SQL_SERVER. Dit voorbeeld gebruikt SQL-authenticatie voor een testinstantie van een SQL Server. Het is geen voorbeeld van Fabric-authenticatie of authenticatie met beheerde identiteit.

Sla de daadwerkelijke databasenaam, gebruikersnaam en wachtwoord op in de omgevingsvariabelen. Elk programma escapet deze waarden één keer voor de ODBC-achtige verbindingsafhandeling van de PHP-driver door sluitende accolades te verdubbelen en elke waarde tussen accolades te plaatsen. Beide verbindingen hergebruiken de geëscapeerde waarden.

SQLSRV

Sla het programma op als recover-sqlsrv.php, en voer php recover-sqlsrv.php uit. De helper genereert een uitzondering bij een SQLSRV-fout; hij probeert geen applicatie-instructie opnieuw.

<?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

Sla dit aparte programma op als recover-pdo.php, en voer php recover-pdo.php uit met dezelfde omgeving en permissies. Het stelt numerieke fetching expliciet in zodat de integer-asserties overeenkomen met het SQLSRV-programma. PDO heeft geen sqlsrv_errors() aanroep; bewerkingsfouten werpen PDOException in uitzonderingsmodus.

<?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;
}

Verwachte output van een van beide programma's wanneer ODBC de verbinding herstelt:

Recovered connection: 3 matching rows; login time changed.

De test beëindigt een opzettelijk inactieve sessie. Het test geen netwerkpartitie, servicefailover, actief transactieherstel, schrijfreplay of Azure-throttling niet. Interpreteer een pass niet als bewijs dat elke onderbroken applicatieoperatie veilig is om te herhalen.

Diagnosticeer een mislukt herstel

Symptom Cheque
KILL is geweigerd Gebruik een toegestane testidentiteit of vraag de beheerder om de gecontroleerde test uit te voeren. Gebruik geen andere sessie-ID ter vervanging.
De eerste query faalt al Los de verbinding, het certificaat, de query en de rechten op voordat je het herstel test.
De query na KILL faalt Controleer ConnectRetryCount, de query-timeout, ODBC-diagnostiek en of de sessiestatus herstelbaar is. Verberg de fout niet door een onvoorwaardelijke nieuwe poging.
Een tijdelijke tabel verdwijnt na het openen van een nieuwe verbinding Een door de applicatie gemaakte vervangende verbinding is niet de oude sessie. Sessie-afhankelijk werk herontwerpen in plaats van automatisch statusherstel aan te nemen.
Een schrijfbewerking resulteert in een verbindingsfout Bepaal de uitkomst ervan voordat je het opnieuw probeert. De clientfout alleen bewijst niet dat de server het heeft teruggedraaid.

Bewaar diagnostische gegevens in beveiligde logs. SQLSRV stelt een foutverzameling bloot die meer dan één record kan bevatten; PDO levert SQLSTATE- en driverdetails via de uitzondering. Noteer de operatie en de time-outinstellingen zonder wachtwoorden of tokens op te nemen. Voor meer informatie, zie Logboekactiviteit.

Doeltoepasselijkheid

ODBC en het SQL-doel voeren de automatische herverbinding uit. PHP geeft instellingen door en rapporteert succes of falen. Het maakt geen tijdelijke objecten opnieuw aan, herstelt geen variabelen van je applicatie, en speelt geen transactie opnieuw af.

De gecontroleerde programma's gebruiken SQL Server en vereisen toestemming om hun eigen testsessie via een tweede verbinding te beëindigen. Azure SQL Database en SQL Database in Fabric hebben hun eigen servicerechten en authenticatievereisten. Behandel die programma's niet als drop-in tests voor Azure SQL Managed Instance, Azure Synapse Analytics of Warehouse in Fabric, of leid hun herstelondersteuning af uit het feit dat een gewone verbinding slaagt. Controleer de recovery-ondersteuning van de geselecteerde dienst voordat je erop vertrouwt. Warehouse en SQL-database in Fabric blijven aparte bestemmingen.