jdbc
12 TopicsLessons Learned #559: Understanding query timeouts in Azure SQL Database
In a recent support case, an application using Hibernate and the Microsoft JDBC Driver for SQL Server reported "The query has timed out". The application team had timestamps for the failures, but the SQL evidence reviewed did not show matching query cancellations. This raised a question that developers and DBAs frequently ask: If the exception contains SQLServerException, does that mean the database engine generated the timeout? The important distinction is where the timeout is reported and enforced, versus what caused the operation to exceed that limit. A command timeout is controlled by the client. Its underlying cause can still be a slow query, blocking, a communication delay, or another issue along the client and database path. In this article, we will explain that distinction, what to capture when it happens, and when increasing the command timeout can be appropriate. The examples use invented values and generic statements; no customer identifiers or application SQL are included. First identify which timeout occurred An application request can wait at several different stages. Before changing a setting, identify the stage that failed. Stage What to check Acquiring a connection from the pool Pool capacity, connection lifetime, and the pool acquisition timeout Establishing a database connection Connection or login timeout, endpoint, authentication, and handshake evidence Executing a SQL command Effective JDBC query timeout or SqlClient CommandTimeout, query execution, and blocking Communicating with the server Driver socket settings and evidence of interrupted or delayed communication Processing the application request ORM processing and the application's request or transaction deadline Changing a connection timeout does not increase the query timeout. Similarly, an HTTP request deadline is not the same setting as a database command timeout. A failure while obtaining a pooled connection should not automatically be investigated as a query that timed out in SQL. The Microsoft JDBC timeout documentation distinguishes these settings. JDBC queryTimeout and cancelQueryTimeout use seconds; socketTimeout uses milliseconds. Understand what the exception tells us The useful part of the exception chain in this case was: org.hibernate.QueryTimeoutException Caused by: java.sql.SQLTimeoutException: The query has timed out. Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The query has timed out. ... TDSCommand.checkForInterrupt(...) The stack places the observed exception in the client/JDBC execution and response handling path, propagated through Hibernate. SQLServerException is a driver exception class. Its presence alone does not establish that SQL Server generated a corresponding engine error. This is a client reported query timeout. That describes the observed error, not the root cause. If blocking prevents a command from completing before the client's limit, the client raises the timeout, while the delay is explained by database activity. Developers and DBAs need to investigate the same attempt together. What queryTimeout actually controls The Microsoft JDBC Driver uses queryTimeout to limit its wait for a server response after sending an execute command. When that timeout expires, the documented behavior is to cancel the command. cancelQueryTimeout controls the subsequent wait for acknowledgment of cancellation before the driver closes the connection. These settings do not define an exact stopwatch for the entire business request. Pool acquisition, connection establishment, cancellation handling, retries, and application processing can have different deadlines. The caller's total elapsed time can therefore differ from the configured query timeout. For JDBC, setQueryTimeout sets the timeout on a statement in seconds. In .NET, the corresponding setting is SqlCommand.CommandTimeout. Its documented semantics include network reads during command execution or result processing; it is not a limit on all user code that processes those results. The default is also provider dependent. SqlClient documents a 30 second default. Do not assume the same default for JDBC or for statements created by an ORM. Check the effective value used by the failing operation. This client command timeout is not an Azure SQL Database engine configuration setting. The same distinction applies when the application connects to Azure SQL Managed Instance or SQL Server on an Azure VM. How a command timeout becomes an Attention The application configures the driver's deadline; it does not send a "CommandTimeout" SQL error to the engine. The diagram shows the cancellation path for a request already sent through the Microsoft JDBC Driver. If the command completes within the limit, no timeout driven Attention is needed. Figure 1. The application owns the deadline. The driver enforces it and requests cancellation; the engine handles the SQL request. The values are illustrative, not recommended settings. For an already submitted request, Attention is a TDS cancellation message. SQL Server marks the request for cancellation and honors it at yield points, as described in the Attention and error 3617 guidance. The timer expiring and the engine stopping are not necessarily simultaneous. The driver consumes the remaining response until it receives the cancellation acknowledgment. The TDS cancellation state describes this response cleanup. A query timeout normally leaves the connection open after that exchange; it does not immediately cut off all communication. If no acknowledgment arrives and a configured cancelQueryTimeout expires, the Microsoft JDBC Driver closes the connection. A socket timeout or transport failure can also close it. Closing the socket alone does not establish the SQL operation's outcome. Other providers can have different cancellation and connection handling details. What we could conclude from the case We reviewed the reported failure windows, the available SQL activity, and the query identified by the application team. Some client initiated aborts were recorded, but we could not correlate them with the reported failures. The resource metrics reviewed did not establish resource pressure as the cause either. The supported conclusion was that the client reported a query timeout, while the SQL evidence reviewed did not identify a matching cancellation or establish the root cause. Further correlation was required. The reported statement was a SELECT. The transaction and write examples later in this article explain additional design considerations; they are not findings of failed writes or abandoned transactions in this case. Statistics maintenance had also been performed, but that action did not establish why the client timed out. A fast maximum or average in the available query statistics does not prove that the failed attempt was fast. Confirm the query, parameter types, destination database and replica, execution context, and time window. Successful executions and retries may appear alongside the attempt that failed. Developers should capture each failed attempt The message alone is not enough. Record the failure as an individual attempt that a DBA can correlate with database evidence. Evidence to record Why it matters UTC start and error timestamps and elapsed time Establish the failure window and observed duration Request correlation identifier and attempt number Separate the original call from retries Full exception chain and available SQLState or error code Identify the provider and phase reporting the failure Driver and ORM versions and effective timeout with its unit Establish which settings actually applied Parameterized SQL template and parameter types Identify the statement without publishing customer data Protected destination and connection identifiers Correlate with the correct database, replica, and session Measure pool acquisition, execution, result reading, and subsequent application processing separately. A request duration alone cannot tell us where the time was spent. If the ordinary logs are insufficient, collect a targeted JDBC driver trace during the same window. Keep credentials and personal data out of diagnostic examples. Retain the necessary correlation details in controlled diagnostic records and anonymize them before sharing publicly. DBAs should correlate cancellations with execution evidence On the database side, use Extended Events to capture sqlserver.attention together with relevant batch or RPC start and completion events. Correlate the timestamps and session or connection information with the application's failed attempt. The query timeout troubleshooting guidance describes this approach. An Attention indicates a client cancellation signal, but it does not identify why the client canceled. A user pressing Cancel can also produce it. Compare the associated execution duration with the application's effective timeout and logs; do not interpret the Attention event's own duration as the query's total runtime. Query Store adds historical context. In sys.query_store_runtime_stats, execution_type = 3 means client initiated aborted execution. It is not an exclusive count of query timeouts. Query Store statistics are aggregated. Select intervals overlapping the incident window, aggregate rows for the same plan, execution type, and interval, and examine both aborted and regular executions. Filtering only on last_execution_time can miss relevant activity within an interval. A query hash identifies a query family, not an individual request. For additional examples, see Lesson Learned 1 on capturing a TSQL command timeout, Lesson Learned 202 on reproducing and finding a command timeout, and Lesson Learned 525 on tracking timeouts beyond Query Store with Extended Events. For Azure SQL Database, use database scoped Extended Events. Check capture coverage, filters, retention, and possible event loss before interpreting missing events. Do not expect every command timeout to appear in the SQL error log. Also check transaction cleanup. Canceling a statement does not guarantee that the entire explicit transaction has been rolled back. The application's error handling must account for the resulting transaction state, as explained in the Azure SQL blocking guidance. When increasing the command timeout makes sense In Lesson Learned 253 and Lesson Learned 372, I shared examples that increase the execution timeout across retry attempts, including a PowerShell example. That technique changes how long the client is prepared to wait. It does not make the SQL statement execute faster. A bounded increase may be appropriate when the workload legitimately needs more time, or as a temporary mitigation while investigating. It can also help a controlled diagnostic execution complete so its behavior can be examined. If the operation normally completes quickly but suddenly exceeds the limit, investigate blocking, plan changes, resource waits, and communication before treating a larger timeout as the permanent solution. Verify the change against representative load and the application's response time requirements. The following examples show where the setting belongs. They assume an already open connection and run a generic statement with an intentional delay. Use them only in a test environment. For Java with the Microsoft JDBC Driver: try (java.sql.Statement statement = connection.createStatement()) { statement.setQueryTimeout(20); // seconds statement.execute("WAITFOR DELAY '00:00:12'; SELECT 1;"); } For .NET with Microsoft.Data.SqlClient: using var command = connection.CreateCommand(); command.CommandTimeout = 20; // seconds command.CommandText = "WAITFOR DELAY '00:00:12'; SELECT 1;"; using var reader = command.ExecuteReader(); With a 5 second command timeout, the deliberate 12 second delay should produce a timeout. With 20 seconds, the operation should have time to complete under normal test conditions. The delay has not become shorter; only the client's permitted wait has changed. This demonstrates the mechanism, not a fix for the original case. For each test, record the UTC start and end, effective timeout, elapsed time, exception, and attempt identifier. Capture Attention and the related execution events at the same time. Compare the two runs: the application setting changed, while the SQL statement and database configuration remained the same. Use the captured events to verify the expected behavior rather than assuming exact timestamps or a fixed event count. When an ORM creates the statement, apply the equivalent setting through the supported ORM configuration and verify its effective value and unit. Setting setQueryTimeout(0) or CommandTimeout = 0 removes that statement's timeout limit; it should not be a routine response to unexplained failures. If retries are used, record the timeout on every attempt, limit the attempts, allow a delay between them, and keep the total within the request's overall deadline. Transactions determine what must succeed or fail together A single UPDATE, INSERT, or DELETE already executes within a transaction in autocommit mode. Use an explicit transaction when several statements form one business operation and must be committed or rolled back together. The transaction documentation describes these transaction modes. For example, an application might UPDATE inventory, INSERT an order, and DELETE its reservation. If the first two statements finish and the third is canceled, the outcome depends on how the unit of work was managed. Transaction design What the application must account for Each statement in autocommit Earlier statements may already be committed. Canceling the later statement does not undo those earlier commits. One explicit transaction for all three statements Earlier changes remain part of that transaction until it is committed or rolled back. An Attention alone does not guarantee rollback of the whole transaction. COMMIT sent but its confirmation not received The client may not know whether the transaction committed. Verify the outcome before retrying the business operation. One transaction preserves the intended unit of work; it does not replace timeout and error handling. An open transaction left after cancellation can retain locks and delay other sessions. A poorly handled timeout can therefore contribute to further timeouts. Keep the transaction as short as the business rules allow. Do not include user interaction or unrelated external calls while holding it open. A command timeout also does not automatically limit the transaction's total duration: if the application sends several commands separately, each command has its own limit. Track the total operation deadline as well. A rollback inside TSQL CATCH may never run for Attention This is an important detail for developers: T-SQL TRY CATCH does not catch Attentions, including client interrupt requests. Placing ROLLBACK only in a stored procedure's CATCH block is therefore insufficient protection against a client cancellation. The application or ORM that owns the transaction must handle the failed call and resolve that transaction. Attempt rollback using its transaction API when the connection remains usable; log cleanup failures separately and do not continue using an unresolved transaction. A rollback attempt is not proof of a completed rollback. Follow the provider's cleanup rules before returning the connection to normal pool use. SET XACT_ABORT ON controls transaction abortion for applicable runtime errors. Microsoft's Attention and transaction guidance also describes it as a way to control rollback when a cancellation occurs. Set it deliberately for the SQL session or batch and validate the behavior with the actual driver, ORM, and transaction ownership model. It does not change CommandTimeout, and it cannot undo a transaction that already committed. In a disposable test environment, repeat the cancellation experiment inside a transaction after a small data modification. Compare XACT_ABORT OFF and ON. After cancellation acknowledgment, inspect transaction state on the same usable connection and ensure cleanup. If cancellation closes the connection, record that outcome instead of claiming to have queried its former session. This test is separate from the original SELECT investigation. A timeout does not establish whether a write committed If a connection fails after COMMIT was processed but before its acknowledgment reaches the application, the client can receive an error even though the changes were committed. Retrying an INSERT or increment blindly can then duplicate a business operation. Microsoft's connection resiliency documentation describes this commit failure and idempotency issue. Use a stable operation identifier that is reused across retries. Record it atomically with the business changes and enforce uniqueness in the database, so a repeated attempt can identify the prior operation rather than apply it again. If the original transaction is still in progress, resolve or wait for its outcome; an absent record in a single check is not always conclusive. The transaction provides atomicity for the changes. An operation identifier and deliberate retry handling help the application manage an uncertain result. Together, they make the timeout response part of the business operation's design.Lessons Learned #554: I Have Used BCP for Years — But How Does It Actually Work?
BCP is one of those SQL Server tools that many of us have been using for years. It is simple, reliable, and extremely fast when we need to move large amounts of data. We execute it, and millions of rows can be loaded into SQL Server or Azure SQL Database. But after using BCP many times, I had a question that stayed in my mind: Why is BCP so fast? And that question led me to another one: What does BCP actually send to SQL Server? Is it simply executing thousands or millions of INSERT statements behind the scenes? And finally: Could I create my own BCP-like application in C++, .NET, Java, or another programming language and adapt it to my own requirements? That curiosity is what motivated this article. The answer is quite interesting. BCP is not simply a faster way to execute INSERT statements. It uses a different model for moving data. A Very Simple BCP Example A typical import might look like this: bcp MyDatabase.dbo.Customers in customers.csv -S myserver.database.windows.net -d MyDatabase -c -t"," -T The bcp utility bulk copies data between SQL Server and a data file in a user-specified format. Microsoft also exposes bulk-copy functionality programmatically through SQL Server drivers. At a very high level, we normally think of the process as: File -> BCP -> SQL Server. But there is much more happening in between. My First Question: Is BCP Sending INSERT Statements? Imagine that we need to load one million rows. A traditional application could repeatedly execute something like: INSERT INTO dbo.Customers ( CustomerId, CustomerName, Amount ) VALUES ( @CustomerId, @CustomerName, @Amount ); Even when the connection is reused and the statement is parameterized, we are still performing a very large number of individual database operations. BCP works differently. And this is probably the most important concept in this article: BCP is not a faster way to send millions of INSERT statements. It avoids sending those INSERT statements in the first place. Instead, BCP establishes a bulk data stream with SQL Server. Going One Level Lower: TDS SQL Server clients communicate with the database engine using TDS — Tabular Data Stream. TDS carries SQL requests, result sets, metadata, errors, authentication information, bulk data, and other messages between the client and SQL Server. For bulk loading, TDS defines a dedicated bulk-load stream. At a simplified level, it looks like this: Conceptually, SQL Server first receives metadata describing the incoming row shape, followed by the row stream and a completion token. This is a completely different model from repeatedly submitting: INSERT INSERT INSERT INSERT INSERT SQL Server is told what the incoming data looks like, and then rows are streamed continuously through the connection. There Is an INSERT BULK Operation This was one of the details I found most interesting. When we execute bcp.exe, we do not manually write an INSERT BULK statement. However, the bulk-load protocol requires the client to identify the destination and incoming structure before sending the actual row stream. A simplified sequence looks like this: The important point is that the rows themselves are not represented as complete SQL statements. They are part of a structured bulk stream. What Is COLMETADATA? Suppose our destination table is: CREATE TABLE dbo.Customers ( CustomerId int, CustomerName varchar(100), Amount decimal(10,2) ); Before SQL Server can interpret the incoming row data, it needs information about the shape and types of the columns. Conceptually, the metadata describes something similar to: Column 1 INT Column 2 VARCHAR Length = 100 Column 3 DECIMAL Precision = 10 Scale = 2 Then the row stream follows: ROW 1001 Juan 42.50 ROW 1002 Pedro 18.75 ROW 1003 Jose 91.00 The real TDS representation is binary and structured, not human-readable like this, but the mental model is useful:BCP sends structured row data through the bulk protocol instead of generating SQL text for every record. From a CSV File to SQL Server Imagine the source file contains: 1001,Juan,42.50 1002,Pedro,18.75 1003,Jose,91.00 and we execute: bcp MyDatabase.dbo.Customers in customers.csv -S myserver -c -t"," -T Because we are using character mode, the source contains character representations. BCP reads the file, recognizes the fields according to the specified format, and feeds the values into the bulk-copy path. For example, the character value "1001" ultimately has to become an INT, and "42.50" has to become a DECIMAL(10,2). So BCP is fast, but it is not magic. Data still has to be interpreted and converted into the destination SQL Server data types. What About Memory? This was another thing I wanted to understand. Suppose I need to import a 100-GB file. Does BCP need 100 GB of memory? No. A better mental model is a streaming pipeline. The complete file does not need to reside in memory at the same time. Buffers can be filled, transmitted, reused, filled again, and transmitted again. The amount of data being transferred is not the same as the amount of memory required to transfer it. A Row Is Not Necessarily a Network Packet Sending one row through the BCP API does not necessarily mean that one network packet is immediately sent for that row. The bulk-copy implementation can accumulate rows while filling network packets. Conceptually: Row 1 ----\ Row 2 -----\ Row 3 ------> Network packet ---> SQL Server Row 4 -----/ Row 5 ----/ This is much more efficient than treating every row as an independent network operation. The command-line utility also exposes a packet-size option through -a packet_size. Larger is not automatically better; the optimal value depends on the environment, row size, driver, network, and workload. Then I Discovered the BCP API This was probably my favorite part of the investigation. BCP is not only a command-line utility. Microsoft exposes bulk-copy functionality programmatically through the ODBC bulk-copy API. Among the available functions are: bcp_init() bcp_bind() bcp_sendrow() bcp_batch() bcp_done() That means we can create our own bulk loader instead of launching bcp.exe. Understanding bcp_bind() Imagine that our application contains: int CustomerId; char CustomerName[100]; With the BCP API, those variables can be associated with destination columns. The application can then populate the same variables repeatedly and call bcp_sendrow() to feed another row into the bulk stream. CustomerId = 1; strcpy(CustomerName, "Customer A"); bcp_sendrow(hdbc); CustomerId = 2; strcpy(CustomerName, "Customer B"); bcp_sendrow(hdbc); The application is not building and submitting a new INSERT INTO... statement each time. It keeps populating values and feeding rows into the existing bulk-copy pipeline. The Source Does Not Even Need to Be a File Once we understand the API, another interesting possibility appears: the source can be anything our application knows how to read. For example, the source could be a REST API, a message stream, another database, generated data, or an in-memory structure. bcp.exe is therefore one implementation of the bulk-copy concept, not the only way to use it. Can I Create My Own BCP in Another Programming Language? Yes. The exact interface depends on the language and driver. C / C++ The ODBC BCP API gives low-level access using functions such as: bcp_init bcp_bind bcp_sendrow bcp_batch bcp_done .NET For .NET applications, Microsoft provides SqlBulkCopy : using var connection = new SqlConnection(connectionString); await connection.OpenAsync(); using var bulkCopy = new SqlBulkCopy(connection); bulkCopy.DestinationTableName = "dbo.Customers"; bulkCopy.BatchSize = 10000; await bulkCopy.WriteToServerAsync(reader); Java The Microsoft JDBC Driver provides SQLServerBulkCopy : SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(connection); bulkCopy.setDestinationTableName("dbo.Customers"); bulkCopy.writeToServer(resultSet); The important concept is that the same bulk-copy model can be consumed directly by applications. Batches: Another Piece of the Puzzle There is another important concept: transaction batching. Using the BCP API, an application can send multiple rows and then call: bcp_batch(); The command-line utility exposes the same general idea through -b batch_size. Batching can affect transaction duration, rollback scope, transaction-log behavior, error recovery, and throughput. Batch size is not the same thing as memory buffer size. If we specify -b 100000, we are primarily defining a transactional boundary. We are not saying that BCP must hold exactly 100,000 rows in RAM. What Happens When the Stream Arrives at SQL Server? Successfully delivering rows to SQL Server is only part of the operation. On the server side, a simplified path looks like this: TDS bulk stream-> Decode metadata and rows -> Bulk-load processing -> Storage Engine -> Data pages -> Transaction log SQL Server still needs to store those rows. Depending on the destination, that can involve data-type processing, page allocation, transaction logging, index maintenance, constraint validation, identity handling, triggers, locking, and dirty data pages. So Why Is BCP So Fast? After looking at the mechanism, I do not think there is one single magic optimization. BCP is fast because several efficiencies work together. Traditional row-by-row processing BCP / Bulk Copy Many individual SQL operations Continuous bulk stream Repeated execution overhead Bulk-oriented processing Potentially many client/server interactions Persistent streaming pipeline SQL statement representation Structured row representation Small network operations possible Rows can be packed efficiently into packets Frequent transaction boundaries possible Controlled batching Generic DML execution pattern Bulk-copy-oriented path For a few rows, the difference might not matter much. Multiply those savings by millions of rows, and the result becomes significant. References Microsoft Learn — bcp utility Microsoft Learn — Import and export bulk data using bcp Microsoft Open Specifications — TDS Bulk Load BCP Microsoft Learn — bcp_bind Microsoft Learn — bcp_sendrow Microsoft Learn — SqlBulkCopy Microsoft Learn — SQLServerBulkCopy430Views0likes0CommentsLesson Learned #503: Lessons Learned Using JDBC Logging with Microsoft JDBC Driver for SQL Server
A few days ago, our customer encountered connectivity issues while using HikariCP in their Java application. To troubleshoot the problem, we enabled detailed logging for the Microsoft JDBC Driver for SQL Server. This logging gave me a lot of information and details, following I would like to share my lessons learned here.1.9KViews2likes0CommentsIntermittent JDBC Connectivity issue - The driver could not establish a secure connection to SQL Server by using Secure Sockets Layer (SSL) encryption. Error: SQL Server returned an incomplete response. The connection has been closed
First published on MSDN on Nov 30, 2016 Symptoms:When connections are repeatedly made to SQL Server, one can observe about ~ 1% of connections having inability to connect.4.3KViews0likes0Comments