问题
Here is a minimal repro example.
Database:
CREATE TABLE temp (x int IDENTITY(1, 1), y int);
Code (using VBA and 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
Expected result: The Debug.Print
line outputs an integer to the debug window.
Actual result: The Debug.Print
line outputs Null
to the debug window.
Notes:
- As soon as I remove the lines marked as
[X]
, the code works as expected (the last inserted identity value is written to the debug window). - This is a minimal repro example: I know that server-side cursors are "evil". I know that in this particular case adding
SELECT SCOPE_IDENTITY()
to the INSERT batch is the correct way to get the newly inserted ID. This is just a minimal example to reproduce the issue with the least code possible. I stumbled upon this issue while modifying legacy code. - Tested with both SQL Server Native Client 11.0 and the "classic" MDAC SQL Server ODBC driver. Tested with SQL Server 2005 and 2012. Doesn't make a difference.
My question: Is this behavior by design, or did I stumble upon an SQL Server bug? If the former, where is it documented?
EDIT: Comparing the two options (with and without [X]) with an SQL Server Profiler trace, there is one significant difference: When the [X] lines are included, the connection is apparently dropped and reopened (Audit Logout
- Audit Login
) between cmd.Execute
and rs2.Open
. I guess that explains why @@IDENTITY
no longer works.
That leaves the following question: Why does ADO (or the SQLNCLI11 driver?) close and reopen the connection in one case but not in the other case? And where is this documented?
回答1:
MARS is a nicer alternative to the default behavior which does actually allow multiple recordsets.
What happens is:
SELECT 1
acts as the active recordset for the connection & remains open- When you then execute the
insert
the provider knows it has an active recordset and tries to be helpful by creating a new connection to execute the statement without interfering with anything - This ephemeral connection executes the
insert
then tidies up by perfoming a logout - destroying state associated with it select @@identity
again uses an ephemeral connection where@@identity
for the previous statement is out of scope, henceNULL
.
回答2:
I noticed you are doing two selects on the same connection. Have you tried enabling "Multiple Active Result Sets" by adding "MultipleActiveResultSets=True" to your connection string?
Enabling Multiple Active Result Sets on MSDN
来源:https://stackoverflow.com/questions/48499514/server-side-forward-only-cursor-breaks-identity