MARS, Transactions and SQL Error 3997, 3988 or 3983

MARS (multiple active result-sets) does allow multiple commands to be active on the same connection, however there are some limitations on allowing multiple transactions to be active. I received a number of questions around this lately. I'll try to address the common denominator scenario in these questions and how to work around some of the error messages you may receive if MARS does not like what you are trying to do with your transactions.

Scenario:

The scenario in question involves implementing a flow that reads a result-set from SQL Server in a loop and goes back into the database to update some records. The app executes the update(s) in a transaction.

Here is the simplified ado.net code: Note that I am leaving out the cleanup and some of the initialization for compactness and table t1 is defined as "id int primary key, c2 xml not null".

The code will error at runtime. The error message you get back will depend on the method you used to manage your transactions but it is likely going to be one of these;

SQL Error 3997 "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back."

SQL Error 3988 "New transaction is not allowed because there are other threads running in the session."

SQL Error 3983 "The operation failed because the session is not single threaded."

So… What now?

When thinking about transactions it is critical to understand that almost all statements in SQL Server run in a transaction without you having to implicitly or explicitly start one. Most obvious example is : 'delete from t1' will delete all records or none. There is nothing in between when SQL Server executes this statement. In the case above however, starting a new transaction on connection that already has an active atomic command executing is not allowed. Depending on the situation and the behavior you want you may be able to do one of the following;

#1 You can simply encapsulate all commands in a single transaction. Obviously in this case, you get one big transaction and that means you hold on to all your resources until the end of the entire transaction. That could mean more blocking in your application.

#2 alternatively, you can submit the inner command you want transacted as a single batch or pack the commands in a stored procedure with the transaction statements with the transaction statements in the batch or the procedure. SQL Server can allow this special case and the batch or the stored procedure can work in its own transaction. So the code looks like this;