比较执行函数

下载 PHP 驱动程序

两个驱动程序都可以单次执行语句,或预处理语句以供重复执行。 即使只运行一次查询,也要对用户提供的值使用参数。

将执行 API 的选择与获取结果的方式分开。 预处理查询不会获取其结果行,缓冲结果也不会让查询变成参数化查询。 以下示例准备一次只读查询,用不同的值执行它两次,并在下一次执行前消耗每个结果。

执行风格 SQLSRV PDO_SQLSRV
运行一次查询,不带参数 sqlsrv_query PDO::query 用于结果,或 PDO::exec 用于受影响行计数
运行一次带有参数的查询 sqlsrv_query($conn, $sql, $params) PDO::prepare() 后接 PDOStatement::execute()
准备一次,使用不同参数反复执行 sqlsrv_prepare + sqlsrv_execute PDO::prepare + PDOStatement::execute

sqlsrv_query() 在一次调用中准备并执行,并返回语句资源。 sqlsrv_prepare() 返回语句资源但不执行;每次执行时调用 sqlsrv_execute()。 PDO 的 query() 执行并返回 PDOStatement,而 exec() 返回的是受影响的行计数而非结果对象。 成功 exec() 返回 0,因此将失败返回与 === false进行比较,而非 if (!$count)。

PDO::query() 不接受参数数组。 其可选参数用于配置获取。 对于参数化PDO_SQLSRV查询,使用 prepare() 和 execute()。

一次准备可以让你用不同值复用语句句柄。 这并不保证服务器永远不会重新编译执行计划。 PDO_SQLSRV默认使用预备执行,包括; PDO::query()其 直接查询属性 控制该独立选择。

准备一次,反复执行

这些都是独立且完整的命令行程序。 安装并加载所选扩展,并在 PHP 进程环境中设置 SQL_SERVER、SQL_DATABASE、SQL_USER 和 SQL_PASSWORD。 使用SQL Server测试数据库和客户端信任的服务器证书。 关于环境设置和无密码替代方案,请参见 连接服务器。

将实际的数据库名称、用户名和密码存储在环境变量中。 两个程序都通过双倍闭括号并将每个值包裹在大括号中,以 PHP 驱动的 ODBC 式连接处理方式逃逸一次。 转义连接值与绑定查询参数是分开的。

这些程序在两个内联行执行 Transact-SQL(T-SQL)查询,无需创建表或更改数据。 将每个程序保存在独立.php文件中,并用 运行。php <file> 不需要样本数据库。

SQLSRV

通过 $productId 引用 &$productId。 然后每个 sqlsrv_execute() 都使用当前值。 SQLSRV_FETCH_ASSOC 返回以列名为键的数组。 结果 null 表示没有更多的行; false 表示错误。

<?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]) . '}';
}
$conn = sqlsrv_connect($config['SQL_SERVER'], [
    'Database' => $config['SQL_DATABASE'],
    'UID' => $config['SQL_USER'],
    'PWD' => $config['SQL_PASSWORD'],
    'Encrypt' => true,
    'TrustServerCertificate' => false,
    'LoginTimeout' => 30,
    'CharacterSet' => 'UTF-8',
]);
if ($conn === false) {
    throw new RuntimeException(print_r(sqlsrv_errors(), true));
}

