Strojenie wydajności dla sterowników Microsoft dla PHP dla SQL Server

Pobieranie sterownika PHP

Ten artykuł opisuje, jak pisać szybki kod PHP na SQL Server, Azure SQL Database, Azure SQL Managed Instance, Azure Synapse Analytics oraz bazie danych SQL w Microsoft Fabric. Wytyczne te dotyczą zarówno SQLSRV, jak i PDO_SQLSRV, które obejmują ten sam podstawowy sterownik Microsoft ODBC dla SQL Server.

Zacznij od zmian o największym wpływie

Jeśli możesz wprowadzić tylko trzy zmiany, wprowadź te poprawki:

  • Włącz pulowanie połączeń. Nawiązanie nowego połączenia TLS z SQL Server zajmuje dziesiątki do setek milisekund, w zależności od ścieżki sieciowej i negocjacji TLS. Ponowne wykorzystanie łączeń w grupie eliminuje ten koszt za każde żądanie. Zobacz Zarządzaj połączeniami efektywnie.
  • Pobieraj tylko te kolumny i wiersze, których potrzebujesz. SELECT * a zapytania nieograniczone są najczęstszymi przyczynami wolnych punktów końcowych. Zobacz Zapytaj tylko to, czego potrzebujesz.
  • Używaj parametrów tabelowych do wstawiań zbiorczych. Dla setek lub więcej wierszy parametry tabelowe (TVP) są zazwyczaj znacznie szybsze niż instrukcje wiersz po wierszu INSERT i skalują się liniowo wraz z liczbą wierszy. Zobacz Wstaw dane efektywnie.

Efektywnie zarządzaj połączeniami

Nawiązanie połączenia to najdroższa operacja wykonywana przez sterownik. Prawie każde badanie wydajności PHP kończy się poprawką zarządzania połączeniami.

Włącz buforowanie połączeń

Pooling ponownie wykorzystuje połączenia ODBC między żądaniami PHP zamiast ich usuwać na końcu żądania. Obiekt connection jest odrzucany, gdy skrypt się kończy, ale podstawowy uchwyt ODBC pozostaje aktywny w puli menedżera sterowników ODBC i jest ponownie używany przez kolejne żądanie, które prosi o ten sam parametry połączenia.

Windows: Pula połączeń jest domyślnie włączona. Aby potwierdzić, pomiń ConnectionPooling tę opcję w swoim DSN. Aby wyłączyć pulowanie do debugowania, ustaw ConnectionPooling=0.

Linux i macOS: Pulowanie połączeń nie jest opcją DSN na tych platformach. Włącz go w menedżerze sterowników, ustawiając Pooling=Yes w [ODBC] sekcji odbcinst.ini, i ustawiając dodatniego CPTimeout w sekcji sterownika. Przykład:

[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

Znajdź właściwą ścieżkę biblioteki z lub odbcinst -q -d -n "ODBC Driver 18 for SQL Server"ls /opt/microsoft/msodbcsql18/lib64/. Nazwa pliku osadza zainstalowaną wersję sterownika ODBC i zmienia się z każdą wersją.

CPTimeout (w sekundach) kontroluje, jak długo bezczynne połączenia pozostają w puli, zanim zostaną zamknięte. Ustaw go na tyle wysoko, by większość żądań znajdowała połączenie w puli, ale na tyle nisko, by uszkodzone połączenia z serwerem z awarią były szybko wycofane. 60 do 300 sekund sprawdza się dobrze dla większości obciążeń webowych.

Szczegółowe informacje znajdują się w artykule Pula połączeń.

Zrozum koszt pierwszego zapytania

Domyślnie MARS (wiele aktywnych zestawów wyników) jest włączone. Gdy MARS i pula połączeń są aktywne, sterownik resetuje połączenie w grupie przy pierwszym zapytaniu, a ten reset ignoruje cały czasowy limit zapytania ustawiony dla tego pierwszego zapytania. Późniejsze zapytania dotyczące tego samego połączenia zwykle uznają timeout. Jeśli ustawiasz agresywne limity pierwszego zapytania na połączonym obciążeniu, uwzględnij to zachowanie lub wyłącz MARS, MultipleActiveResultSets=false jeśli nie jest to potrzebne. Zobacz notatkę MARS i pooling w Connection pooling.

Trwałe połączenia PDO nie są obsługiwane

PDO_SQLSRV odrzuconych PDO::ATTR_PERSISTENT. Ustawienie go na konstruktorze rzuca:

SQLSTATE[IMSSP]: An unsupported attribute was designated on the PDO object.

Używaj puli połączeń ODBC do ponownego użycia między żądaniami. To natywny mechanizm sterownika, działa zarówno dla PDO_SQLSRV, jak i SQLSRV, i wyłącza połączenia CPTimeout bezczynnościowe (co również utrzymuje odświeżanie tokenów Microsoft Entra uczciwym).

Ponownie wykorzystaj połączenie w żądaniu

Nawet przy puli otwieranie nowego połączenia PDO lub SQLSRV powoduje połączenie ODBC w obie strony, aby pobrać i zweryfikować uchwyt w puli. Otwieraj połączenie raz na każde żądanie i przekazuj je każdej funkcji, która tego potrzebuje.

Tip

Wystarczy pojemnik wstrzykujący zależności lub leniwy accessor. Chodzi o to, by uniknąć new PDO(...) w środku obsługi żądań.

Pytaj tylko to, czego potrzebujesz

Sieciowe połączenia w obie strony i materializacja zestawu wyników dominują w opóźnieniach zapytań dla większości obciążeń PHP. Poprawki są takie same, jak na każdej warstwie dostępu do bazy danych.

Wybierz tylko kolumny, których używasz

SELECT * pobiera każdą kolumnę, w tym kolumny varchar(max) i varbinary(max), które przyćmiewają dane, które faktycznie konsumujesz. Wymień kolumny:

<?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");

Przynieś tylko te rzędy, których potrzebujesz

Filtrowanie push do SQL Server. Nigdy nie pobieraj całej tabeli do PHP tylko po to, żeby filtrować w pętli foreach .

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

Stronicowanie dużych zestawów wyników

W przypadku widoku listy, który pokazuje kilkaset wierszy z milionów, nie zwracaj wszystkich wierszy i pozwól klientowi sam to uporządkować. Użyj stronacji po stronie serwera z :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);
}

Wybierz odpowiednią metodę pobierania

  • Używaj fetch(PDO::FETCH_ASSOC) w pętli do streamingu iteracji, gdy nie potrzebujesz wszystkich wierszy w pamięci naraz.
  • Używaj, fetchAll(PDO::FETCH_ASSOC) gdy dzwoniący naprawdę potrzebuje całego zestawu (na przykład przy pełnej odpowiedzi JSON).
  • Używaj, fetchColumn() gdy zależy ci tylko na jednym skalarze (, , COUNTSUMlub MAX).
  • Używaj PDO::FETCH_KEY_PAIR lub PDO::FETCH_UNIQUE buduj słowniki wyszukiwania bez drugiego przejścia.

Tryby pobierania numerycznego (PDO::FETCH_NUM) są nieco szybsze niż tryby pobierania asocjacyjnego, ponieważ pomijają budowanie mapy nazw kolumn. Wolę klarowność; Przełączaj tylko wtedy, gdy profiler oznaczy narzut pobierania jako znaczący.

Preferuj SET NOCOUNT ON w procedurach i partiach przechowywanych

Każde INSERTpolecenie , UPDATE, and DELETE zwraca DONE_IN_PROC token o dotkniętym liczbie wierszy, który PHP zazwyczaj odrzuca. Token nie dodaje podróży w obie strony, ale każdy z nich i tak kosztuje bajty na przewodzie i trochę pracy sterownika. W procedurze wielozadaniowej lub partii, która wykonuje setki instrukcji na jedno połączenie, oszczędności się sumują. Wyłącz to:

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;

Efektywnie wprowadzaj dane

Wybierz odpowiednią metodę wstawiania w zależności od liczby wierszy, które przesuwasz. Zły wybór może być 100 razy wolniejszy.

Mniej niż około 100 wierszy: przygotowane zdanie w pętli

Dla małych partii wykonaj pojedyncze przygotowane zdanie w pętli:

<?php
$stmt = $conn->prepare("INSERT INTO dbo.Products (Name, Price) VALUES (?, ?)");
foreach ($products as $p) {
    $stmt->execute([$p["name"], $p["price"]]);
}

Opakuj pętlę w transakcję, tak aby wszystkie inserty zatwierdzały się jako jedna jednostka, a log nie musiał się wyrównywać po każdym wierszu:

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

Setki do milionów wierszy: parametry tabelowe

Parametry tabelowe (TVP) wysyłają całą partię do SQL Server w jednej obie i pozwalają SQL Server przetworzyć zestaw jako pojedyncze zdarzenie. Dla partii setek lub więcej wierszy TVP są zazwyczaj znacznie szybsze niż pętla przygotowanych oświadczeń i skalują się liniowo wraz z liczbą wierszy.

Najpierw stwórz typ tabeli na serwerze:

CREATE TYPE dbo.ProductTableType AS TABLE (
    Name  NVARCHAR(100),
    Price DECIMAL(10, 2)
);

PDO_SQLSRV przekazuje TVP jako tablicę asocjacyjną, której kluczem jest nazwa typu, a wartością jest zbiór wierszy. Zwiąż ją z :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();

Dla schematu niedomyślnego, przekażmy schemat jako następny element tablicy: ["ProductTableType" => $rows, "Sales"]. Przykłady składni proceduralnej SQLSRV i procedur przechowywanych można znaleźć w artykule Używaj parametrów tabelowych.

Miliony wierszy: bcp lub BULK INSERT

Do prawdziwych operacji masowych (obciążenia hurtowni, początkowe migracje) używaj bcp lub BULK INSERT zamiast PHP. Zapisz swoje dane do pliku w formacie ograniczonym lub natywnym, a następnie uruchom bcp lub BULK INSERT z zaplanowanego zadania, kroku ETL lub skryptu administratora.

Caution

Jeśli inwestujesz w bcp z PHP lub shell_exec()proc_open(), nigdy nie interpoluj nieufnych danych wejściowych do linii poleceń. Używam escapeshellarg() na każdym arteście i wolę uruchamiać ładowanie poza pasmem zamiast w ścieżce żądań webowych.

Zmniejsz liczbę podróży w obie strony

Każda sieć w obie strony między PHP a SQL Server ma stały koszt. Gdy wysyłasz pięć wyciągów w jednej partii, płacisz ten koszt raz zamiast pięciokrotnie.

Dla powiązanych prac, które wykonują się razem, umieść instrukcje w jednej partii i zużywaj każdy zestaw wyników:

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

W SQLSRV używaj sqlsrv_next_result przechodzenia między zestawami wyników.

Włącz wiele aktywnych zestawów wyników, gdy potrzebujesz

Multiple Active Result Sets (MARS) pozwala pojedynczemu połączeniu mieć wiele aktywnych instrukcji. Bez MARS nie możesz wydać nowego zapytania na połączenie, które nadal ma otwarty zestaw wyników. Oba sterowniki domyślnie włączają MARS. Aby ją wyłączyć, wstaw MultipleActiveResultSets=false swój parametry połączenia. Zobacz Wyłącz wiele aktywnych zestawów wyników (MARS).

MARS jest wygodny, ale nie darmowy. Każdy aktywny zestaw wyników zużywa zasoby po stronie serwera. Wolej najpierw zjeść cały zestaw wyników przed rozpoczęciem kolejnego. Użyj MARS, aby odblokować prawdziwie zagnieżdżone wzory kursora.

Tune przygotowane oświadczenia

Przygotowane instrukcje oszczędzają sterownikowi ponownego parsowania SQL na serwerze i pozwalają bezpiecznie przypisać nieufne dane wejściowe jako parametry.

Preferuj lokalne produkty

PDO_SQLSRV może przygotowywać oświadczenia w dwóch trybach. Natywne przygotowania wysyłają tekst SQL na serwer raz i ponownie używają parsowanego polecenia dla każdego wykonywania, wysyłając tylko wartości parametrów dla każdego execute(). Emulowane przygotowania zachowują tekst SQL w kliencie i odbudowują pełny ciąg SQL z parametrami interpolowanymi przy każdym wykonaniu.

Ustaw PDO::ATTR_EMULATE_PREPARES => false tak, aby sterownik korzystał z natywnych preparatów. Natywne przygotowania pozwalają SQL Server buforować i ponownie używać planu zapytań, a także unikają ponownego analizowania tekstu SQL przy każdym wykonaniu.

<?php
$conn = new PDO($dsn, null, null, [
    PDO::ATTR_ERRMODE          => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

Ponowne wykorzystanie przygotowanych instrukcji

Przygotuj się raz, wykonaj wiele. Każde prepare() wywołanie kosztuje alokację uchwytu ODBC oraz parsowanie po stronie serwera. W gorącej pętli utrzymuj $stmt obiekt przy życiu i wywołuj execute() wewnątrz pętli:

<?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"]]);
}

Uważaj na TOP (?) i IN (?, ?, ...)

TOPwymaga nawiasów wokół markera SELECT TOP (?) ...parametru , aby SQL Server mógł parsować liczbę wierszy jako parametr. IN (?, ?, ?, ?) wymaga stałej liczby zastępczych w czasie przygotowania. Dla IN dynamicznych rozmiarów list albo zbuduj łańcuch zastępczy z zweryfikowanej liczby liczb całkowitych, albo przekazuj listę jako parametr tabelowy.

Caution

Nigdy nie interpoluj surowych danych wejściowych użytkownika do tekstu SQL (w tym liczby zastępczych). Rzuć liczbę z przed (int) zbudowaniem stringu placeholder i zawsze przepuszczaj rzeczywiste wartości execute() jako parametry.

Zarządzanie kursorami i pamięcią

Domyślny typ kursora to PDO::CURSOR_FWDONLY, węż strażacki działający tylko do przodu. Streamuje wiersze do PHP pojedynczo i nie buforuje, więc duży zbiór wyników jest ograniczony pamięcią wierszową, a nie całkowitą liczbą wierszy. Zazwyczaj tego właśnie chcesz.

Używaj kursorów buforowanych tylko wtedy, gdy musisz cofnąć się lub liczyć wiersze

PDO::SQLSRV_CURSOR_BUFFERED (statyczny kursor buforowany po stronie klienta) pobiera cały zestaw wyników do pamięci PHP od razu. To podejście pozwala wywołać rowCount(), szukać wstecz, i ponownie użyć zdania. Domyślnie bufor jest ograniczony do 10 240 KB (10 MB) za pomocą PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, a zapytanie, którego zestaw wyników przekracza limit, zwraca false zamiast nadmiaru pamięci PHP. Możesz podnieść limit w kierunku limitu pamięci PHP, ale robiąc to, wymieniasz false zwrot na Allowed memory size exhausted prawdziwy błąd śmiertelny, gdy zapytanie przerasta nowy limit. Strojcie celowo. Zobacz Typy kursorów (PDO_SQLSRV).

Przewijane kursory po stronie serwera (PDO::SQLSRV_CURSOR_STATIC, PDO::SQLSRV_CURSOR_DYNAMIC, ) PDO::SQLSRV_CURSOR_KEYSETbuforują na serwerze zamiast na kliencie, więc nie zużywają pamięci PHP. Jednak przechowują zasoby po stronie serwera przez cały czas trwania kursora i są wolniejsze w każdym wierszu niż tylko przekazywanie kursora.

Używaj domyślnego "tylko do przodu" do czytania strumieniowego. Używaj buforowanego klienta dla małych zestawów wyników, gdy potrzebujesz rowCount() , lub do przewijania wstecz. Unikaj przewijanych kursorów po stronie serwera, chyba że robisz coś konkretnego.

<?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();

Pełne podziały można znaleźć w artykule Typy kursorów (PDO_SQLSRV) i Typy kursorów (SQLSRV).

Strumienie dużych wartości binarnych i znakowych

Dla varbinary(max), varchar(max), nvarchar(max), xml i innych dużych typów używaj strumieni PHP zamiast materializowania całej wartości w pamięci:

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

Do wstawiania lub aktualizacji z dużymi wartościami, używaj SendStreamParamsAtExec=false w SQLSRV do przesyłania danych strumieniowych w fragmentach po .sqlsrv_execute() Szczegóły można znaleźć w artykule Wyślij dane jako strumień.

Ustaw odpowiednie limity czasu

Timeouty to ustawienia wydajności równie mocno, co ustawienia niezawodności. Długo wiszące zapytania zatrzymują połączenia z basenem i odbierają inne prośby.

Limit czasu instrukcji

Ustaw limit czasu na każde polecenie, aby zapytanie bez kontroli nie utrzymwało połączenia puli na czas nieskończony. Dla PDO_SQLSRV:

<?php
$stmt = $conn->prepare("SELECT ... FROM dbo.HugeTable ...");
$stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, 30); // seconds
$stmt->execute();

Dla SQLSRV przekaż "QueryTimeout" => 30 tablicę opcji do sqlsrv_query lub sqlsrv_prepare.

Ustaw wartość odpowiadającą twojemu obciążeniu. Dla synchronicznego żądania webowego zwykle trwa 15 do 30 sekund. Przy zadaniu wsparcia w tle, kilka minut może być rozsądne. Nigdy nie ustawiaj limitu czasu na zero (nieograniczony) w żądaniu internetowym.

Limit czasu logowania

LoginTimeoutw parametry połączenia kontroluje, jak długo sterownik czeka na nawiązanie połączenia. Ustaw wartość jawną podczas łączenia z Azure SQL Database lub Azure SQL Managed Instance, aby zimne starty i failover-group nie zawieszały klienta na czas nieokreślony. Wartości od 30 do 90 sekund dobrze sprawdzają się w większości obciążeń chmurowych. Szczegóły dotyczące rozmiarowania LoginTimeout względem i ConnectRetryCount * ConnectRetryInterval wynikających z nich trybów awarii można znaleźć w artykule Limit połączenia. Odniesienie do opcji można znaleźć w Opcjach połączenia.

Kieruj obciążenia tylko do odczytu do repliki

W przypadku zapytań tylko do odczytu do bazy danych w grupie dostępności Always On, Azure SQL Managed Instance lub Azure SQL Database z skalowaniem odczytu lub geo-repliką, dodaj ApplicationIntent=ReadOnly do swojego parametry połączenia:

<?php
$dsn = "sqlsrv:Server=<listener>;Database=<database>;" .
       "Encrypt=true;ApplicationIntent=ReadOnly";

Routing tylko do odczytu wysyła połączenie do zsynchronizowanej repliki wtórnej, odciążając pracę od podstawowego. Łącz z , MultiSubnetFailover=true aby uzyskać najszybsze połączenie z grupowymi słuchaczami dostępności wielu podsieci.

Obserwuj wydajność serwera

Timing po stronie klienta pokazuje tylko, ile czasu zajęło zapytanie od początku do końca. Aby dowiedzieć się, dlaczego było wolniej, skorzystaj z wbudowanej diagnostyki SQL Server.

Magazyn zapytań

Query Store rejestruje plany wykonania, statystyki w czasie działania oraz statystyki oczekiwania dla każdego zapytania w bazie danych. Domyślnie jest włączony w Azure SQL Database, Azure SQL Managed Instance oraz bazie danych SQL w Fabric. Na SQL Server włącz to dla każdej bazy danych:

ALTER DATABASE <database_name> SET QUERY_STORE = ON;

Następnie użyj raportów Query Store SQL Server Management Studio, aby znaleźć najwolniejsze i najczęściej wykonywane zapytania. Zobacz Monitorowanie wydajności za pomocą Query Store.

Azure SQL Query Performance Insight

Dla Azure SQL Database, Query Performance Insight portalu Azure automatycznie pokazuje największe zapytania zużywające zasoby bez żadnej konfiguracji. Aby uzyskać więcej informacji, zobacz Szczegółowe informacje o wydajności zapytań dla usługi Azure SQL Database.

SET STATISTICS za jednorazowe śledztwo

Dla pojedynczego zapytania, które chcesz profilować, uruchom je w SQL Server Management Studio z włączonymi statystykami:

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

-- your query here

Wysokie odczyty logiczne prawie zawsze oznaczają brak lub nieużyteczny indeks. Wysoki czas CPU przy niskiej liczbie odczytów logicznych zwykle oznacza zły plan (sniffing parametrów, ukryta konwersja uniemożliwiająca użycie indeksu lub funkcja skalarna zapobiegająca równoległości).

Rozszerzone zdarzenia dla śledzenia na poziomie kierowcy

Aby dokładnie zobaczyć, co sterownik wysyła do SQL Server (w tym rzeczywiste wartości parametrów, które interpoluje), przechwyć sesję Extended Events, używając zdarzeń rpc_completed i sql_batch_completed

Lista kontrolna wydajności

Użyj tej listy kontrolnej jako przeglądu przed wdrożeniem dowolnej aplikacji PHP łączącej się z SQL Server:

Area Sprawdź Reference
Connection Pula połączeń jest włączona i skonfigurowana dla platformy Efektywnie zarządzaj połączeniami
Connection Aplikacja ponownie wykorzystuje połączenia w ramach żądania i nie otwiera połączeń na każde zapytanie Ponownie wykorzystaj połączenie w żądaniu
Connection LoginTimeoutcovers cold starts and failover for Azure SQL Czas logowania
Query Zapytania wybierają tylko potrzebne kolumny, nie SELECT * Wybierz tylko kolumny, których używasz
Query Filtrowanie odbywa się w SQL, a nie w PHP z array_filter Przynieś tylko te rzędy, których potrzebujesz
Query Duże zbiory wyników są paginowane z OFFSET ... FETCH Paginat duże zbiory wyników
Query Zestaw procedur przechowywanych SET NOCOUNT ON Preferuj SET NOCOUNT ON
Wstawki Wkłady masowe wykorzystują parametry tabelowe, a nie pętle na wiersze Efektywnie wprowadzaj dane
Statements PDO::ATTR_EMULATE_PREPARES jest ustawiona na wartość false Preferuj lokalne produkty
Statements Aplikacja wykorzystuje przygotowane instrukcje wielokrotnie podczas wykonywania Ponowne wykorzystanie przygotowanych instrukcji
Cursors Aplikacja używa domyślnego kursora tylko do przodu, chyba że jest potrzebne buforowanie Zarządzanie kursorami i pamięcią
Memory Duże wartości binarne i znakowe są przesyłane strumieniowo, a nie materializowane Strumienie dużych wartości binarnych i znakowych
Timeouts Limit żądania jest ustawiany dla wszystkich zapytań skierowanych do użytkownika Czas na oświadczenie
Trasowanie Obciążenia tylko do odczytu ustawione ApplicationIntent=ReadOnly tam, gdzie istnieje replika Obciążenia tylko do odczytu trasy
Observability Query Store jest włączany i regularnie przeglądany Magazyn zapytań