M
Mike Labosh
I'm not asking a question here, I'm sharing some insight I just discovered.
Consider the following [air] code. My actual code operates on a huge table
with over a million and a half records, so I have to loop in blocks of
100,000 at a time, and the "processing" that it does to the data is several
pages long, not a silly += 1 that I did in this post.
Dim cnstr As String = "connect to SQL Northwind"
Dim cn As New SqlConnection(cnstr)
Dim da As New SqlDataAdapter()
Dim dt As New DataTable()
Dim sel As String = "SELECT * FROM [Order Details]"
Dim upd As String = _
"UPDATE [Order Details] SET Quantity = @quantity " & _
"WHERE OrderID = @orderID AND ProductID = @productID"
With da 'Setup a data adapter to select and update
.SelectCommand = New SqlCommand(sel, cn)
.UpdateCommand = New SqlCommand(upd, cn)
With .UpdateCommand.Parameters
.Add("@quantity", SqlDbType.Int)
.Add("@orderID", SqlDbType.Int)
.Add("@productID", SqlDbType.Int)
End With
.Fill(dt) 'Get some rows
'Do some kind of processing
For Each dr As DataRow In dt.Rows
'I know this is the dumb way to update, but this
'isn't what I'm getting at.
dr("Quantity") = CType(dr("Quantity"), Integer) + 1
Next
'Update them.
.Update(dt)
End With
Now, all the microsoft documentation I have ever read about the data adapter
vaguely implies that it is really really fast, and I have always understood
that the data adapter did something like this inside the update method to
gain performance through fewer round trips to the server:
Dim bigFatCommand as String
For i = 0 to CountOfChanges
bigFatCommand &= vbCrLf & "update table set fld = 'asdf'...;"
Next i
cn.Open()
sqlCmd.CommandText = bigFatCommand
sqlCmd.ExecuteNonQuery
cn.Close()
What I have discovered today is that's not how it works at all. You can
verify this with the SQL Profilier. Startup a session in the profiler and
have it watch for RPC:Completed events. Then run code similar to that at
the top of this post. When you run the VB in the debugger, you can pause
the debugger and all activity with SQL Server in the Profiler stops.
Unpause it, and there is activity again. Pause it and the activity stops.
This demonstrates that the Data Adapter is actually sending one command for
each individual updated record, like this:
cn.Open()
For i = 0 to CountOfChanges
sqlCmd.CommandText = "update table set fld = ..."
sqlCmd.ExecuteNonQuery()
Next i
cn.Close()
Note that the Profiler demonstrates that the data adapter is not opening and
closing the connection inside the loop, but DANG it sure makes a lot of
calls across the network!
In conclusion, I have decided that gigantor batch processing needs a more
clever solution than the DataAdapter. I am going to use a StringBuilder to
build huge stacks of commands, and send just one big fat string of updates
in a single round trip.
--
Peace & happy computing,
Mike Labosh, MCSD
"It's 4:30 am. Do you know where your stack pointer is?"
Consider the following [air] code. My actual code operates on a huge table
with over a million and a half records, so I have to loop in blocks of
100,000 at a time, and the "processing" that it does to the data is several
pages long, not a silly += 1 that I did in this post.
Dim cnstr As String = "connect to SQL Northwind"
Dim cn As New SqlConnection(cnstr)
Dim da As New SqlDataAdapter()
Dim dt As New DataTable()
Dim sel As String = "SELECT * FROM [Order Details]"
Dim upd As String = _
"UPDATE [Order Details] SET Quantity = @quantity " & _
"WHERE OrderID = @orderID AND ProductID = @productID"
With da 'Setup a data adapter to select and update
.SelectCommand = New SqlCommand(sel, cn)
.UpdateCommand = New SqlCommand(upd, cn)
With .UpdateCommand.Parameters
.Add("@quantity", SqlDbType.Int)
.Add("@orderID", SqlDbType.Int)
.Add("@productID", SqlDbType.Int)
End With
.Fill(dt) 'Get some rows
'Do some kind of processing
For Each dr As DataRow In dt.Rows
'I know this is the dumb way to update, but this
'isn't what I'm getting at.
dr("Quantity") = CType(dr("Quantity"), Integer) + 1
Next
'Update them.
.Update(dt)
End With
Now, all the microsoft documentation I have ever read about the data adapter
vaguely implies that it is really really fast, and I have always understood
that the data adapter did something like this inside the update method to
gain performance through fewer round trips to the server:
Dim bigFatCommand as String
For i = 0 to CountOfChanges
bigFatCommand &= vbCrLf & "update table set fld = 'asdf'...;"
Next i
cn.Open()
sqlCmd.CommandText = bigFatCommand
sqlCmd.ExecuteNonQuery
cn.Close()
What I have discovered today is that's not how it works at all. You can
verify this with the SQL Profilier. Startup a session in the profiler and
have it watch for RPC:Completed events. Then run code similar to that at
the top of this post. When you run the VB in the debugger, you can pause
the debugger and all activity with SQL Server in the Profiler stops.
Unpause it, and there is activity again. Pause it and the activity stops.
This demonstrates that the Data Adapter is actually sending one command for
each individual updated record, like this:
cn.Open()
For i = 0 to CountOfChanges
sqlCmd.CommandText = "update table set fld = ..."
sqlCmd.ExecuteNonQuery()
Next i
cn.Close()
Note that the Profiler demonstrates that the data adapter is not opening and
closing the connection inside the loop, but DANG it sure makes a lot of
calls across the network!
In conclusion, I have decided that gigantor batch processing needs a more
clever solution than the DataAdapter. I am going to use a StringBuilder to
build huge stacks of commands, and send just one big fat string of updates
in a single round trip.
--
Peace & happy computing,
Mike Labosh, MCSD
"It's 4:30 am. Do you know where your stack pointer is?"