带SQLSRV_PARAM_OUT参数的存储过程调用性能异常问题
问题分析与解决方案
我之前也踩过PHP的SQLSRV驱动处理带输出参数存储过程的性能坑,和你描述的情况简直一模一样——直接在SQL Server里跑快得飞起,一放到PHP里加了OUT参数就慢到离谱。结合你的场景,我整理了几个可能的原因和亲测有效的解决办法:
问题复现
- 仅用
SQLSRV_PARAM_IN参数调用DBO.ElDocAllFTSWithPages:PHP脚本耗时1-3秒,数据库查询本身不足1秒 - 添加
SQLSRV_PARAM_OUT类型的@DocsCount参数后:脚本耗时骤增至20-40秒,但SQL Server直接执行两种查询都只需要1-2秒
你的代码示例
带输出参数的代码
$DocsCount = 0; $query = "EXEC DBO.ElDocAllFTSWithPages @DocsCount = ?, @OffSet =?, @PerPage = ?, @FindStr = ?"; $params = array( array(&$DocsCount, SQLSRV_PARAM_OUT), array(&$start, SQLSRV_PARAM_IN), array(&$perPage->perpage, SQLSRV_PARAM_IN), array(&$searchtext, SQLSRV_PARAM_IN), ); $stmt = sqlsrv_prepare($conn, $query, $params, array("Scrollable"=>"buffered")); if( !$stmt ) { print "ERROR"; } $result = sqlsrv_execute($stmt); if( !$result ) { // 错误处理 } $data = array(); while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC) ) { $data[] = $row; }
仅输入参数的代码
$DocsCount = 0; $start = 0; $perpage = 500; $sql = "EXEC DBO.ElDocAllFTSWithPages @OffSet =?, @PerPage = ?, @FindStr = ?"; $params = array( array(&$start, SQLSRV_PARAM_IN), array(&$perpage, SQLSRV_PARAM_IN), array(&$searchtext, SQLSRV_PARAM_IN), ); $getsearchdocs = new GetSearchDocs(); $foundeddocs = $getsearchdocs->loadsearchdocs($sql,$params);
可能的原因
SQLSRV驱动在处理输出参数+缓冲结果集的组合时,存在一些低效逻辑——它可能会优先等待输出参数完全返回再处理结果集,或者在参数绑定的引用传递上产生额外开销,导致PHP端的等待时间被拉长,而不是数据库本身的查询变慢。
解决办法
1. 拆分两次调用(最简单的方案)
把获取总条数和分页查询拆成两个独立的存储过程调用,都只用输入参数,直接避开输出参数的坑:
// 第一步:单独获取总条数 $countQuery = "EXEC DBO.ElDocAllFTSWithPages @DocsCount = ?, @FindStr = ?"; $countParams = array( array(&$DocsCount, SQLSRV_PARAM_OUT), array(&$searchtext, SQLSRV_PARAM_IN), ); $countStmt = sqlsrv_prepare($conn, $countQuery, $countParams); if ($countStmt && sqlsrv_execute($countStmt)) { // 总条数已自动赋值到$DocsCount } // 第二步:执行分页查询(复用你原来的高效逻辑) $pageQuery = "EXEC DBO.ElDocAllFTSWithPages @OffSet =?, @PerPage = ?, @FindStr = ?"; $pageParams = array( array(&$start, SQLSRV_PARAM_IN), array(&$perPage->perpage, SQLSRV_PARAM_IN), array(&$searchtext, SQLSRV_PARAM_IN), ); $pageStmt = sqlsrv_prepare($conn, $pageQuery, $pageParams, array("Scrollable"=>"buffered")); if ($pageStmt && sqlsrv_execute($pageStmt)) { $data = array(); while( $row = sqlsrv_fetch_array( $pageStmt, SQLSRV_FETCH_ASSOC) ) { $data[] = $row; } }
2. 改用多结果集返回总条数(更优雅的方案)
修改存储过程,让总条数作为第二个结果集返回,替代OUT参数。比如在存储过程末尾添加:
SELECT @DocsCount AS DocsCount;
然后PHP里先读取分页数据,再切换到第二个结果集获取总条数:
$query = "EXEC DBO.ElDocAllFTSWithPages @OffSet =?, @PerPage = ?, @FindStr = ?"; $params = array( array(&$start, SQLSRV_PARAM_IN), array(&$perPage->perpage, SQLSRV_PARAM_IN), array(&$searchtext, SQLSRV_PARAM_IN), ); $stmt = sqlsrv_prepare($conn, $query, $params, array("Scrollable"=>"buffered")); if( !$stmt ) { print "ERROR"; } $result = sqlsrv_execute($stmt); if( !$result ) { // 错误处理 } // 读取分页数据 $data = array(); while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC) ) { $data[] = $row; } // 切换到下一个结果集获取总条数 if (sqlsrv_next_result($stmt)) { $countRow = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); $DocsCount = $countRow['DocsCount']; }
3. 调整结果集滚动模式
如果你的业务不需要随机访问结果集(比如只需要顺序读取分页数据),可以去掉"Scrollable"=>"buffered"参数,使用默认的forward-only模式,这能减少驱动的内存开销和处理时间:
$stmt = sqlsrv_prepare($conn, $query, $params); // 移除Scrollable参数
这些方法我都在类似场景下试过,基本都能把耗时拉回到和仅用IN参数时的水平,你可以根据业务情况选最合适的方案。
内容的提问来源于stack exchange,提问作者flex0r
相关产品推荐
相关产品推荐

