Microsoft PHP for SQL Server 驱动程序

下载 PHP 驱动程序

Microsoft PHP for SQL Server 驱动是 PHP 扩展,允许你从 PHP 脚本读取和写入 Microsoft SQL 数据库引擎 中的数据。 该软件包包含两个驱动,它们都包裹了相同的 Microsoft ODBC SQL Server 驱动,并且共享相同的连接选项,因此你可以选择适合你代码库的 API:

  • SQLSRV 会暴露一个针对 SQL Server 特性定制的过程式 API(sqlsrv_*函数)。
  • PDO_SQLSRV实现了PHP 数据对象(PDO)接口,因此,已使用 PDO 连接其他数据库的代码只需进行少量修改即可适配 SQL Server。

这两个驱动程序都连接到 Azure SQL 数据库、Microsoft Fabric 中的 SQL 数据库、Azure SQL 托管实例 以及所有支持的 SQL Server 版本和版本(包括 Express 版本)。 它们使用 PHP 流来移动大型二进制和字符值,而无需完全加载到内存中。

选择起点

Azure SQL的生产基线

将此代码片段用作使用 PDO_SQLSRV 驱动程序连接面向生产环境的 Azure SQL 的起点。 它从环境变量中读取服务器和数据库信息(例如 Azure 应用服务 应用设置),使用托管标识进行身份验证,启用传输层安全性 (TLS) 并验证服务器证书,设置登录超时时间以涵盖冷启动故障转移所需的时间,并设置 ConnectRetryCountConnectRetryInterval 以支持 SQL Server 的空闲连接复原能力。 应用层的 connectWithRetryqueryWithRetry 辅助函数对初始连接和每条语句都采用有界指数退避机制进行包装,并将瞬时连接错误(需要建立新连接)与瞬时查询错误(可复用同一连接)区分开来。

需要PHP 8.0及更高版本、PDO_SQLSRV扩展,以及SQL Server 17.3.1.1及更高版本Microsoft ODBC驱动。Authentication=ActiveDirectoryMsi 有关受支持的 Authentication 值的完整列表,请参阅使用 Microsoft Entra 身份验证进行连接

<?php
declare(strict_types=1);

// Transient errors that require a fresh connection to recover. SQLSTATE values
// starting with '08' cover ODBC connection-established and connection-broken
// states (for example, 08001, 08S01).
const CONNECT_RETRY_SQLSTATE_PREFIX = '08';

// SQL Server error codes that are transient regardless of when they surface:
// 1205 (deadlock victim), 1222 (lock request timeout), and the Azure SQL
// throttling, mid-query failover, and "database not currently available"
// codes that arrive with SQLSTATE HY000.
const TRANSIENT_SERVER_ERROR_CODES = [1205, 1222, 40501, 40613, 40197, 10928, 10929, 49918];

/**
 * Open a connection, retrying transient failures with exponential backoff.
 */
function connectWithRetry(string $dsn, array $options, int $maxAttempts = 3): PDO
{
    for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
        try {
            $pdo = new PDO($dsn, null, null, $options);
            error_log(sprintf('connected on attempt %d/%d', $attempt, $maxAttempts));
            return $pdo;
        } catch (PDOException $e) {
            $sqlstate = (string) $e->getCode();
            $driverCode = isset($e->errorInfo[1]) ? (int) $e->errorInfo[1] : 0;
            $isTransient = str_starts_with($sqlstate, CONNECT_RETRY_SQLSTATE_PREFIX)
                || in_array($driverCode, TRANSIENT_SERVER_ERROR_CODES, true);
            if (!$isTransient || $attempt === $maxAttempts) {
                error_log(sprintf('connect failed on attempt %d/%d: %s', $attempt, $maxAttempts, $e->getMessage()));
                throw $e;
            }
            $delay = 2 ** ($attempt - 1); // 1, 2, 4 seconds
            error_log(sprintf('connect attempt %d hit transient %s/%d; retrying in %d seconds', $attempt, $sqlstate, $driverCode, $delay));
            sleep($delay);
        }
    }
    throw new RuntimeException('connectWithRetry exhausted retries');
}

