将SQLSRV和PDO_SQLSRV一起升级,使两个扩展使用相同的版本系列。 PHP 驱动使用 Microsoft ODBC 驱动 for SQL Server,升级前请查看系统需求。
升级会改变加载到PHP进程中的本地代码。 混合部署可能继续运行上一个版本,PHP启动时失败,或向CLI和Web应用暴露不同版本的驱动。 对每个PHP运行时进行盘点,并保留之前的文件和配置,直到验证完成。
选择升级路径
| 当前安装 | 目标安装位置 | Procedure |
|---|---|---|
| PIE | PIE | 用相同的 PHP 可执行文件或显式指定 php-config 运行 PIE,然后验证每个 SAPI |
| Windows 版 ZIP | Windows 生成的 ZIP | 替换两个版本化的 DLL,并同时更新两个配置条目 |
| Windows 已发布的 ZIP | PIE | 安装两个 PIE 包,用 PIE 条目替换版本化的 DLL 条目,并删除重复项 |
| Linux 或 macOS 已发布的归档文件 | PIE | 安装两个 PIE 包,将归档文件名条目替换为 sqlsrv.so 和 pdo_sqlsrv.so,并保留 PDO 加载顺序 |
| Linux 或 macOS PIE | 新的PHP小版本 | 安装新的PHP开发工具,为该PHP安装构建两个扩展,并分别配置每个SAPI |
在你拿到回滚副本之前,不要覆盖之前的二进制文件。 当应用同时使用两个 API 时,不要只升级一个扩展。
升级之前
记录PHP可执行文件、配置文件、架构、线程安全模式及当前驱动版本。
php --version php --ini php --ri sqlsrv php --ri pdo_sqlsrv备份
php.ini文件以及任何特定于扩展名的.ini文件,这些文件会加载sqlsrv或pdo_sqlsrv。请查看目标PHP版本和操作系统 的支持矩阵 。
请查看 系统需求中的ODBC兼容性表。 当目标驱动版本要求时,在PHP驱动升级前或升级期间安装支持的ODBC版本。
计划重启所有加载PHP的进程,包括Web服务器、 PHP FastCGI进程管理器(PHP-FPM)、队列工作者和长期运行的命令行工作者。
记录每个运行时的基线:
- 由
php --ri pdo_sqlsrv和php --ri sqlsrv显示的ExtensionVer。 - 已加载的配置文件和已扫描的
.ini目录 - 扩展目录和活动驱动程序文件名。
- 一个已通过的应用程序冒烟测试,使用你的应用程序所依赖的每个 API。
- 由
准备回滚:
- 如果您更改 PHP 次版本,请保留之前的 PHP 运行时可供使用。 记录为每个 Web 服务器和工作进程配置的可执行文件。
- 保留之前的 SQLSRV 和 PDO_SQLSRV 文件副本。
- 保留所有已更改的
php.ini或特定于.ini扩展名的文件副本。 - 如果升级更改了原来的ODBC包版本,请记录下来。
- 定义如何在本地扩展文件被替换时停止流量或工作进程。
在Windows上使用PIE升级
使用PHP 扩展安装程序(PIE)配合为应用程序服务的PHP可执行文件。 首先,下载并验证PIE。 在执行任一安装命令前,先切走应用请求流量,并停止使用该 PHP 安装的 Web 服务器、FastCGI 进程和工作进程。 在两个扩展及其配置都准备好之前,防止它们自动重启。 为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 安装
从Microsoft Drivers for PHP for SQL Server releases页面下载目标版本的 Windows ZIP。
请确认新的动态链接库(DLL)名称是否与PHP小版本、架构和线程安全模式相符。 在替换文件之前,先停止PHP Web服务器或工作进程。
替换 PHP 扩展目录中的两个
php_sqlsrv_*.dll和php_pdo_sqlsrv_*.dll文件。 如果文件名仍然匹配,就保留现有extension=条目。 否则,就一起更新它们。如果目标PHP驱动需要较新的受支持版本,安装或升级 Microsoft ODBC Driver for SQL Server。
重启所有使用被替换DLL的PHP进程。 运行中的进程会让旧的本地扩展保持加载,直到它退出。
在Linux或macOS上升级,使用PIE
如果没有安装PIE,请按照 PIE 的设置和平台先决条件。 请确认PHP开发工具和ODBC开发包已安装。 在交互式会话中,PIE 可以通过平台包管理器提示安装缺失的构建工具。 在非交互式会话中,先安装它们再运行 PIE。
升级前先选择 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 安装命令。排空应用流量,停止使用该 PHP 安装的 Web 服务器、PHP-FPM 进程和工作进程。 在两个扩展及其配置都准备好之前,防止它们自动重启。 为PIE保留一个单独的命令行会话。
暂时将 PIE 使用的 CLI 配置中现有的 SQLSRV 和 PDO_SQLSRV 扩展条目注释掉。 请保持 PIE 的前提扩展处于启用状态。
分别升级这两个软件包。 这些命令使用 PIE 默认的 PHP 安装:
pie install microsoft/sqlsrv pie install microsoft/pdo_sqlsrv检查你的应用程序所用 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、网页服务器和工作者配置也进行同样的配置更改。
当目标版本要求时,升级适用于 SQL Server 的 Microsoft ODBC 驱动程序。
重启每个使用升级PHP安装的CLI工作程序、队列工作程序、网页服务器和 PHP-FPM 服务。
重启并验证
替换扩展文件并不会更新已经加载过旧二进制文件的PHP进程。 在验证升级前,先重启 Web 服务器、PHP-FPM 以及所有长时间运行的 PHP 工作进程。
验证命令行运行时:
php --ri sqlsrv php --ri pdo_sqlsrv php -m | findstr /I "sqlsrv pdo_sqlsrv"在 Linux 或 macOS 上,将最后一个命令替换为
php -m | grep -E 'sqlsrv|pdo_sqlsrv'。对于网页应用,在创建临时页面之前,先
phpinfo()。 验证两个驱动程序的已加载配置文件、扩展目录以及ExtensionVer。 确认ExtensionVer报告的是目标版本后,删除该页面。如果您的应用同时使用这两者,请对现有应用运行一次同时测试 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 可执行文件运行程序,并暂时通过各个 Web 运行时运行。 确认已报告的两个版本是否与你安装的版本一致。 验证后删除网页副本。
该程序验证了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
回滚未成功的升级
停止受影响的网络服务器、PHP-FPM 池和长时间运行的工作进程。
如果你更改了 PHP 小版本,请恢复之前的 PHP 运行时,并将每个 Web 服务器和工作者指向该安装。
恢复之前的两个驱动程序文件。 将之前的 SQLSRV 和PDO_SQLSRV配置条目一起恢复。
只有在新版本导致失败且旧包仍被你恢复的PHP驱动版本支持时,才恢复之前的ODBC包。
重启进程,重新运行基线版本检查和应用烟雾测试。
保留失败的升级日志和确切的 PHP 启动或连接错误。 它们有助于识别故障是源自PHP加载、操作系统加载器、ODBC还是数据库连接。
排查升级问题
| 问题 | 可能的原因 | 纠正措施 |
|---|---|---|
PIE成功了,但ExtensionVer没有变化 |
旧的版本化条目仍然会加载之前的文件 | 替换旧条目,删除重复,重启所有PHP进程 |
| SQLSRV 和 PDO_SQLSRV 报告不同的版本 | 只有一个扩展被替换,或者有一个SAPI配置过时 | 每个运行时一起升级和配置两个扩展 |
| CLI报告目标版本,但网站没有 | 网页SAPI使用了另一个PHP安装,或者未被重启 | 比较配置目录和扩展目录,然后重新启动正确的进程 |
| PHP 在启动时报告架构或符号错误 | 扩展和PHP构建不匹配 | 恢复之前的文件,然后重建或选择正确的PHP小版本、架构和线程安全模式的文件 |
| 扩展加载,但连接在更改 ODBC 后会失败 | ODBC 配置、证书行为、认证或网络访问发生变化 | 在回滚或更改连接设置之前,先捕获 SQLSTATE 和原生错误 |
| 工作进程发生故障,而新的网页请求成功 | 长时间运行的工作线程仍持有之前的扩展程序或配置 | 排空并重启所有工作进程 |