$sql = "SELECT Name FROM (VALUES (1, 'Bike'), (2, 'Helmet')) AS Product(ProductId, Name) WHERE ProductId = ?";
$productId = 1;
$stmt = null;
try {
    $stmt = sqlsrv_prepare($conn, $sql, [&$productId], ['QueryTimeout' => 30]);
    if ($stmt === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    foreach ([1 => 'Bike', 2 => 'Helmet'] as $productId => $expected) {
        if (!sqlsrv_execute($stmt)) {
            throw new RuntimeException(print_r(sqlsrv_errors(), true));
        }
        $names = [];
        while (($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) !== null) {
            if ($row === false) {
                throw new RuntimeException(print_r(sqlsrv_errors(), true));
            }
            $names[] = $row['Name'];
        }
        if ($names !== [$expected]) {
            throw new RuntimeException('Expected exactly one matching product.');
        }
        echo $names[0], PHP_EOL;
    }
} finally {
    if (is_resource($stmt)) {
        sqlsrv_free_stmt($stmt);
    }
    sqlsrv_close($conn);
}

PDO_SQLSRV

用 绑定$productId一次。bindParam() 每个 execute() 都读取其当前值。 PDO::PARAM_INT 指定参数的PHP/PDO绑定类型;它不控制从结果中获取的列的PHP类型。 设置异常模式,使数据库错误中断执行,而不是误认为是空结果。

<?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'];
$pdo = new PDO(
    "sqlsrv:Server=$server;Database=$database;Encrypt=true;"
    . "TrustServerCertificate=false;LoginTimeout=30",
    $config['SQL_USER'],
    $config['SQL_PASSWORD'],
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8,
    ]
);
$sql = "SELECT Name FROM (VALUES (1, 'Bike'), (2, 'Helmet')) AS Product(ProductId, Name) WHERE ProductId = ?";
$stmt = null;
try {
    $stmt = $pdo->prepare($sql, [PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 30]);
    $productId = 1;
    $stmt->bindParam(1, $productId, PDO::PARAM_INT);
    foreach ([1 => 'Bike', 2 => 'Helmet'] as $productId => $expected) {
        $stmt->execute();
        $names = $stmt->fetchAll(PDO::FETCH_COLUMN);
        if ($names !== [$expected]) {
            throw new RuntimeException('Expected exactly one matching product.');
        }
        echo $names[0], PHP_EOL;
        $stmt->closeCursor();
    }
} finally {
    $stmt = null;
    $pdo = null;
}

每个示例均输出:

Bike
Helmet

传递给PDO execute() 的数组中的值被处理为 PDO::PARAM_STR。 当 SQL 需要整数参数(如参数化 TOP 计数)时,先调用 bindValue() , PDO::PARAM_INT 然后 execute() 调用不带数组。

绑定值而不更改查询

参数标记表示一个值,而不是表名、列名、关键词或排序方向。 不要在 ? 周围加上引号。 对于动态标识符或 ASC/DESC,从应用控制的允许列表中选择,而不是串接不可信的文本。 对于 IN 列表,每个值都提供一个标记。

要求 SQLSRV PDO_SQLSRV
为单次执行绑定值 将参数数组传递给 sqlsrv_query()。 在 prepare() 之后将数组传递给 execute(),或者先绑定值。
重用变量当前值 将 &$variable 包含在提供给 sqlsrv_prepare() 的数组中。 先使用该变量调用 bindParam(),然后在不使用数组的情况下调用 execute()。 PHP 通过引用传递变量;不要在调用位置添加 &。
在绑定时绑定一个值 在参数数组中使用一个值用于执行。 调用 bindValue();后续对原始变量的更改不会改变该绑定值。
指定一个整数输入 在需要显式类型时,使用带有 SQLSRV_PARAM_IN、SQLSRV_PHPTYPE_INT 和 SQLSRV_SQLTYPE_INT 的参数描述符。 与PDO::PARAM_INT绑定。 位置bindParam()和bindValue()索引从0开始,而不是1。
发送SQL NULL 在操作需要时,绑定 PHP null,并显式指定 SQL 类型。 与 PDO::PARAM_NULL 绑定。
接收输出参数 使用带有 SQLSRV_PARAM_OUT 或 SQLSRV_PARAM_INOUT的引用变量。 使用 bindParam() 与 PDO::PARAM_INPUT_OUTPUT 和 所需的类型和长度。

在执行预处理语句时,保持引用变量的存续。 SQLSRV程序是更改绑定变量,而不是替换参数数组。 PDO 程序在绑定后故意调用 execute(),且不传入数组;不要将这种模式与基于数组的执行混淆。

存储过程的输出值需要单独处理,包括完成该存储过程的结果处理。 不要从这些仅输入的例子中推断输出参数行为。 更多信息请参见 使用方向参数。

配置执行和获取行为