/**
 * Run a parameterized query, retrying transient statement failures on the same
 * connection. Deadlocks (1205) roll back the transaction before the driver sees
 * the error, so rerunning a single statement is safe. If the statement was part
 * of a multistatement transaction, wrap the whole transaction in your own retry
 * loop so earlier statements replay too.
 */
function queryWithRetry(PDO $pdo, string $sql, array $params = [], int $maxAttempts = 3): PDOStatement
{
    for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            $driverCode = isset($e->errorInfo[1]) ? (int) $e->errorInfo[1] : 0;
            $isTransient = in_array($driverCode, TRANSIENT_SERVER_ERROR_CODES, true);
            if (!$isTransient || $attempt === $maxAttempts) {
                error_log(sprintf('query failed on attempt %d/%d: %s', $attempt, $maxAttempts, $e->getMessage()));
                throw $e;
            }
            $delay = 2 ** ($attempt - 1);
            error_log(sprintf('query attempt %d hit transient code %d; retrying in %d seconds', $attempt, $driverCode, $delay));
            sleep($delay);
        }
    }
    throw new RuntimeException('queryWithRetry exhausted retries');
}

// Load endpoint details from application configuration. In Azure App Service,
// these can come from app settings or Key Vault-backed settings.
$server = getenv('SQL_SERVER') ?: null;
$database = getenv('SQL_DATABASE') ?: null;

if ($server === null || $database === null) {
    throw new RuntimeException('Set SQL_SERVER and SQL_DATABASE in your application configuration.');
}

$dsn = sprintf(
    'sqlsrv:Driver={ODBC Driver 18 for SQL Server};Server=%s;Database=%s;'
    . 'Encrypt=true;TrustServerCertificate=false;'
    . 'LoginTimeout=90;Authentication=ActiveDirectoryMsi;'
    . 'ConnectRetryCount=5;ConnectRetryInterval=15;'
    . 'MultiSubnetFailover=true;',
    $server,
    $database
);

$options = [
    PDO::ATTR_ERRMODE               => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE    => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES      => false,
    PDO::SQLSRV_ATTR_QUERY_TIMEOUT  => 30,
];

$pdo = connectWithRetry($dsn, $options);
$stmt = queryWithRetry($pdo, 'SELECT TOP (?) name FROM sys.databases ORDER BY name', [5]);
foreach ($stmt as $row) {
    echo $row['name'], PHP_EOL;
}

