Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Odporność połączeń pozwala sterownikowi Microsoft ODBC dla SQL Server przywrócić uszkodzone nieaktywne połączenie, gdy serwer i stan sesji wspierają odzyskiwanie. Rozszerzenia PHP przekazują ConnectRetryCount i ConnectRetryInterval do ODBC; nie implementują protokołu rekonekcji. Dokumentacja ODBC zawiera wsparcie dla SQL Server 2014 i nowszych, Azure SQL Database oraz bazy danych SQL w Microsoft Fabric. Nie wnioskuj o obsłudze Warehouse na podstawie obsługi bazy danych SQL w Fabric.
| Keyword | Wartości | Default | Description |
|---|---|---|---|
ConnectRetryCount |
Liczba całkowita z zakresu od 0 do 255 (włącznie) | 1 | Maksymalna liczba prób ponownego nawiązania przerwanego połączenia przed rezygnacją. Wartość 0 wyłącza ponowne łączenie. |
ConnectRetryInterval |
Liczba całkowita z zakresu od 1 do 60 (włącznie) | 10 | Liczba sekund oczekiwania między próbami. Ignorowane, jeśli ConnectRetryCount ma wartość 0. |
Limit czasu zapytania w instrukcji ogranicza, ile czasu ODBC może poświęcić na odzyskiwanie bezczynnego połączenia podczas wykonywania. Ustaw QueryTimeout w opcjach instrukcji SQLSRV lub PDO::SQLSRV_ATTR_QUERY_TIMEOUT dla PDO_SQLSRV.
LoginTimeout kontroluje nawiązywanie połączenia; jego zwiększenie nie zwiększa budżetu odzyskiwania instrukcji.
Ustaw te same słowa kluczowe ponawiania prób w tablicy opcji połączenia SQLSRV, takie jak ["ConnectRetryCount" => 2, "ConnectRetryInterval" => 5], lub w nazwie źródła danych PDO_SQLSRV (DSN), na przykład ConnectRetryCount=2;ConnectRetryInterval=5. Te ustawienia nie ponawiają nieudanej transakcji ani nie próbują ponownie wywołać początkowego wywołania połączenia aplikacji.
Informacje o ponawianiu na poziomie aplikacji w przypadku błędów przejściowych, które pojawiają się podczas wykonywania zapytania (czego nie obejmuje odporność bezczynnego połączenia), można znaleźć we wzorcu ponawiania w Microsoft Drivers for PHP for SQL Server.
Wybierz politykę odzyskiwania
Odzyskiwanie bezczynnego połączenia zachowuje sesję, którą można odzyskać, dzięki czemu aplikacja może kontynuować korzystanie z uchwytu połączenia PHP. To nie jest ogólna instrukcja, by wykonać polecenie ponownie.
| Failure | Czy funkcja odporności połączeń bezczynnych to obsługuje? | Akcja aplikacji |
|---|---|---|
| Początkowe połączenie nie może zostać ustanowione | No. Nie ma aktywnej sesji do przywrócenia. | Sprawdź błąd połączenia. Spróbuj ponownie tylko w przypadku przejściowej awarii zgodnie z ograniczoną polityką ponawiania. |
| Przerwanie bezczynnego połączenia możliwego do odzyskania | ODBC może podjąć próbę odzyskania połączenia, gdy połączenie zostanie ponownie użyte. | Ustaw wystarczająco długi limit czasu instrukcji dla planowanych prób odzyskiwania; nadal obsługuj końcowe niepowodzenie. |
| Instrukcja kończy się niepowodzeniem podczas wykonywania | Nie zakładaj odzyskiwania ani ponownego wykonania. | Sprawdź błąd i sprawdź, czy operacja została zakończona. Ponów próbę odczytu tylko wtedy, jeśli jego powtórzenie jest akceptowalne dla twojej aplikacji. |
| Połączenie zostaje przerwane podczas transakcji | Nie zakładaj, że transakcja nie może być kontynuowana. | Porzuć zerwaną sesję i sprawdź, czy transakcja została dokonana, zanim zdecydujesz się na kolejną próbę. |
| Klient jawnie zamyka połączenie w PHP | No. Zamknięcie uchwytu to sprzątanie aplikacji, a nie bezczynne rozłączenie. | Otwórz nowe połączenie, jeśli będzie potrzebna więcej pracy. |
Utrzymuj regularne okresy regeneracji i terminy zapytań. Na przykład programy używają dwóch prób ponownego połączenia z 5-sekundowym odstępem czasu oraz 30-sekundowego limitu czasu wykonania instrukcji. Próby połączenia również zajmują czas; mnożenie liczby przez interwały nie gwarantuje całkowitego czasu odzyskania. Ostateczne przekroczenie limitu czasu nadal wymaga obsługi błędów.
Uwagi
Odporność połączenia dotyczy tylko wtedy, gdy połączenie jest bezczynne. Awaria podczas transakcji nie powoduje ponownego łączenia. Następujące stany sesji również zapobiegają ponownemu połączeniu:
- Tymczasowe stoły.
- Kursory globalne i lokalne.
- Kontekst transakcji i blokady na poziomie sesji.
- Blokady aplikacji.
-
EXECUTE AS/REVERTkontekst bezpieczeństwa. - uchwyty automatyzacji linkowania i osadzania obiektów (OLE)
- Przygotowane uchwyty rozszerzalnego języka znaczników (XML).
- Flagi śledzenia.
Example
Każdy program otwiera dwa połączenia z testową instancją SQL Server. Drugie połączenie kończy pierwszą sesję z KILL. Kolejne zapytanie na pierwszym połączeniu sprawdza odzyskiwanie nieaktywnego połączenia przez ODBC. Zapytanie korzysta z wierszy wbudowanych, więc nie jest wymagane ustawianie tabeli. Programy zwalniają każdą instrukcję przed przerwaniem połączenia i weryfikują zarówno wartości odzyskanego zapytania, jak i zmieniony czas logowania na serwer. Sam identyfikator sesji nie jest dowodem odzyskania, ponieważ SQL Server może go ponownie wykorzystać.
Caution
Uruchom ten przykład tylko na instancji testowej. To konto wymaga uprawnienia do zakończenia sesji za pomocą KILL. Kod kończy tylko sesję, którą właśnie otworzył. Nie zastępuj ID sesji innej aplikacji.
Ustaw SQL_SERVER, SQL_DATABASE, SQL_USER oraz SQL_PASSWORD w środowisku procesu. Konfiguruj zaufany certyfikat serwera, którego nazwa odpowiada SQL_SERVER. Ten przykład wykorzystuje uwierzytelnianie SQL względem testowej instancji SQL Server. To nie jest przykład uwierzytelniania Fabric ani uwierzytelniania tożsamością zarządzaną.
Przechowuj rzeczywistą nazwę bazy danych, nazwę użytkownika i hasło w zmiennych środowiskowych. Każdy program stosuje znaki ucieczki dla tych wartości raz dla obsługi połączeń w stylu ODBC sterownika PHP, podwajając zamykające klamry i ujmując każdą wartość w nawiasy klamrowe. Oba połączenia ponownie wykorzystują wartości eskapowane.
SQLSRV
Zapisz program jako recover-sqlsrv.php, i uruchom php recover-sqlsrv.php. Helper zgłasza wyjątek w przypadku błędu SQLSRV; nie próbuje ponownie żadnego polecenia aplikacji.
<?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
Zapisz ten osobny program jako recover-pdo.php i uruchom php recover-pdo.php z tymi samymi ustawieniami środowiska i uprawnieniami. Ustawia on pobieranie liczb w sposób jawny, aby asercje całkowite odpowiadały programowi SQLSRV. PDO nie ma wywołania sqlsrv_errors(); niepowodzenia operacji wyrzucają PDOException w trybie wyjątku.
<?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;
}
Oczekiwany wynik z jednego z programów po przywróceniu połączenia przez ODBC:
Recovered connection: 3 matching rows; login time changed.
Test kończy celowo nieaktywną sesję. Nie testuje partycji sieciowej, przełączania awaryjnego serwisu, aktywnego odzyskiwania transakcji, powtórki zapisu ani dławienia w Azure. Nie interpretuj zaliczenia jako dowodu, że każda przerwana operacja aplikacji jest bezpieczna do powtórzenia.
Zdiagnozuj nieudane odzyskiwanie
| Objaw | Sprawdzanie |
|---|---|
KILL odmowa dostępu |
Użyj dozwolonej tożsamości testowej lub poproś administratora o przeprowadzenie kontrolowanego testu. Nie podmieniaj go innym identyfikatorem sesji. |
| Pierwsze zapytanie już zawodzi | Napraw połączenie, certyfikat, zapytanie i uprawnienia przed testowaniem odzyskiwania. |
Zapytanie po KILL kończy się niepowodzeniem |
Sprawdź ConnectRetryCount, limit czasu zapytania, diagnostykę ODBC oraz czy stan sesji jest możliwy do odzyskania. Nie ukrywaj błędu bezwarunkową próbą. |
| Tymczasowa tabela znika po otwarciu nowego połączenia | Połączenie zastępcze utworzone przez aplikację to nie stara sesja. Przeprojektowanie pracy zależnej od sesji, zamiast zakładać automatyczne odtwarzanie stanu. |
| Zapis zwraca błąd połączenia | Ustal, jaki jest wynik przed ponowną próbą. Sam błąd klienta nie dowodzi, że serwer wycofał tę operację. |
Przechowuj diagnostykę w chronionych dziennikach. SQLSRV udostępnia kolekcję błędów, która może zawierać więcej niż jeden rekord; PDO dostarcza dane o SQLSTATE i sterownikach poprzez wyjątek. Nagrywaj operację i jej ustawienia limitu bez zapisywania haseł czy tokenów. Więcej informacji można znaleźć w sekcji Aktywność rejestrowa.
Docelowe zastosowanie
ODBC i cel SQL wykonują automatyczne ponowne połączenie. PHP przekazuje ustawienia i raportuje sukces lub porażkę. Nie odtwarza obiektów tymczasowych, nie przywraca zmiennych aplikacji ani nie ponawia transakcji.
Kontrolowane programy korzystają z SQL Server i wymagają zgody na zakończenie własnej sesji testowej przez drugie połączenie. Azure SQL Database i SQL w Fabric mają własne uprawnienia do usług i wymagania uwierzytelniania. Nie traktuj tych programów jako testów typu drop-in dla Azure SQL Managed Instance, Azure Synapse Analytics czy Warehouse in Fabric, ani nie wyciągaj wniosków o ich obsłudze odzyskiwania na podstawie faktu, że zwykłe połączenie się udaje. Sprawdź wsparcie odzyskiwania wybranej usługi, zanim na nim polegasz. Magazyn i baza danych SQL w Fabric pozostają odrębnymi celami.