比較執行函式

下載 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() 回傳的是受影響的列計數,而非結果物件。 成功的 0 可返回 === false,因此將失敗返回與 if (!$count) 進行比較,而非 exec()。

PDO::query() 不接受參數值陣列。 其選用引數可設定擷取作業。 對於參數化PDO_SQLSRV查詢,請使用 prepare() 和 execute()。

只要準備一次,你就可以用不同的值重複使用一個陳述式控制代碼。 這並不保證伺服器永遠不會重新編譯執行計畫。 PDO_SQLSRV 預設使用預備執行,包括 PDO::query();其 直接查詢屬性 控制該獨立選擇。

準備一次,反覆執行

這些都是獨立且完整的命令列程式。 安裝並載入所選擴充功能,並在 PHP 程序環境中設定 SQL_SERVER、SQL_PASSWORD、SQL_DATABASE 和 SQL_USER。 使用 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 清單,請為每個值提供一個標記。

Requirement 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() 和 1 索引從 0 開始,而不是從 bindValue() 開始。
傳送 SQL NULL 當操作需要時,將 PHP null與明確的 SQL 型別綁定。 與PDO::PARAM_NULL綁定。
接收輸出參數 使用帶有 SQLSRV_PARAM_OUT 或 SQLSRV_PARAM_INOUT的參考變數。 使用 bindParam() 搭配 PDO::PARAM_INPUT_OUTPUT,並指定所需的類型和長度。

執行預備敘述時,保持參考變數的存活狀態。 SQLSRV 程式是改變綁定變數,而非替換參數陣列。 PDO 程式在綁定後會故意在沒有陣列的情況下呼叫 execute();不要將這種模式與以陣列為基礎的執行方式混用。

儲存程序的輸出值需要自行處理,包括完成該程序傳回的結果。 不要從這些僅輸入的例子推斷輸出參數行為。 欲了解更多資訊,請參閱 使用方向參數。

設定執行與擷取行為

Setting SQLSRV PDO_SQLSRV
執行逾時 QueryTimeout 在 statement 選項陣列中。 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 與 direct-query 屬性是分開的。 不要用它來替代正確類型的綁定。

這些範例會回傳一些小小的結果。 fetchAll() 將每一列載入 PHP 記憶體;使用擷取迴圈取得較大的結果。 查詢未傳回任何相符結果,這不是執行錯誤。 SQLSRV 回報結果結束時,從 sqlsrv_fetch_array() 傳回 null;PDO 回報從 fetch() 傳回 false。 在被擷取的列內的 SQL NULL 仍然是 PHP null。

不要只用 SELECT 行數當作唯一的成功檢定。 僅向前的游標無法提供緩衝或可捲動游標相同的列數功能。 取取並計算你需要的列數,或使用獨立 COUNT 查詢。 關於游標專屬選項與限制,請參見指定游標類型並選取資料列。

處理失敗與最終結果

分別檢查 SQLSRV 的準備、執行和取回操作。 成功的 prepare 並不代表執行會成功:伺服器可以在執行時拒絕參數值、權限或陳述式。 當操作返回sqlsrv_errors()時立即擷取false。 SQLSRV 預設將許多警告視為錯誤,因此請檢查整個集合。

對於 PDO,連線失敗與例外模式下的資料庫錯誤會拋出 PDOException。 在受保護的應用程式診斷中檢查其 SQLSTATE 與驅動程式錯誤資訊;在 HTTP 回應中不要回傳例外文字或憑證。 prepare() 成功並不代表該陳述式已經執行。

徵兆 Action
每次執行都使用第一個值 對於 SQLSRV,請以參照方式繫結變數。 對於 PDO,對於會變動的變數,請使用 bindParam(),或對每個新值呼叫 bindValue()。
數值參數會被拒絕,例如 TOP 計數 明確綁定所需的整數型別。 PDO execute([$value]) 即使 PHP 值是整數,也會使用字串參數。
第二個指令則回報待處理的結果 在重複使用連線前,先消費或釋放結果。 至於PDO,當不需要剩下的結果時就用 closeCursor() 。
預期輸出參數沒有改變 完成所有程序回傳結果,並驗證參數方向、PHP 型態及輸出緩衝區長度。
記憶體使用量會隨結果大小增加 以循序擷取取代 fetchAll() 或不必要的用戶端緩衝。

這些範例會在 finally 中釋放其陳述式,並釋放其連線。 他們不開始交易。 當多個寫入必須同時提交或全部回滾時,使用明確的事務。 重複使用預備陳述式不會將寫入群組成交易。

目標與實作差異

prepare/execute API 和 PHP 參考頁規則屬於擴充功能和 PHP/PDO。 SQL 目標決定支援哪些 T-SQL 語句與型別,而 ODBC 則執行底層通訊。 一次準備可以減少重複工作,但不保證特定的伺服器執行計畫或速度提升。

輸入綁定模式適用於 SQL Server、Azure SQL Database、Azure SQL 受控執行個體、Azure Synapse Analytics、Microsoft Fabric 中的 SQL 資料庫,以及 Microsoft Fabric 中的 Warehouse。 這些程式使用 SQL 認證來登入 SQL Server。 對於 Azure 導入流程,建議優先使用 Microsoft Entra 認證。 Fabric 產品皆需此功能,Warehouse 也需停用多個主動結果集(MARS)。 在重用 SQL Server 程式碼前,請檢查所選目標對結構變更、交易及儲存程序的支援。 關於端點與認證的差異,請參見 Azure 與 Fabric 連線。