【发布时间】:2018-01-29 10:53:17
【问题描述】:
这是一个最小的复制示例。
数据库:
CREATE TABLE temp (x int IDENTITY(1, 1), y int);
代码(使用 VBA 和 ADO):
Public Sub repro()
Dim cn As New Connection
Dim rs1 As New Recordset
Dim cmd As New Command
Dim rs2 As New Recordset
cn.Open "Provider=SQLNCLI11;Server=myServer;Database=myDatabase;Trusted_Connection=Yes"
rs1.Open "SELECT 1", cn, adOpenForwardOnly ' [X] '
cmd.ActiveConnection = cn
cmd.CommandText = "INSERT INTO temp (y) VALUES (1) "
cmd.Execute
rs2.Open "SELECT @@IDENTITY", cn, adOpenStatic
Debug.Print rs2(0).value
rs2.Close
rs1.Close ' [X] '
cn.Close
End Sub
预期结果:Debug.Print 行向调试窗口输出一个整数。
实际结果:Debug.Print 行将Null 输出到调试窗口。
注意事项:
- 一旦我删除标记为
[X]的行,代码就会按预期工作(最后插入的标识值被写入调试窗口)。 -
这是一个最小的重现示例:我知道服务器端游标是“邪恶的”。我知道在这种特殊情况下,将
SELECT SCOPE_IDENTITY()添加到 INSERT 批处理是获取新插入的 ID 的正确方法。这只是用尽可能少的代码重现问题的最小示例。我在修改遗留代码时偶然发现了这个问题。 - 使用 SQL Server Native Client 11.0 和“经典”MDAC SQL Server ODBC 驱动程序进行测试。使用 SQL Server 2005 和 2012 进行测试。没有区别。
我的问题:这种行为是设计使然,还是我偶然发现了 SQL Server 错误?如果是前者,它记录在哪里?
编辑:将两个选项(带和不带 [X])与 SQL Server Profiler 跟踪进行比较,有一个显着的区别:当包含 [X] 行时,连接显然被删除并重新打开(Audit Logout - Audit Login) 在cmd.Execute 和rs2.Open 之间。我想这解释了为什么 @@IDENTITY 不再有效。
这留下了以下问题:为什么 ADO(或 SQLNCLI11 驱动程序?)在一种情况下关闭并重新打开连接,而在另一种情况下却没有?这是在哪里记录的?
【问题讨论】:
-
可能两者都不是。光标需要一堆不可见的设置(如果启用分析,您可以看到其中的一部分)。作为该隐形设置的一部分,我可以生动地想象
@@IDENTITY会因为第一个行集游标仍然处于活动状态并且需要在幕后进行一些操作而感到无聊。尝试连接分析器,看看是否有任何类似性质的东西通过网络发送。 -
@JeroenMostert:好主意。我做了一个分析器跟踪并将结果添加到我的问题中。
-
ADO 关闭和打开连接还首先解释了此代码如何工作——因为没有 MARS,连接上只能有一个活动结果集。您的代码,如果它按字面意思工作,则需要两个。如果
rs2已打开,则从rs1读取将/应该失败,并且在尝试打开rs2之前关闭rs1应该与消除rs1具有相同的效果。尝试检查rs1.State;我希望它会因rs2打开而隐式关闭。这可能是 ADO 故障保护,因为糟糕的清理代码很常见;它弄乱了你的身份是无意的。 -
请注意,如果这 是 你的问题,
SCOPE_IDENTITY()也不会解决任何问题——唯一会为rs2显式创建一个新连接所以有没有来自另一个可能在cn上打开的记录集的干扰。 (什么也可能有效,但我不确定,在命令中合并一个输出参数并使用类似INSERT ... ; SELECT @outparm = SCOPE_IDENTITY()的东西。这避免了行集,但在行集处于活动状态时确实会发回一些东西,而且我'我不确定这是否合法。) -
@JeroenMostert:有道理,感谢您的分析。有趣的是,
rs1.State(官方)仍然是adStateOpen,直到rs1.Close。关于您的最后一条评论:使用命令INSERT ...; SELECT SCOPE_IDENTITY()打开记录集可以正常工作(但需要 rs2.NextRecordset)。
标签: sql-server vba oledb ado identity