此代码片段针对Azure SQL 数据库故障转移组和Azure SQL 托管实例进行了优化。

  • Driver={ODBC Driver 18 for SQL Server} 会锁定 ODBC 18 驱动程序版本。 如果主机也安装了 ODBC 17,PDO_SQLSRV可以绑定到 ODBC 17。 较旧的 17.x 构建版本会拒绝较新的 Authentication 值;例如,Authentication=ActiveDirectoryMsi 需要 ODBC 17.3.1.1 或更高版本。 请参阅为连接字符串属性“Authentication”指定了无效值

  • ConnectRetryCountConnectRetryInterval 是 ODBC 连接字符串 关键字,使 SQL Server 实现空闲连接弹性:驱动程序透明地重新连接断开的空闲连接。 这与应用层 queryWithRetry不同,后者会重试因短暂错误(如死锁或查询超时)失败的 语句 。 两者互补,所以两者都保留。 确保 LoginTimeout 至少为 ConnectRetryCount * ConnectRetryInterval,以便空闲重连路径获得完整的时间预算;示例中使用 90 秒,以覆盖 5 × 15 秒的重试时间,并为冷故障转移时的初始登录预留余量。

  • 在应用层 error_log() 呼叫中加入驾驶员诊断。 对于PDO_SQLSRV,设置 pdo_sqlsrv.log_severityphp.ini (仅初始化时可设置);对于 SQLSRV,运行时调用 sqlsrv_configure("LogSubsystems", ...) 。 更多信息请参见 日志活动

    ; php.ini - enable PDO_SQLSRV driver diagnostics alongside the application-level
    ; error_log() calls in the sample. Use 1 (errors) in production; -1 (all) is
    ; useful during triage but very chatty.
    [pdo_sqlsrv]
    pdo_sqlsrv.log_severity = 1
    
  • 对于 用户分配的 托管标识,将该标识的 ID 作为 PDO 的 $username 参数(new PDO($dsn, $identityId, null, $options))传递。 在 Azure 应用服务Azure 容器实例上,使用该标识的客户端 ID;对于其他情况,使用其对象 ID。 PHP 驱动程序继承了底层 Microsoft ODBC 驱动 for SQL Server 的行为;更多信息请参见“使用 Microsoft Entra ID 配合 ODBC 驱动。 PDO_SQLSRV 会拒绝在 DSN 内部包含 UID,因此请使用构造函数参数位置传入。 如示例所示,将 null 作为用户传入,即可选用 Azure 主机的系统分配托管标识。 对于 SQLSRV(过程式),请在连接选项数组中传入 UID

  • 当连接到故障转移组侦听器、可用性组侦听器或故障转移群集实例终结点时,设置 MultiSubnetFailover=true。 设置该值可提升单子网和多子网可用性组监听者的连接性能。 更多信息请参见 高可用性支持,灾难恢复

  • 对于读取扩展或可读辅助副本,请将 ApplicationIntent=ReadOnly 添加到数据源名称 (DSN)。

  • 对于证书使用者可选名称 (SAN) 不包含所连接主机的主权云,请将 HostNameInCertificate 添加到 DSN(例如,对于 Azure 政府,为 *.database.usgovcloudapi.net)。

  • 该驱动程序依赖于底层的 Microsoft ODBC SQL Server 驱动来获取令牌。 托管标识、服务主体和访问令牌流均通过 ODBC 进行。 有关详细信息,请参阅在 ODBC 驱动程序中使用 Microsoft Entra ID

  • 为了提高安全性和跨环境的可移植性,建议将连接信息保留在代码之外。 将连接信息存储在应用的配置系统中,并使用 Azure 密钥保管库 来管理敏感值和集中管理的连接设置。

  • 等效的 SQLSRV 连接使用 sqlsrv_connect($server, ['Database' => $database, 'Encrypt' => true, 'Authentication' => 'ActiveDirectoryMsi', /* ... */]) 并返回资源。 重试模式是一样的:捕获来自 sqlsrv_connectfalse 返回值,检查 sqlsrv_errors() 中的 SQLSTATE,然后在重试前先退避。 有关完整示例,请参阅 步骤 4:使用 PHP 以弹性方式连接到 SQL

  • 重试帮助程序会读取由 isset() 保护的 $e->errorInfo[1]PDOException::$errorInfo 被声明为 ?array,默认值为 null,因此防御性检查会回退到值为 0 的驱动程序代码,并由 SQLSTATE 的 08 前缀决定是否重试。

有关此配置的每个部分的详细信息,请参阅:

有关 Azure SQL 瞬态错误列表,请参阅排查瞬态连接错误

主要功能

  • 两个 API,一个驱动包:程序式 SQLSRV 用于SQL Server优先代码,或 PDO_SQLSRV 用于可移植 PDO 代码。
  • 广泛支持平台:支持Windows、Linux和macOS,支持PHP版本。
  • 加密连接:通过 Encrypt=trueTLS 加密的连接,服务器证书验证由 TrustServerCertificate控制。
  • Microsoft Entra ID 身份验证:通过底层的 Microsoft ODBC Driver for SQL Server,使用托管标识、服务主体和访问令牌流实现无密码连接。
  • Always Encrypted:针对敏感列的客户端加密,并支持用于就地操作的可选安全隔离区。
  • 连接恢复能力:内置空闲连接重试机制,支持 ConnectRetryCountConnectRetryInterval
  • PHP 流:将大二进制和字符值读写为流,而不是将它们加载到内存中。
  • 丰富的 SQL Server 数据类型支持datetimeoffset、表值参数、nvarchar,以及结合 PDO::SQLSRV_ENCODING_UTF8 的 Unicode。

开始

文章 Description
系统要求 支持PHP、操作系统和SQL Server版本。
支持矩阵 PHP驱动版本的详细兼容性矩阵。
下载用于 SQL Server 的 Microsoft PHP 驱动程序 下载链接和发布项目。
Linux 和 macOS 安装教程 在Linux和macOS上安装驱动及其ODBC前置条件。
加载驱动程序 php.ini中启用扩展。
开始使用 PHP SQL 驱动 将四个入门步骤整合在一起的端到端演练。
PHP SQL 驱动概述 包里有什么,什么时候该选择 SQLSRV 或 PDO_SQLSRV。

配置和连接

文章 Description
连接服务器 从PHP打开一个SQL Server实例的连接。
连接选项 关于连接关键词、默认值及其设置方法的完整参考资料。
连接到 Microsoft Azure SQL 数据库 将PHP应用连接到Azure SQL 数据库。
连接到指定的端口 选择非默认的TCP端口。
连接池 在PHP请求中重复使用ODBC连接。
禁用多个主动结果集(MARS) 关闭MARS以获取兼容性。
支持 LocalDB 连接到一个 SQL Server LocalDB 实例。
支持高可用性与灾难恢复 可用性组侦听器和多子网故障转移。
空闲连接弹性 空闲连接断裂时自动重新连接。

Authenticate

文章 Description
使用 Microsoft Entra 身份验证进行连接 托管标识、服务主体、访问令牌和密码流。
使用 SQL Server 认证连接 使用SQL登录,并输入用户名和密码。
使用 Windows 身份验证连接 在已加入域的主机上使用 Windows 集成身份验证。

Secure

文章 Description
安全注意事项 PHP应用的威胁模型与防御深度指导。
始终用PHP驱动加密 为敏感列配置客户端加密。
具有安全隔区的 Always Encrypted 通过安全 Enclave,对加密列启用丰富操作。

检索和更新数据

文章 Description
编程指南 两个驱动程序的端到端编程指南。
执行函数比较 选择适合你工作负载的执行函数。
直接和预备语句执行(PDO_SQLSRV) 何时使用直接执行,何时使用预设陈述。
获取数据 获取行、列和流式值。
数据更新 插入、更新和删除行。
执行参数化查询 绑定参数以防止SQL注入。
以数据流形式发送数据 将大型二进制和字符值流式传输到 SQL Server。
进行交易 将语句分组为原子事务。
使用表值参数 TABLE 参数传递给存储过程。
指定光标类型并选择行 选择仅向前、静态、动态或键集游标。

数据类型

文章 Description
数据类型的转换 驱动程序如何将 PHP 类型映射到 SQL Server 类型。
默认SQL Server数据类型 每个PHP值的默认SQL Server类型。
默认PHP数据类型 每个 SQL Server 列类型的默认 PHP 类型。
指定 SQL Server 数据类型 (SQLSRV) 在绑定参数时覆盖 SQL Server 类型。
指定PHP数据类型 在提取时覆盖 PHP 类型。
发送和检索UTF-8数据 使用 PDO::SQLSRV_ENCODING_UTF8 进行 Unicode 往返转换。
在Linux和macOS上发送和检索ASCII数据 处理非 Windows 主机上的 ASCII 往返转换。
格式化小数和货币(SQLSRV) 用 SQLSRV 驱动格式化 十进制货币 列。
格式:小数点和货币(PDO_SQLSRV) 使用 PDO_SQLSRV 驱动格式化 decimalmoney 列。
非系统区域设置 局部小数分隔符及其他地区因素。

错误和诊断

文章 Description
处理错误与警告 两个驱动程序的错误和警告处理。
配置错误与警告处理(SQLSRV) 优化 SQLSRV 驱动程序报告错误和警告的方式。
处理错误与警告(SQLSRV) 检查SQLSRV函数返回的错误。
日志记录活动 启用驱动程序日志以捕获诊断信息。

部署和操作

文章 Description
性能优化 连接管理、批处理、预备语句、光标、内存和服务器端监控。
Troubleshooting 诊断常见的安装、连接、查询、数据类型、事务和容器问题。

参考内容

文章 Description
SQLSRV 驱动 API 参考 所有 sqlsrv_* 函数、参数和返回值。
PDO_SQLSRV 驱动程序参考 PDO_SQLSRV 驱动程序支持的 PDO 和 PDOStatement 方法。
常量 驱动程序暴露的常量,包括类型常量和编码常量。
文章 Description
发行说明 每个版本的历史,包含新功能、漏洞修复、平台支持变更和下载链接。
关于文档中的代码示例 本节示例代码所使用的约定。
PHP SQL 驱动程序的代码示例 SQLSRV和PDO_SQLSRV的端到端示例应用。
支持资源 社区和支持渠道。