Skip to content

fix: MSSQL connection left in busy state after insert#2169

Closed
seladb wants to merge 6 commits intotortoise:developfrom
seladb:fix-mssql-bulk-create
Closed

fix: MSSQL connection left in busy state after insert#2169
seladb wants to merge 6 commits intotortoise:developfrom
seladb:fix-mssql-bulk-create

Conversation

@seladb
Copy link
Copy Markdown
Contributor

@seladb seladb commented Apr 5, 2026

Description

We recently see a lot of these errors in MSSQL CI which relies on pyodbc:

[HY000] [Microsoft][ODBC Driver 18 for SQL Server]Connection is busy with results for another command (0) (SQLExecDirectW)

bulk_create routes through execute_many, which uses cursor.executemany(). By default, MSSQL sends a "rows affected" message after each statement executed by executemany. These messages accumulate as pending result sets on the connection, and since they are never consumed, the connection is left in a busy state when returned to the pool, causing the next operation to fail.

The fix is to override execute_many in MSSQLClient to replace cursor.executemany() with an explicit loop of individual cursor.execute() calls, each prefixed with SET NOCOUNT ON. This suppresses the row-count messages at the source, preventing them from accumulating on the connection.

NOTE: There is potentially a small performance cost from the extra Python overhead, but the inserted data will be identical.

Motivation and Context

Fix failing CI in MSSQL.

How Has This Been Tested?

Run MSSQL in CI and make sure the error doesn't show up.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added the changelog accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 5, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing seladb:fix-mssql-bulk-create (1a91ae3) with develop (e1eb507)

Open in CodSpeed

@seladb seladb marked this pull request as ready for review April 5, 2026 08:29
@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented Apr 5, 2026

@abondar I think this should fix the issue that causes MSSQL CI to fail

@abondar
Copy link
Copy Markdown
Member

abondar commented Apr 5, 2026

Did you check that issueing "SET NOCOUNT ON" before execute many doesn't work? Is it limited only to individual inserts?
Also - we probably need to change "ODBCTransactionWrapper" too, as it overrides execute_many too

@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented Apr 5, 2026

Did you check that issueing "SET NOCOUNT ON" before execute many doesn't work? Is it limited only to individual inserts?

Yes, this was one of my attempts that didn't work: 339cebe

According to Claude:

The `SET NOCOUNT ON` prefix in `executemany` may not be reliable — some ODBC driver configurations don't support multi-statement strings in `executemany` the same way as execute. The safest fix for `MSSQLClient` is to override `execute_many` using a loop of individual execute calls, mirroring how `execute_insert` works

Also - we probably need to change "ODBCTransactionWrapper" too, as it overrides execute_many too

Yes, you're right. I'll address it shortly. Addressed in 1a91ae3

@abondar can you re-review?

@abondar
Copy link
Copy Markdown
Member

abondar commented Apr 5, 2026

Can you please try instead of your fix to both client and wrapper - to make such fix to transaction wrapper?

  @translate_exceptions                                                                                                                                                            
  async def execute_many(self, query: str, values: list) -> None: 
      async with self.acquire_connection() as connection:
          self.log.debug("%s: %s", query, values)                                                                                                                                  
          async with connection.cursor() as cursor:  # ← was missing
              await cursor.executemany(query, values)  

There is a chance that all problems we are having are just because transactional wrapper doesn't have proper cursor closing, and as tests are running in heavy transaction enviroment - tests are flaking because of it

@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented Apr 6, 2026

Can you please try instead of your fix to both client and wrapper - to make such fix to transaction wrapper?

  @translate_exceptions                                                                                                                                                            
  async def execute_many(self, query: str, values: list) -> None: 
      async with self.acquire_connection() as connection:
          self.log.debug("%s: %s", query, values)                                                                                                                                  
          async with connection.cursor() as cursor:  # ← was missing
              await cursor.executemany(query, values)  

There is a chance that all problems we are having are just because transactional wrapper doesn't have proper cursor closing, and as tests are running in heavy transaction enviroment - tests are flaking because of it

@abondar I think you're right! I opened this PR and it seems to be working: #2171
I think we can close this PR and merge the other one?

@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented Apr 8, 2026

We implemented a better solution in #2171

@seladb seladb closed this Apr 8, 2026
@seladb seladb deleted the fix-mssql-bulk-create branch April 8, 2026 03:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants