升級 SQL Server 的 PHP 驅動程式

將 SQLSRV 和 PDO_SQLSRV 一起升級,讓兩個擴充套件使用相同的版本系列。 PHP 驅動程式使用 Microsoft ODBC 的 SQL Server 驅動程式,因此升級前請先確認系統需求。

升級會改變載入 PHP 行程中的原生程式碼。 混合部署可能繼續執行前一個版本、在 PHP 啟動時失敗,或將不同驅動版本暴露給 CLI 與網頁應用程式。 盤點每個 PHP 執行環境,並保留先前的檔案和設定,直到驗證完成。

選擇升級路徑

目前安裝 目標安裝位置 Procedure
PIE PIE 使用相同的 PHP 執行檔或明確指定 php-config 執行 PIE,然後驗證每個 SAPI
Windows 已解壓縮的 ZIP Windows 已解壓縮 ZIP 檔案 替換兩個版本化的 DLL,並同時更新兩個設定項目
Windows 已解壓縮的 ZIP PIE 安裝兩個 PIE 套件,將版本化的 DLL 條目替換為 PIE 條目,並移除重複項目
Linux 或 macOS 發行封存檔 PIE 安裝兩個 PIE 套件,將 archive-filename 條目替換為 sqlsrv.so 和 pdo_sqlsrv.so,並保留 PDO 載入順序
Linux 或 macOS PIE 新 PHP 小版本 安裝新的 PHP 開發工具,為該 PHP 安裝建置這兩個擴充功能,並分別設定每個 SAPI

在你有回滾副本之前,不要覆寫之前的二進位檔。 當應用程式同時使用兩個 API 時,不要只升級一個擴充功能。

升級之前

  1. 記錄 PHP 可執行檔、設定檔、架構、執行緒安全模式及目前驅動程式版本。

    php --version
    php --ini
    php --ri sqlsrv
    php --ri pdo_sqlsrv
    
  2. 備份php.ini檔案,以及任何會載入sqlsrv或pdo_sqlsrv的特定副檔名.ini檔案。

  3. 請檢查目標 PHP 版本與作業系統的 支援矩陣 。

  4. 請參考 系統需求中的 ODBC 相容性表。 當目標驅動程式版本有此要求時,請在 PHP 驅動升級前或期間安裝支援的 ODBC 版本。

  5. 安排重新啟動所有載入 PHP 的程序,包括網頁伺服器、PHP FastCGI 程序管理器(PHP-FPM)、佇列工作者,以及長期執行的命令列工作者。

  6. 記錄每個執行階段的基準:

    • ExtensionVer 由 php --ri sqlsrv 和 php --ri pdo_sqlsrv 顯示。
    • 已載入設定檔並掃描 .ini 目錄。
    • 擴充功能目錄和作用中的驅動程式檔名。
    • 一個通過的應用程式煙霧測試,使用您的應用程式所依賴的每個 API。
  7. 準備復原:

    • 如果你更改了 PHP 的次要版本,請保留之前的 PHP 執行環境。 記錄為每個網頁伺服器和背景工作程序所設定的執行檔。
    • 保留之前的 SQLSRV 和 PDO_SQLSRV 檔案的副本。
    • 保留所有已變更的 php.ini 或特定擴充功能的 .ini 檔案副本。
    • 如果升級變更了 ODBC 套件版本,請記錄先前的版本。
    • 定義如何在替換原生擴充功能檔案時停止流量或工作程序。

在 Windows 上使用 PIE 升級

使用PHP 擴充套件安裝程式(PIE)搭配服務該應用程式的 PHP 執行檔。 首先, 下載並驗證 PIE。 在執行任一安裝指令前,先先排空應用程式流量,並停止網頁伺服器、FastCGI 程序及使用此 PHP 安裝的工作程序。 在擴充功能及其設定都準備好之前,阻止它們自動重啟。 為 PIE 保留一個獨立的命令列工作階段。

暫時將 PIE 使用的 CLI 設定中現有的 SQLSRV 和 PDO_SQLSRV 擴充功能項目註解掉。 否則,執行 PIE 的 PHP 程序可能會讓舊的 DLL 持續載入,阻止替換。 請保持啟用 PIE 的必要擴充功能。

從包含 pie.phar的目錄中分別安裝兩個套件:

php .\pie.phar install microsoft/sqlsrv
php .\pie.phar install microsoft/pdo_sqlsrv

PIE 安裝 php_sqlsrv.dll 和 php_pdo_sqlsrv.dll。 在每個啟用配置中,將版本化的 ZIP 條目(例如 extension=php_sqlsrv_85_ts_x64.dll 和 extension=php_pdo_sqlsrv_85_ts_x64.dll)替換為:

extension=php_sqlsrv.dll
extension=php_pdo_sqlsrv.dll

啟用兩個項目,並移除重複的 SQLSRV 和 PDO_SQLSRV 條目,即使 PIE 報告自動啟用成功。 如果網頁伺服器使用不同的 PHP 安裝,請重複該安裝的升級與設定變更。

重新啟動每個 PHP 程序,然後使用對應的可執行檔執行 php --ri sqlsrv 和 php --ri pdo_sqlsrv。 確認 ExtensionVer 會針對兩個驅動程式回報目標版本。 對於網頁應用程式,在建立臨時頁面前,先phpinfo()權限。 驗證兩個數值後,再移除該頁面。

升級 Windows ZIP 安裝版本

  1. 請從 Microsoft Drivers for PHP for SQL Server 發行版本頁面下載目標版本的 Windows ZIP。

  2. 請確認新的動態連結函式庫(DLL)名稱是否符合 PHP 的次要版本、架構及執行緒安全模式。 在替換檔案前,先停止 PHP 網頁伺服器或工作程序。

  3. 把 PHP 擴充套件目錄裡的 php_pdo_sqlsrv_*.dll 和 php_sqlsrv_*.dll 檔案都替換掉。 如果檔名仍然一致,就保留現有 extension= 的條目。 否則,請一起更新。

  4. 如果目標 PHP 驅動程式需要較新的支援版本,請安裝或升級 Microsoft ODBC for SQL Server 驅動程式。

  5. 重新啟動所有使用被替換 DLL 的 PHP 程序。 執行中的程序會讓舊的原生擴充功能一直載入,直到它退出。

使用 PIE 在 Linux 或 macOS 上升級

  1. 如果尚未安裝 PIE,請遵循 PIE 安裝和平台先決條件。 確認 PHP 開發工具和 ODBC 開發套件已安裝。 在互動式會話中,PIE 可透過平台套件管理器提示安裝缺少的建置工具。 在非互動式會話中,先安裝它們再執行 PIE。

  2. 升級前先選擇 PHP 安裝項目。 對於版本化安裝,可以在兩個安裝指令中加入 PIE 的 --with-php-config 選項。 例如,當預期的設定工具為 pie install --with-php-config=/usr/bin/php-config8.4 microsoft/sqlsrv 時,使用 /usr/bin/php-config8.4。 將同樣的選項套用到 microsoft/pdo_sqlsrv。 對於 Alpine 帶版本號的可執行檔,請使用 Alpine 安裝指令。

  3. 排空應用程式流量,停止使用此 PHP 安裝的網頁伺服器、PHP-FPM 程序與工作者。 在擴充功能及其設定都準備好之前,阻止它們自動重新啟動。 為 PIE 保留一個獨立的命令列工作階段。

    暫時註解 PIE 使用的 CLI 設定中現有的 SQLSRV 和 PDO_SQLSRV 擴充功能項目。 請保持 PIE 的必要擴充功能為啟用狀態。

  4. 分別升級兩個套件。 這些指令使用 PIE 預設的 PHP 安裝:

    pie install microsoft/sqlsrv
    pie install microsoft/pdo_sqlsrv
    
  5. 檢查你的應用程式所使用的 PHP 安裝中的每個啟用中設定。 如果先前的手動安裝使用了像 extension=SQLSRV_8.4_NTS.so 和 extension=PDO_SQLSRV_8.4_NTS.so這樣的壓縮檔檔名,請將這些項目替換為 PIE 安裝的檔名:

    extension=sqlsrv.so
    extension=pdo_sqlsrv.so
    

    重新啟用兩個驅動程式條目,並移除重複的。 請將 PDO_SQLSRV 保存在會被掃描的設定檔中,該設定檔會在 PDO 之後載入,詳見 在 Linux 和 macOS 上載入驅動程式。 請對你的應用程式所使用的命令列、PHP-FPM、網頁伺服器和工作者配置重複此設定變更。

  6. 在目標版本要求時,升級 Microsoft ODBC Driver for SQL Server。

  7. 重新啟動每個使用升級後 PHP 安裝的 CLI 工作者、佇列工作者、網頁伺服器和 PHP-FPM 服務。

重新啟動並驗證

替換擴充功能檔案並不會更新已經載入舊二進位檔的 PHP 程序。 在驗證升級前,先重啟網頁伺服器、PHP-FPM 以及所有長期運行的 PHP 工作者。

  1. 驗證命令列執行環境:

    php --ri sqlsrv
    php --ri pdo_sqlsrv
    php -m | findstr /I "sqlsrv pdo_sqlsrv"
    

    在 Linux 或 macOS 上,將最後一個指令替換成 php -m | grep -E 'sqlsrv|pdo_sqlsrv'。

  2. 對於網頁應用程式,在建立臨時頁面前,先phpinfo()權限。 檢查這兩個驅動程式載入的設定檔、擴充功能目錄,以及 ExtensionVer。 確認該 ExtensionVer 顯示目標版本,然後移除該頁面。

  3. 如果你的應用程式同時使用這兩者,執行一個既有的應用程式煙霧測試,該測試會同時涵蓋 SQLSRV 和 PDO_SQLSRV。 在檢查過程中保持連線設定不變,這樣升級測試時就不會同時測試無關的連線變更。

若驅動程式載入失敗,請將 PHP 的次要版本、架構、執行緒安全模式、擴充目錄及 ODBC 安裝與升級前的數值進行比較。

驗證兩個 API 和兩個版本

將此程式儲存為 verify-driver-upgrade.php:

<?php
$results = [
    'SQLSRV' => [
        'loaded' => extension_loaded('sqlsrv') && function_exists('sqlsrv_connect'),
        'version' => phpversion('sqlsrv'),
    ],
    'PDO_SQLSRV' => [
        'loaded' => extension_loaded('pdo_sqlsrv')
            && in_array('sqlsrv', PDO::getAvailableDrivers(), true),
        'version' => phpversion('pdo_sqlsrv'),
    ],
];

$failed = false;
foreach ($results as $name => $result) {
    $version = $result['version'] === false ? 'unknown' : $result['version'];
    echo "$name: " . ($result['loaded'] ? 'loaded' : 'missing') . ", version $version" . PHP_EOL;
    $failed = $failed || !$result['loaded'] || $result['version'] === false;
}

exit($failed ? 1 : 0);

請以 CLI 執行檔執行程式,並暫時透過每個網頁執行階段執行。 確認回報的兩個版本是否與你安裝的版本相符。 驗證後移除網頁複本。

此程式驗證 PHP 擴充功能層。 之後執行未更改的應用程式煙霧測試,以驗證 ODBC 載入、連線選項、認證、資料庫存取、回傳的 PHP 類型及清理行為。

在升級前後驗證資料庫行為

升級前後使用相同的程式和連線設定。 保持測試不變可將驅動程式變更與應用程式或連線設定變更區分開來。

透過回答提示,為測試帳號設定 SQL_SERVER_NAME、SQL_DATABASE、SQL_UID 和 SQL_PWD 環境變數。 輸入原始值,不包含 shell 引號或驅動程式編碼。 不要在命令列或錄製的終端機會話中加入憑證。

在 Windows PowerShell 上,輸入伺服器名稱和資料庫名稱,然後使用認證對話方塊,或依提示輸入測試帳號認證資訊:

$env:SQL_SERVER_NAME = Read-Host "Server name (tcp:<server>,1433)"
$env:SQL_DATABASE = Read-Host "Database name"
$credential = Get-Credential -Message "Enter the database test account"
if ($null -eq $credential) {
    throw "Database test credentials are required."
}
$env:SQL_UID = $credential.UserName
$env:SQL_PWD = $credential.GetNetworkCredential().Password
Remove-Variable credential

在 Linux 或 macOS 上,使用 Bash。 讀取任何值前先關閉指令追蹤。 密碼提示不會顯示你輸入的內容:

set +x
IFS= read -r -p 'Server name (tcp:<server>,1433): ' SQL_SERVER_NAME
IFS= read -r -p 'Database name: ' SQL_DATABASE
IFS= read -r -p 'User name: ' SQL_UID
IFS= read -r -s -p 'Password: ' SQL_PWD
printf '\n'
export SQL_SERVER_NAME SQL_DATABASE SQL_UID SQL_PWD

環境包含明文密碼,讓 PHP 能讀取。 這些變數只在測試時使用,不要列印,測試後再清除。

將此程式儲存為 test-driver-upgrade.php。 保持環境值不變。 程式會加倍右大括號並增加外層大括號,然後將資料庫名稱、使用者名稱和密碼傳給驅動程式。 此編碼保留了字面上的大括號,並防止分號分隔連接選項。

<?php
$serverName = getenv('SQL_SERVER_NAME');
$database = getenv('SQL_DATABASE');
$uid = getenv('SQL_UID');
$pwd = getenv('SQL_PWD');

if ($serverName === false || $serverName === ''
    || $database === false || $database === ''
    || $uid === false || $uid === ''
    || $pwd === false || $pwd === '') {
    fwrite(STDERR, "Set SQL_SERVER_NAME, SQL_DATABASE, SQL_UID, and SQL_PWD." . PHP_EOL);
    exit(1);
}

$database = '{' . str_replace('}', '}}', $database) . '}';
$uid = '{' . str_replace('}', '}}', $uid) . '}';
$pwd = '{' . str_replace('}', '}}', $pwd) . '}';

$connectionOptions = [
    "Database" => $database,
    "UID" => $uid,
    "PWD" => $pwd,
    "Encrypt" => 1,
    "TrustServerCertificate" => 0,
];

$sqlsrvConnection = sqlsrv_connect($serverName, $connectionOptions);
if ($sqlsrvConnection === false) {
    foreach (sqlsrv_errors() as $error) {
        fwrite(
            STDERR,
            "SQLSRV {$error['SQLSTATE']} ({$error['code']}): {$error['message']}" . PHP_EOL
        );
    }
    exit(1);
}

$sqlsrvStatement = sqlsrv_query($sqlsrvConnection, "SELECT 1 AS TEST_VALUE");
if ($sqlsrvStatement === false) {
    foreach (sqlsrv_errors() as $error) {
        fwrite(
            STDERR,
            "SQLSRV {$error['SQLSTATE']} ({$error['code']}): {$error['message']}" . PHP_EOL
        );
    }
    sqlsrv_close($sqlsrvConnection);
    exit(1);
}

$sqlsrvRow = sqlsrv_fetch_array($sqlsrvStatement, SQLSRV_FETCH_ASSOC);
if ($sqlsrvRow === false || $sqlsrvRow === null || (int) $sqlsrvRow['TEST_VALUE'] !== 1) {
    fwrite(STDERR, "SQLSRV returned an unexpected result." . PHP_EOL);
    sqlsrv_free_stmt($sqlsrvStatement);
    sqlsrv_close($sqlsrvConnection);
    exit(1);
}

echo "SQLSRV query: 1" . PHP_EOL;
sqlsrv_free_stmt($sqlsrvStatement);
sqlsrv_close($sqlsrvConnection);

$failed = false;
try {
    $pdoConnection = new PDO(
        "sqlsrv:server=$serverName;Database=$database;Encrypt=1;TrustServerCertificate=0",
        $uid,
        $pwd,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
    $pdoStatement = $pdoConnection->query("SELECT 1 AS TEST_VALUE");
    $pdoRow = $pdoStatement->fetch(PDO::FETCH_ASSOC);
    if ($pdoRow === false || (int) $pdoRow['TEST_VALUE'] !== 1) {
        throw new RuntimeException("PDO_SQLSRV returned an unexpected result.");
    }

    echo "PDO_SQLSRV query: 1" . PHP_EOL;
} catch (Throwable $exception) {
    fwrite(STDERR, $exception->getMessage() . PHP_EOL);
    $failed = true;
} finally {
    unset($pdoStatement);
    unset($pdoConnection);
}

exit($failed ? 1 : 0);

在升級前執行程式並儲存成功執行後的輸出。 重新啟動所有 PHP 程序後,再次執行未更改的檔案:

php test-driver-upgrade.php

兩次執行都應該會產生:

SQLSRV query: 1
PDO_SQLSRV query: 1

如果升級後執行失敗,則保留相同的 SQLSTATE 或例外。 接著將失敗與基準版本、擴充版本、使用中的組態、ODBC 版本及程序重新啟動狀態進行比較。 當你無法在部署視窗內修正不匹配時,將兩個 PHP 擴充功能一起回滾。

驗證後,刪除測試檔並清除臨時環境變數。 在 PowerShell 中,執行:

Remove-Item .\verify-driver-upgrade.php, .\test-driver-upgrade.php
Remove-Item Env:\SQL_SERVER_NAME, Env:\SQL_DATABASE, Env:\SQL_UID, Env:\SQL_PWD

在 Linux 或 macOS 上,執行:

rm verify-driver-upgrade.php test-driver-upgrade.php
unset SQL_SERVER_NAME SQL_DATABASE SQL_UID SQL_PWD

回復失敗的升級作業

  1. 停止受影響的網頁伺服器、PHP-FPM 池和長期運作的工作程序。

  2. 如果你更改了 PHP 的次要版本,請還原之前的 PHP 執行環境,並將每個網頁伺服器和工作者指向該安裝。

  3. 還原之前的兩個驅動程式檔案。 將之前的 SQLSRV 和PDO_SQLSRV設定項目一起還原。

  4. 只有當新版本導致失敗,且舊套件仍被你還原的 PHP 驅動程式版本支援時,才還原先前的 ODBC 套件。

  5. 重新啟動處理程序,並重新執行基線版本檢查和應用程式煙霧測試。

  6. 保留升級失敗的日誌,以及確切的 PHP 啟動或連線錯誤訊息。 它們有助於判斷故障是出在 PHP 載入、作業系統載入器、ODBC 或資料庫連線。

疑難排解升級

徵兆 可能的原因 糾正措施
PIE 雖然成功,但 ExtensionVer 未變更 舊的已版本化項目仍會載入先前的檔案 替換舊條目、移除重複資料,並重新啟動所有 PHP 程序
SQLSRV 和 PDO_SQLSRV 回報的版本不同 只有一個分機被替換,或有一個 SAPI 設定過時 在每個執行時一起升級並設定兩個擴充功能
CLI 會回報目標版本,但網站卻沒有 網頁版 SAPI 使用另一個 PHP 安裝,或是沒有重新啟動 比較組態目錄與擴充目錄,然後重新啟動正確的程序
PHP 啟動時會回報架構或符號錯誤 這個擴充套件和 PHP 建置版本不符 還原先前的檔案,然後重建或選擇正確的 PHP 小版本、架構及執行緒安全模式的檔案
擴展器會載入,但 ODBC 變更後連線會失敗 ODBC 設定、憑證行為、認證或網路存取變更 在回滾或更改連線設定前,先擷取 SQLSTATE 和原生錯誤
工作處理序失敗,而新的網頁請求成功 長時間執行的工作程序仍持有先前的擴充功能或配置 排空並重新啟動所有工作者流程