设置 SQLSRV PDO_SQLSRV
执行超时 QueryTimeout 在声明选项数组中。 PDO::SQLSRV_ATTR_QUERY_TIMEOUT 关于连接或语句。
已命名的结果键 SQLSRV_FETCH_ASSOC 传递给 sqlsrv_fetch_array()。 PDO::FETCH_ASSOC 传递给取物方法,或设置为连接的默认取物模式。
仅向前结果 默认光标;按顺序获取。 默认光标;按顺序提取。
客户端缓冲结果 Scrollable => SQLSRV_CURSOR_CLIENT_BUFFERED。 带 PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL 的 PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED。
直接执行与准备执行 没有 PDO 直接查询属性。 选择合并 sqlsrv_query() 调用或显式准备/执行对。 PDO::SQLSRV_ATTR_DIRECT_QUERY 选择直接执行而非驱动程序默认的预备执行。
参数仿真 没有 PDO 模拟属性。 PDO::ATTR_EMULATE_PREPARES 与直接查询属性是分开的。 不要用它来替代正确类型的装订。

这些例子会返回较少的结果。 fetchAll() 将每一行加载到PHP内存中;对于大结果,使用取指循环。 查询没有匹配不算执行错误。 SQLSRV 报告结果结束时为 nullsqlsrv_fetch_array();PDO 报告falsefetch()为 。 在被抓取的行内的SQL NULL 仍然是PHP null。

不要只用 SELECT 行数作为唯一的成功检定。 仅前进光标不具备缓冲或可滚动光标的行计数功能。 取出并统计你需要的行数,或者用单独 COUNT 的查询。 关于光标特定的选项和限制,请参见 “指定光标类型并选择行”。

处理故障与完成结果

分别检查SQLSRV的准备、执行和获取操作。 成功的 prepare 并不证明执行一定成功:服务器可以在执行时拒绝参数值、权限检查或语句。 当操作返回false时立即捕获sqlsrv_errors()。 SQLSRV默认将许多警告视为错误,因此请检查完整的集合。

对于PDO,异常模式下的连接失败和数据库错误会抛出 PDOException。 在受保护的应用诊断中检查其SQLSTATE和驱动程序错误信息;不要在HTTP响应中返回异常文本或凭证。 prepare() 成功并不意味着声明已经发表。

症状 Action
每次执行都使用第一个值 对于 SQLSRV,通过引用绑定一个变量。 对于 PDO,使用 bindParam() 表示可变变量,或针对每个新值调用 bindValue()。
拒绝一个数值参数,例如 TOP 计数 明确绑定所需的整数类型。 即使PHP值是整数,PDO execute([$value]) 仍然使用字符串参数。
第二个命令报告待处理结果 在重新使用连接前,先消费结果或释放它们。 对于PDO,当你不需要剩余结果时使用 closeCursor() 。
预期输出参数未变 完成所有存储过程结果,验证参数方向、PHP类型和输出缓冲区长度。
内存使用随结果大小增长 用 fetchAll() 顺序取用替换或不必要的客户端缓冲。

这些例子释放了它们的语句,并在 中释放了它们的 finally连接。 他们不会开始交易。 当多个写入必须同时提交或全部回滚时,使用显式 事务 。 重复使用预处理语句不会将这些写入归入一个事务。

目标与实施差异

准备/执行 API 和 PHP 参考规则属于扩展和 PHP/PDO。 SQL 目标决定支持哪些 T-SQL 语句和类型,ODBC 执行底层通信。 一次性准备可以减少重复工作,但不能保证特定的服务器执行计划或加速。

输入绑定模式适用于 SQL Server、Azure SQL 数据库、Azure SQL 托管实例、Azure Synapse Analytics、Microsoft Fabric 中的 SQL 数据库以及 Microsoft Fabric 中的 Warehouse。 这些程序使用 SQL 认证来访问 SQL Server。 对于 Azure onboarding,prefer Microsoft Entra 认证。 两个 Fabric 产品都需要这样做,仓库也需要禁用多个主动结果集(MARS)。 在重用 SQL Server 代码之前,请检查所选目标对模式变更、事务和存储过程的支持。 关于端点和认证的区别,请参见 Azure 和 Fabric 连接。