sqlsrv_query 是否限制在一个查询中可以执行的语句数?
Does sqlsrv_query limit the number of statements that can be exectuted in one query?
我正在 PHP for
循环中生成 SQL insert
语句。
生成的 SQL 字符串是大量单独的 SQL 语句,如下所示:
INSERT INTO tbl VALUES(1,2,3);
INSERT INTO tbl VALUES(4,5,6);
INSERT INTO tbl VALUES(7,8,9);
等...
然后我执行:
$InsertResult = sqlsrv_query($conn, $InsertSQL);
问题是只有前 312 条语句被执行,而不是完整的 2082 行(只有 312 行被插入 table)。
当我将 $InsertSQL
变量输出到 JavaScript 控制台,然后在 SSMS 中手动执行它时,它完美地工作并插入了所有 2082 行。只有当我通过 sqlsrv_query
运行 $InsertSQL
变量时,它才不会完成。
我也没有收到任何错误,查询结果返回真,正如在这一行中测试的那样:
if(!$InsertResult) die('Problem with Insert query: ' . $InsertSQL);
当我搜索这个问题的解决方案时,我看到(尽管 PHP 手册站点中没有提到)sqlsrv_query
显然在 $SQL
上有一个字符串字符限制变量(大约 65k 个字符)。
在此处查看另一篇 Whosebug 文章:
length restriction for the sql variable on sqlsrv_query?
我认为这是问题所在,因此创建了一个较短版本的字符串(通过仅添加我实际想要导入的列值)。然而,这个短得多的版本仍然只插入前 312 行!所以现在看来这与最大字符串长度无关。事实上,如果是的话,我应该只得到大约 250 行(在 250 条语句之后我大约有 65k 个字符)。
我也可以单独执行每个 insert
语句,但这当然需要更长的时间。在我的测试中,这样做需要 90 秒左右,而 运行 在 SMSS 中手动组合语句只需要 40 秒左右。
请注意,我还查看了 SQL 服务器的批量插入,但是我无法将文件发送到安装了 SQL 服务器的计算机(SQL服务器和 Web 服务器位于不同的计算机上)。据我了解,这消除了这种可能性。
非常感谢任何帮助,因为我什至无法弄清楚是什么限制了我,更不用说修复它了,我不想一次只执行一行。
说明:
此驱动程序有一个已知的 issue,发布在 GitHub 上,关于执行大型 SQL 语句。提供的解决方案的一部分是以下解释:
似乎在执行大量 SQL 语句时,Microsoft SQL 服务器可能会在批处理中的所有语句执行完之前停止处理该批处理。处理批处理结果时,SQL 服务器用批处理创建的结果集填充连接的输出缓冲区。这些结果集必须由客户端应用程序处理。如果您正在执行具有多个结果集的大型批处理,SQL 服务器会填充该输出缓冲区,直到达到内部限制并且无法继续处理更多结果集。那时,控制 returns 到客户端。此行为是设计使然。
客户端应用程序应刷新所有待处理的结果集。一旦客户端使用了所有待处理的结果集,SQL 服务器就完成了批处理的执行。客户端应用程序可以调用 sqlsrv_next_result() 直到它 returns NULL。
所以,我不认为 SQL 语句长度有限制,只有 PHP 字符串变量的大小($InsertSQL
在你的情况下)是限于允许的最大 PHP 内存限制。这种意外行为的实际原因是,使用 SET NOCOUNT OFF
(默认情况下)和大量单个 INSERT
语句,SQL 服务器 returns作为结果集的受影响行的计数(例如 (1 row affected)
)。
解法:
我能够重现这个问题(使用 SQL Server 2012、PHP 7.1.12 和 PHP SQL Server 4.3.0+9904 的驱动程序) 并且您有以下选项来解决此问题:
- 使用
sqlsrv_next_result()
刷新未决结果集。
- 作为复杂 T-SQL 语句的第一行执行
SET NOCOUNT ON
以停止 SQL 服务器以 [=68=] 受影响行的计数作为结果集。
- 使用参数化语句使用
sqlsrv_prepare()\sqlsrv_execute()
Table:
CREATE TABLE MyTable (
Column1 int,
Column2 int,
Column3 int
)
一个复杂的语句(使用sqlsrv_query()
和sqlsrv_next_result()
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Statement with sqlsrv_query
$sql = "";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// Clean the buffer
while (sqlsrv_next_result($stmt) != null){};
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
一个复杂的语句(使用sqlsrv_query()
和SET NOCOUNT ON
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
// Statement with sqlsrv_query
$sql = "SET NOCOUNT ON;";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
参数化语句(使用sqlsrv_prepare()
和sqlsrv_execute()
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
$sql = "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (?, ?, ?);";
$value1 = 0;
$value2 = 0;
$value3 = 0;
$params = array(&$value1, &$value2, &$value3);
$stmt = sqlsrv_prepare($con, $sql, $params);
if ($stmt === false ) {
echo "Error (sqlsrv_prepare): ".print_r(sqlsrv_errors(), true);
exit;
}
for ($i = 1; $i <= 1000; $i++) {
$value1 = $i;
$value2 = 0;
$value3 = 0;
$result = sqlsrv_execute($stmt);
if ($result === false) {
echo "Error (sqlsrv_execute): ".print_r(sqlsrv_errors(), true);
exit;
}
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
我正在 PHP for
循环中生成 SQL insert
语句。
生成的 SQL 字符串是大量单独的 SQL 语句,如下所示:
INSERT INTO tbl VALUES(1,2,3);
INSERT INTO tbl VALUES(4,5,6);
INSERT INTO tbl VALUES(7,8,9);
等...
然后我执行:
$InsertResult = sqlsrv_query($conn, $InsertSQL);
问题是只有前 312 条语句被执行,而不是完整的 2082 行(只有 312 行被插入 table)。
当我将 $InsertSQL
变量输出到 JavaScript 控制台,然后在 SSMS 中手动执行它时,它完美地工作并插入了所有 2082 行。只有当我通过 sqlsrv_query
运行 $InsertSQL
变量时,它才不会完成。
我也没有收到任何错误,查询结果返回真,正如在这一行中测试的那样:
if(!$InsertResult) die('Problem with Insert query: ' . $InsertSQL);
当我搜索这个问题的解决方案时,我看到(尽管 PHP 手册站点中没有提到)sqlsrv_query
显然在 $SQL
上有一个字符串字符限制变量(大约 65k 个字符)。
在此处查看另一篇 Whosebug 文章: length restriction for the sql variable on sqlsrv_query?
我认为这是问题所在,因此创建了一个较短版本的字符串(通过仅添加我实际想要导入的列值)。然而,这个短得多的版本仍然只插入前 312 行!所以现在看来这与最大字符串长度无关。事实上,如果是的话,我应该只得到大约 250 行(在 250 条语句之后我大约有 65k 个字符)。
我也可以单独执行每个 insert
语句,但这当然需要更长的时间。在我的测试中,这样做需要 90 秒左右,而 运行 在 SMSS 中手动组合语句只需要 40 秒左右。
请注意,我还查看了 SQL 服务器的批量插入,但是我无法将文件发送到安装了 SQL 服务器的计算机(SQL服务器和 Web 服务器位于不同的计算机上)。据我了解,这消除了这种可能性。
非常感谢任何帮助,因为我什至无法弄清楚是什么限制了我,更不用说修复它了,我不想一次只执行一行。
说明:
此驱动程序有一个已知的 issue,发布在 GitHub 上,关于执行大型 SQL 语句。提供的解决方案的一部分是以下解释:
似乎在执行大量 SQL 语句时,Microsoft SQL 服务器可能会在批处理中的所有语句执行完之前停止处理该批处理。处理批处理结果时,SQL 服务器用批处理创建的结果集填充连接的输出缓冲区。这些结果集必须由客户端应用程序处理。如果您正在执行具有多个结果集的大型批处理,SQL 服务器会填充该输出缓冲区,直到达到内部限制并且无法继续处理更多结果集。那时,控制 returns 到客户端。此行为是设计使然。 客户端应用程序应刷新所有待处理的结果集。一旦客户端使用了所有待处理的结果集,SQL 服务器就完成了批处理的执行。客户端应用程序可以调用 sqlsrv_next_result() 直到它 returns NULL。
所以,我不认为 SQL 语句长度有限制,只有 PHP 字符串变量的大小($InsertSQL
在你的情况下)是限于允许的最大 PHP 内存限制。这种意外行为的实际原因是,使用 SET NOCOUNT OFF
(默认情况下)和大量单个 INSERT
语句,SQL 服务器 returns作为结果集的受影响行的计数(例如 (1 row affected)
)。
解法:
我能够重现这个问题(使用 SQL Server 2012、PHP 7.1.12 和 PHP SQL Server 4.3.0+9904 的驱动程序) 并且您有以下选项来解决此问题:
- 使用
sqlsrv_next_result()
刷新未决结果集。 - 作为复杂 T-SQL 语句的第一行执行
SET NOCOUNT ON
以停止 SQL 服务器以 [=68=] 受影响行的计数作为结果集。 - 使用参数化语句使用
sqlsrv_prepare()\sqlsrv_execute()
Table:
CREATE TABLE MyTable (
Column1 int,
Column2 int,
Column3 int
)
一个复杂的语句(使用sqlsrv_query()
和sqlsrv_next_result()
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Statement with sqlsrv_query
$sql = "";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// Clean the buffer
while (sqlsrv_next_result($stmt) != null){};
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
一个复杂的语句(使用sqlsrv_query()
和SET NOCOUNT ON
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
// Statement with sqlsrv_query
$sql = "SET NOCOUNT ON;";
for ($i = 1; $i <= 1000; $i++) {
$sql .= "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (".$i.", 0, 0);";
}
$stmt = sqlsrv_query($con, $sql);
if ($stmt === false) {
echo "Error (sqlsrv_query): ".print_r(sqlsrv_errors(), true);
exit;
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>
参数化语句(使用sqlsrv_prepare()
和sqlsrv_execute()
):
<?php
// Connection info
$server = 'server\instance';
$database = 'database';
$username = 'username';
$password = 'password';
$cinfo = array(
"Database" => $database,
"UID" => $username,
"PWD" => $password
);
// Connection
$con = sqlsrv_connect($server, $cinfo);
if ($con === false) {
echo "Error (sqlsrv_connect): ".print_r(sqlsrv_errors(), true);
exit;
}
$sql = "INSERT INTO MyTable (Column1, Column2, Column3) VALUES (?, ?, ?);";
$value1 = 0;
$value2 = 0;
$value3 = 0;
$params = array(&$value1, &$value2, &$value3);
$stmt = sqlsrv_prepare($con, $sql, $params);
if ($stmt === false ) {
echo "Error (sqlsrv_prepare): ".print_r(sqlsrv_errors(), true);
exit;
}
for ($i = 1; $i <= 1000; $i++) {
$value1 = $i;
$value2 = 0;
$value3 = 0;
$result = sqlsrv_execute($stmt);
if ($result === false) {
echo "Error (sqlsrv_execute): ".print_r(sqlsrv_errors(), true);
exit;
}
}
// End
sqlsrv_free_stmt($stmt);
sqlsrv_close($con);
echo "OK";
?>