跨环境下SQL_SRV查询结果不一致问题排查求助
PHP sqlsrv查询远程机器返回结果不完整且不稳定,本地/SSMS运行正常
我在远程机器部署了PHP脚本,用于从Microsoft SQL Server拉取数据插入MySQL,配置cron每小时同步。但无论命令行还是cron运行脚本,SQL Server查询结果总是不完整,相同参数下返回行数每次都不一样;但在SQL Server Management Studio或本地测试脚本中运行相同查询,结果完全正确。
已排查的方向:
- 用
ini_set()设置max_execution_time=0、max_input_time=0、memory_limit=-1,无改善 - 远程机器未安装防火墙,出站连接完全开放,排除网络入站限制问题
- 查阅过类似问题方案,但均无效或不适用
本地测试脚本
<?php $connection_info = array( "UID"=>"UserID", "PWD"=>"password", "Database"=>"database_name" ); $serverName = 'database_host'; try{ $conn = sqlsrv_connect($serverName, $connection_info); } catch(Exception $e){ die("Not able to connect."); } echo "Connected!\n"; $sql = " SELECT SP.SaleInvoiceID, SP.AssociationNumber, SP.Priority, S.Abbreviation, GP.GlobalProductID, SI.OrderEntryID, SI.OriginalSaleInvoiceID, SI.AccountsReceivableID, SI.EmployeeID3, SI.InvoiceIDByStore AS [invoice], (C.Customer_Name + ' ' + C.First_Name + ' ' + C.Last_Name) AS [customer], C.Id_Number as [customer_id], GP.ProductIdentifier AS [product_sku], RGP.ManufacturerPartNumber AS [model], CAST( L2.FieldText AS varchar ) AS [manufacturer], CAST( L1.FieldText AS varchar ) AS [description], CAST( L3.FieldText AS varchar ) AS [category], SP.Quantity AS Qty, SP.SerialNumber, SP.ContractNumber, SP.MobileNumber, GP.CategoryNumber, SP.UnitPrice, SP.UnitCost, SP.ListPrice, SI.SplitRate1, SI.SplitRate2, SI.EmployeeID1, SI.EmployeeID2, E1.Employee_Name AS [Employee1], E2.Employee_Name AS [Employee2], SI.DateCreated, SI.Comments FROM [database_name].[dbo].[SaleInvoices] SI INNER JOIN [database_name].[dbo].[SaleInvoicesAndProducts] SP ON SI.SaleInvoiceID = SP.SaleInvoiceID INNER JOIN [database_name].[dbo].[iQclerk_GlobalProducts] GP ON SP.GlobalProductID = GP.GlobalProductID INNER JOIN [database_name].[dbo].[LanguageTranslations] L1 ON GP.ProductNameID = L1.ReferenceID INNER JOIN [database_name].[dbo].[Stores] S ON S.StoreID = SI.StoreID1 INNER JOIN [database_name].[dbo].[Customer_Information] C ON SI.CustomerID1 = C.Id_Number INNER JOIN [database_name].[dbo].[Employees] E1 ON E1.Id_Number = SI.EmployeeID1 INNER JOIN [database_name].[dbo].[Categories] CAT ON CAT.CategoryNumber = GP.CategoryNumber INNER JOIN [database_name].[dbo].[LanguageTranslations] L3 ON CAT.CategoryPathID = L3.ReferenceID LEFT OUTER JOIN [database_name].[dbo].[Employees] E2 ON E2.Id_Number = SI.EmployeeID2 LEFT OUTER JOIN [database_name].[dbo].[RegularGlobalProducts] RGP ON RGP.GlobalProductID = GP.GlobalProductID LEFT OUTER JOIN [database_name].[dbo].[Manufacturers] M ON M.ManufacturerID = RGP.ManufacturerID LEFT OUTER JOIN [database_name].[dbo].[LanguageTranslations] L2 ON M.ManufacturerNameID = L2.ReferenceID WHERE SI.DateCreated BETWEEN cast( '2024-03-04 00:00:00' AS DateTime ) AND cast( '2024-05-09 23:59:59' AS DateTime ) AND L3.LanguageCode = 'en-us' "; $result = sqlsrv_query($conn, $sql, array(), array("Scrollable" => 'static')); print("Data package received, retrieving data from package...\n"); $result_rows = array(); $i = 1; while( $row = sqlsrv_fetch_array( $result, SQLSRV_FETCH_ASSOC ) ){ print("Records retrieved: ".$i."\r"); $i++; array_push($result_rows, $row); } print("\n"); print(sizeof($result_rows));
远程机器模块化脚本
<?php class SQLSRV{ protected $dbConn; public function open_db($server, $user, $password, $database, $throwError=false) { global $_REGISTRY; ini_set("max_execution_time","0"); ini_set("max_input_time","0"); ini_set("memory_limit","-1" ); $errorMsg = "SQLSRV_ERROR: Cannot connect to SQL server database!\n"; $serverName = $server; $connectionInfo = array( 'UID' => $user, 'PWD' => $password, 'DATABASE' => $database ); $this->dbConn = sqlsrv_connect($serverName, $connectionInfo); if (!$this->dbConn) { $_REGISTRY['STATE'] = 'FATAL'; $_REGISTRY['ERROR_MESSAGE'] = 'DATABASE_CONNNECTION_ERROR'; if($throwError) throw new \ErrorException( $errorMsg, 10001, 0,__file__, __line__ ); else return $errorMsg; } return true; } public function query( $sql, $throwError=true ) { if( !$sql ) return null; if(!$throwError) { sqlsrv_configure("WarningReturnAsErrors", 0); } $errorMsg = "SQLSRV_ERROR: No database connection"; if(!$this->dbConn ) { if( $throwError) throw new \ErrorException( $errorMsg, 10001, 0,__file__, __line__ ); else return $errorMsg; } $query = false; try { $query = sqlsrv_query( $this->dbConn, $sql, array(), array("Scrollable" => 'static') ); } catch( Exception $e ) {} $queryError = 'SQLSRV_ERROR: ' . sqlsrv_errors() . "\n\n<pre>$sql</pre>\n"; if(!$throwError) { sqlsrv_configure("WarningReturnAsErrors", 1); } if (!$query) { if($throwError) throw new \Exception( $queryError ); else return $queryError; } else { return $query; } } public function fetchData( $sql, $throwError=true ) { $res = $this->query( $sql, $throwError ); if($res) { $queryError = 'SQLSRV_ERROR: ' . sqlsrv_errors() . "\n\n$sql\n"; file_put_contents('/var/tmp/sqlsrv.log', "fetchData Res: $queryError\n", FILE_APPEND ); try{ if(sqlsrv_num_rows($res)) { $ar = array(); while( $row = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC ) ) { array_push($ar, $row); } return $ar; } } catch( \Exception $e ) { return false;} } else return false; } } $sql_srv_obj = new SQLSRV(); $sql_srv_obj->open_db('database_host', 'username', 'password', 'database_name'); $sql_query = " SELECT SP.SaleInvoiceID, SP.AssociationNumber, SP.Priority, S.Abbreviation, GP.GlobalProductID, SI.OrderEntryID, SI.OriginalSaleInvoiceID, SI.AccountsReceivableID, SI.EmployeeID3, SI.InvoiceIDByStore AS [invoice], (C.Customer_Name + ' ' + C.First_Name + ' ' + C.Last_Name) AS [customer], C.Id_Number as [customer_id], GP.ProductIdentifier AS [product_sku], RGP.ManufacturerPartNumber AS [model], CAST( L2.FieldText AS varchar ) AS [manufacturer], CAST( L1.FieldText AS varchar ) AS [description], CAST( L3.FieldText AS varchar ) AS [category], SP.Quantity AS Qty, SP.SerialNumber, SP.ContractNumber, SP.MobileNumber, GP.CategoryNumber, SP.UnitPrice, SP.UnitCost, SP.ListPrice, SI.SplitRate1, SI.SplitRate2, SI.EmployeeID1, SI.EmployeeID2, E1.Employee_Name AS [Employee1], E2.Employee_Name AS [Employee2], SI.DateCreated, SI.Comments FROM [database_name].[dbo].[SaleInvoices] SI INNER JOIN [database_name].[dbo].[SaleInvoicesAndProducts] SP ON SI.SaleInvoiceID = SP.SaleInvoiceID INNER JOIN [database_name].[dbo].[iQclerk_GlobalProducts] GP ON SP.GlobalProductID = GP.GlobalProductID INNER JOIN [database_name].[dbo].[LanguageTranslations] L1 ON GP.ProductNameID = L1.ReferenceID INNER JOIN [database_name].[dbo].[Stores] S ON S.StoreID = SI.StoreID1 INNER JOIN [database_name].[dbo].[Customer_Information] C ON SI.CustomerID1 = C.Id_Number INNER JOIN [database_name].[dbo].[Employees] E1 ON E1.Id_Number = SI.EmployeeID1 INNER JOIN [database_name].[dbo].[Categories] CAT ON CAT.CategoryNumber = GP.CategoryNumber INNER JOIN [database_name].[dbo].[LanguageTranslations] L3 ON CAT.CategoryPathID = L3.ReferenceID LEFT OUTER JOIN [database_name].[dbo].[Employees] E2 ON E2.Id_Number = SI.EmployeeID2 LEFT OUTER JOIN [database_name].[dbo].[RegularGlobalProducts] RGP ON RGP.GlobalProductID = GP.GlobalProductID LEFT OUTER JOIN [database_name].[dbo].[Manufacturers] M ON M.ManufacturerID = RGP.ManufacturerID LEFT OUTER JOIN [database_name].[dbo].[LanguageTranslations] L2 ON M.ManufacturerNameID = L2.ReferenceID WHERE SI.DateCreated BETWEEN cast( '2024-03-04 00:00:00' AS DateTime ) AND cast( '2024-05-09 23:59:59' AS DateTime ) AND L3.LanguageCode = 'en-us' "; $data = $sql_srv_obj->fetchData($sql_query);
排查建议
- 更新sqlsrv驱动版本:远程机器的sqlsrv扩展版本可能存在bug,建议升级到官方最新稳定版,对比本地环境的驱动版本。
- 移除静态游标配置:远程脚本中
sqlsrv_query使用了"Scrollable" => 'static',静态游标需要一次性加载全部结果集,在网络不稳定时容易出现截断。改为默认的向前游标(删除该参数),逐行读取即可满足同步需求。 - 完善错误日志:当前日志中
sqlsrv_errors()返回的是数组,直接拼接会显示为Array,改为序列化后写入日志:
这样能获取具体的错误信息,比如网络中断、数据类型转换问题。$errors = sqlsrv_errors(); $queryError = 'SQLSRV_ERROR: ' . json_encode($errors) . "\n$sql\n"; - 修正CAST长度:查询中
CAST(L2.FieldText AS varchar)未指定长度,SQL Server默认varchar长度为30,会导致长文本截断,改为CAST(L2.FieldText AS varchar(MAX)),其他CAST字段同理。 - 分批拉取数据:如果结果集过大,一次性拉取容易超时或内存溢出,改为按日期分批次查询,比如每次拉取1天的数据,循环处理。
- 检查cron环境配置:cron运行时的PHP环境变量可能与命令行不同,在cron脚本开头添加
php -i > /var/tmp/php_cron_env.log,对比命令行php -i的输出,确认sqlsrv相关配置一致。
内容的提问来源于stack exchange,提问作者Spencer Ke
相关产品推荐
相关产品推荐

