sqlserver
68 TopicsQuery plan regressions got you down? Here's how can Automatic Plan Correction turns it around.
Automatic Plan Correction (APC) is one of the features that I find myself talking about quite a bit with customers, support engineers, and the broader SQL community. It is part of the Automatic Tuning family and has been quietly doing its job since SQL Server 2017, detecting query plan regressions and automatically forcing a previously known good plan to restore performance. But one of the questions that comes up often is: how does APC actually decide whether a regression has occurred? And how confident is it in that decision? With SQL Server 2022 CU4 and continuing into SQL Server 2025, we have made significant improvements to the statistical model that APC uses for regression detection. This blog post walks through what changed, why it matters, and how you can take advantage of it. ⚙️ Two Ways to Detect a Regression APC supports two statistical models for determining whether a query plan has regressed. Both analyze execution statistics captured by Query Store (such as CPU time) to compare the performance of a current plan against a previously known good plan. The original model: sigma-based comparison The original regression detection model uses a sigma-based comparison (commonly associated with the "three-sigma rule"). It computes whether the difference in average CPU time between the current plan and the last known good plan exceeds a threshold of 3 standard deviations. This approach works well when execution times follow a normal distribution with consistent variance. However, we found some limitations with this approach over time: High variance can mask regressions. If CPU times vary widely for a given plan (which is common in real workloads), the standard deviation becomes large. A regression where the mean CPU time doubles might still fall within the 3-sigma band, causing APC to miss it. It assumes equal variance. The model treats the variance of both the current and baseline plan populations as comparable. When they differ significantly (for example, the regressed plan has highly variable execution times while the good plan was stable), the comparison loses precision. It requires more executions. The model needs at least 15 executions of both the current plan and the baseline plan before it makes a determination. 🆕 The newer model: Welch's t-test The newer regression detection model uses the Welch's t-test, a well-established statistical hypothesis test designed specifically for comparing two populations that may have unequal variances and unequal sample sizes. If you are not familiar with the Welch's t-test, you can think of it as a more robust way to answer the question: "are these two sets of numbers actually different, or is the difference just noise?" Key improvements over the original model: Better accuracy with high-variance workloads. The Welch's t-test explicitly accounts for different variances between the current plan and the baseline plan. It won't be fooled by a plan that sometimes runs fast and sometimes runs very slow. It can still detect that the distribution has shifted unfavorably. Faster reaction time. APC can begin evaluating a plan after as few as 3 executions of the new plan, compared to 15 with the original model. When the performance difference is statistically significant, APC can detect and correct a regression much sooner. Adaptive sample sizing. If the initial sample of 3 executions produces an "uncertain" result, APC doesn't give up. It progressively increases the number of executions required before rechecking, until enough execution statistics have been gathered to make a confident determination. Multiple checks before final determination. The Welch's t-test model performs more than one check over the lifecycle of a potential regression. It re-evaluates as more data arrives, reducing the chance of both false positives and false negatives. With the Welch's t-test model, we have significantly improved both the accuracy of regression detection and the reaction time that APC takes with correcting a regression. 🧭 Enabling the Welch's t-test model The Welch's t-test model is the default regression detection model in: Azure SQL Database, SQL Database in Microsoft Fabric, Azure SQL Managed Instance (enabled by default, no action required) SQL Server 2025 (enabled by default, no action required) For SQL Server 2022, enable it with trace flag 12618 (available starting with CU4): -- Enable globally (startup trace flag) DBCC TRACEON (12618, -1); -- Or add as a startup parameter: -T12618 The original sigma-based model remains as a fallback and is used when trace flag 12618 is not enabled on SQL Server 2022 CU4+. As always, we strongly recommend applying the latest Cumulative Updates for SQL Server. 🧪 Handling long-running and timed-out queries Both regression detection models share a common bias that I want to call out: APC checks are triggered after a certain number of query executions complete. This means APC inherently evaluates the fastest-completing executions first and may miss regressions where the symptom is queries running for minutes or timing out entirely. Let me paint a picture of what this looks like. Imagine a situation where the overall CPU utilization on a server goes from an average of 10% to 100% unexpectedly and you find out that there had been a plan change for a particular query. The extended events may show that the query had executed a few times quickly, say 15 executions that completed in milliseconds. However, during your analysis you notice that there were a lot of other query executions that were running longer, maybe for a few minutes. Some of those longer running queries completed successfully, but others were timing out. By the time those slow executions finish and their statistics reach Query Store, APC has already made its regression decision based on the fast executions and concluded there was no regression. Trace flag 12656 addresses this by enabling a time-based delayed recheck. When enabled, APC schedules an additional check 5 minutes after a plan change is first detected. This delayed check re-evaluates the query using execution statistics that now include the slower and timed-out executions, providing a more complete picture. -- Enable globally DBCC TRACEON (12656, -1); Like trace flag 12618, trace flag 12656 is also available starting with SQL Server 2022 CU4. Per-query timed recheck with sp_configure_automatic_tuning While trace flag 12656 applies this recheck globally, you can also enable it on a per-query basis using the sp_configure_automatic_tuning stored procedure with the FORCE_LAST_GOOD_PLAN_EXTENDED_CHECK option. This is especially useful in Azure SQL Database, SQL Database in Microsoft Fabric, and Azure SQL Managed Instance where the global trace flag is not available. It provides the same 5-minute delayed recheck that trace flag 12656 enables, but scoped to only the queries you choose: -- Enable timed recheck for a specific query EXEC sp_configure_automatic_tuning @option = 'FORCE_LAST_GOOD_PLAN_EXTENDED_CHECK', @option_value = 'ON', @type = 'QUERY', @type_value = <query_id>; -- Disable timed recheck for a specific query EXEC sp_configure_automatic_tuning @option = 'FORCE_LAST_GOOD_PLAN_EXTENDED_CHECK', @option_value = 'OFF', @type = 'QUERY', @type_value = <query_id>; This allows you to target specific queries that are known to have long-running or timeout-prone execution patterns without imposing the overhead of timed rechecks on every query in the database. The following table summarizes the scope differences: Method Scope Where available Trace flag 12656 All queries on the instance (global) SQL Server 2022 CU4 and later versions (including SQL Server 2025 and later versions) sp_configure_automatic_tuning FORCE_LAST_GOOD_PLAN_EXTENDED_CHECK Individual queries (per-query) SQL Server 2022 CU4 and later versions, SQL Server 2025, Azure SQL Database, SQL Database in Microsoft Fabric, and Azure SQL Managed Instance 🔍 Monitoring APC decisions The illustration below can be used as a mental map of all of the phases that the automatic plan correction feature takes which can be monitored. sys.dm_db_tuning_recommendations The sys.dm_db_tuning_recommendations DMV can be used to see any actions that APC has taken if it is enabled and will also be populated if the feature is disabled, showing the action that it would have taken if it were enabled. The result set is in JSON format, so a query similar to the one below can be used to parse the output: SELECT JSON_VALUE([state], '$.currentValue') AS [state], script = JSON_VALUE(details, '$.implementationDetails.script'), planForceDetails.* FROM sys.dm_db_tuning_recommendations CROSS APPLY OPENJSON(Details, '$.planForceDetails') WITH ( [query_id] int '$.queryId', regressedPlanId int '$.regressedPlanId', recommendedPlanId int '$.recommendedPlanId', regressedPlanErrorCount int, recommendedPlanErrorCount int, regressedPlanExecutionCount int, regressedPlanCpuTimeAverage float, recommendedPlanExecutionCount int, recommendedPlanCpuTimeAverage float ) AS planForceDetails; ⚠️ Note: Data in sys.dm_db_tuning_recommendations is not persisted across Database Engine restarts. 📋 Extended events for deeper analysis It may also be a good idea to capture some of the APC extended events for additional insight and analysis. For general APC observability, the key events are: Extended event When it fires automatic_tuning_plan_regression_detection_check_completed APC finishes evaluating a plan for regression. Shows whether a regression was detected and corrected. automatic_tuning_plan_regression_verification_check_completed APC finishes validating a previously forced plan. automatic_tuning_check_abandoned APC abandons a check (for example, only one plan exists). automatic_tuning_recommendation_expired APC backs off from a forced plan that isn't helping. automatic_tuning_diagnostics Periodic health summary with check counts, detection counts, and correction counts. For analyzing the Welch's t-test model specifically, there is a dedicated event (not currently available for Azure SQL Database and SQL Database in Microsoft Fabric): Extended event When it fires automatic_tuning_wtest_details Fires during the Welch's t-test evaluation with detailed statistical information about the test. ⚠️ Note: The automatic_tuning_wtest_details event can be chattier than the other events. It is one way to validate the effects of having trace flag 12618 enabled if you are interested in comparing results in a workload between the old and new regression detection models, but consider the overhead before enabling it in production. Additionally, this event is not available in database-scoped sessions in Azure SQL Database for SQL Database in Microsoft Fabric. Here is a sample script to create a session that captures the key events: CREATE EVENT SESSION [APC_Welch_Monitor] ON SERVER ADD EVENT qds.automatic_tuning_plan_regression_detection_check_completed ( ACTION (sqlserver.database_id, sqlserver.database_name) ), ADD EVENT qds.automatic_tuning_plan_regression_verification_check_completed ( ACTION (sqlserver.database_id, sqlserver.database_name) ), ADD EVENT qds.automatic_tuning_wtest_details ( ACTION (sqlserver.database_id, sqlserver.database_name) ), ADD EVENT qds.automatic_tuning_diagnostics ( ACTION (sqlserver.database_id, sqlserver.database_name) ) ADD TARGET package0.event_file ( SET filename = N'APC_Welch_Monitor.xel', max_file_size = 50, max_rollover_files = 5 ) WITH ( MAX_MEMORY = 4096 KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY = 5 SECONDS, STARTUP_STATE = OFF ); GO ALTER EVENT SESSION [APC_Welch_Monitor] ON SERVER STATE = START; 📊 Summary The table below provides a quick comparison between the two regression detection models. If you are on SQL Server 2022, I would encourage you to try trace flag 12618 and see how it improves APC's responsiveness for your workloads. If you are on SQL Server 2025 or Azure SQL Database SQL Database in Microsoft Fabric, or Azure SQL Managed Instance, the Welch's t-test model is already active by default. Regression detection models Aspect Original model (sigma-based) Welch's t-test model Statistical method 3-sigma threshold on mean CPU difference Welch's t-test (unequal variance t-test) Handles unequal variance No (assumes similar variance) Yes (explicitly models different variances) Min executions to evaluate 15 (current plan) As few as 3 (current plan), adapts exponentially Adaptive sample sizing No Yes (progressively increases required executions on uncertain results) Multiple checks Single check Multiple re-evaluations as data accumulates Default in Azure SQL DB, SQL Database in Microsoft Fabric, Azure SQL Managed Instance No (fallback) Yes Enable on SQL Server Default on SQL Server 2022 Default on SQL Server 2025; trace flag 12618 required for SQL Server 2022 CU4+ As we continue to make Automatic Plan Correction more reliable and more robust, I hope you find this blog helpful. We are always looking to hear from the community, so please continue to share your feedback and experiences at https://aka.ms/sqlfeedback. 📚 Learn More Automatic tuning sys.dm_db_tuning_recommendations Query Store overview82Views2likes0CommentsAnnouncing Microsoft.Data.SqlClient 7.0.1
We are pleased to announce the release of Microsoft.Data.SqlClient 7.0.1, the first servicing update to the 7.0 line. This patch addresses several compatibility fixes reported by the community after the 7.0.0 GA release. Install or update from NuGet: dotnet add package Microsoft.Data.SqlClient --version 7.0.1 Full release notes: 7.0.1 Release Notes What's in this release SqlBulkCopy fixes for SQL Server 2016 and Azure Synapse Two separate issues affected SqlBulkCopy on older or specialized SQL Server engines: SQL Server 2016 compatibility -- SqlBulkCopy operations failed with Invalid column name 'graph_type' because the column metadata query referenced a column introduced in SQL Server 2017. The query now uses dynamic SQL so the graph_type reference is not compiled on versions that lack the column. (#3714) Azure Synapse dedicated SQL pools -- The column-list query used a variable-assignment pattern that Synapse does not support. The driver now detects Synapse (engine edition 6) and uses STRING_AGG instead, while preserving the variable-assignment fallback for SQL Server 2016 compatibility. (#4149) Vector column type metadata SqlDataReader.GetFieldType() and GetProviderSpecificFieldType() previously returned typeof(byte[]) for vector float32 columns instead of typeof(SqlVector<float>). These methods now follow the same type-determination logic as GetValue(), returning the correct vector type. (#4104) Missing System.Data.Common dependency on .NET Framework The inbox System.Data.Common assembly on .NET Framework predates APIs such as IDbColumnSchemaGenerator. Without the NuGet package dependency, consumers hit CS0012 compilation errors when using these types through Microsoft.Data.SqlClient. The package now explicitly depends on System.Data.Common v4.3.0 for .NET Framework targets. (#4063) User Agent TDS extension enabled unconditionally The Switch.Microsoft.Data.SqlClient.EnableUserAgent AppContext switch has been removed. The driver now always sends User Agent information during login. (#4124) Type forwards for extracted Azure authentication types Type forwards have been added from the core Microsoft.Data.SqlClient assembly to public types that moved to the Microsoft.Data.SqlClient.Extensions.Abstractions package in 7.0.0: SqlAuthenticationMethod, SqlAuthenticationParameters, SqlAuthenticationProvider, SqlAuthenticationProviderException, and SqlAuthenticationToken. This ensures binary compatibility for assemblies compiled against earlier versions of Microsoft.Data.SqlClient. (#4067) Community contributions Both of the SqlBulkCopy fixes in this release were driven by community contributor edwardneal: SQL Server 2016 fix -- Edward authored the original PR (#3719) that rewrote the column metadata query to use dynamic SQL, preventing the graph_type column reference from being compiled on servers that lack it. The fix was recreated on an internal branch (#4092) to enable CI pipeline testing against SQL Server 2016 and 2017. Azure Synapse fix -- Edward identified and fixed (#4176) the incompatibility with Azure Synapse dedicated SQL pools, where the variable-assignment concatenation pattern used to build the column list is not supported. His fix detects Synapse via SERVERPROPERTY('EngineEdition') and switches to STRING_AGG, while preserving the variable-assignment fallback for SQL Server 2016. He manually validated the fix against SQL Server 2016, SQL Server 2025, and an Azure Synapse dedicated SQL pool. We are grateful for Edward's continued contributions to SqlClient. Community involvement like this directly improves the experience for all SqlClient users. Getting started If you are new to Microsoft.Data.SqlClient, check out the documentation. For users of System.Data.SqlClient, see the porting cheat sheet. If you encounter any issues, please report them on the GitHub repository.251Views0likes0Commentsmssql-python 1.6: Unblocking Your Threads
The last two mssql-python releases shipped big features: Bulk Copy in 1.4 for high-throughput data loading, and Apache Arrow in 1.5 for zero-copy analytics. Version 1.6 is about what happens next: you take those features into production, scale up your thread pool, and find out where the driver was quietly holding you back. This release unblocks your threads during connection setup, fixes crashes and incorrect results in common cursor patterns, and hardens security for passwords with special characters and log file paths. pip install --upgrade mssql-python Your threads can run while connections are opening If you're running mssql-python behind Flask, FastAPI, Django, or any WSGI/ASGI server with thread-based workers, this one matters. Opening a database connection is slow. There's DNS resolution, a TCP handshake, TLS negotiation, and SQL Server authentication. In previous versions, every other Python thread in your process was frozen while that happened, because the driver held the Global Interpreter Lock (GIL) during the entire operation. One thread opening a connection meant no other thread could serve requests, process data, or do anything at all. Version 1.6 releases the GIL during connect and disconnect. Your other threads keep running while the network round-trip completes. If you have a multi-threaded web server handling concurrent requests, this removes a serialization bottleneck you may not have realized you had. The connection pool was also reworked to stay safe under this change. Previously, the pool held an internal lock while calling connect, which would have created a deadlock now that connect releases the GIL. The pool now reserves a slot first, connects outside the lock, and rolls back the reservation if the connection fails. Decimal parameters work with setinputsizes If you use cursor.setinputsizes() to declare parameter types for performance-sensitive batch inserts, you may have hit a crash when specifying SQL_DECIMAL or SQL_NUMERIC. This is fixed. Decimal values now bind correctly whether you're using execute() or executemany(): cursor.setinputsizes([ (mssql_python.SQL_WVARCHAR, 100, 0), (mssql_python.SQL_INTEGER, 0, 0), (mssql_python.SQL_DECIMAL, 18, 2), ]) cursor.executemany( "INSERT INTO Products (Name, CategoryID, Price) VALUES (?, ?, ?)", [ ("Widget", 1, Decimal("19.99")), ("Gadget", 2, Decimal("29.99")), ], ) Iterating catalog results with fetchone() If you've used cursor.tables(), cursor.columns(), or other catalog methods and tried to walk the results with fetchone(), you may have gotten incorrect data. Row tracking was broken for catalog result sets. This now works the way you'd expect: cursor.tables(tableType="TABLE") while True: row = cursor.fetchone() if row is None: break print(row.table_name) This also applies to primaryKeys(), foreignKeys(), statistics(), procedures(), and getTypeInfo(). Reusing prepared statements without reset If you call cursor.execute() with reset_cursor=False to reuse a prepared statement across calls, this no longer raises an "Invalid cursor state" error. Passwords with special characters stay masked in logs If your SQL Server password contains semicolons, braces, or other ODBC-special characters (e.g., PWD={Top;Secret}), previous versions could accidentally leak part of it in sanitized log output. The password masking logic has been rewritten to correctly handle all ODBC connection string formats. If the connection string can't be parsed at all, the entire string is now redacted rather than partially exposed. The logging system also now rejects log file paths that attempt directory traversal, preventing setup_logging(log_file_path="../../somewhere/else.log") from writing outside the intended directory. Better type checker support for executemany If your type checker flagged executemany() when you passed dictionaries as parameter rows, that warning is gone. The type annotations now correctly accept Mapping types, matching the DB API 2.0 spec for named parameters. Get started pip install --upgrade mssql-python For questions or issues, file them on GitHub or email mssql-python@microsoft.com.175Views0likes0CommentsIntroducing Pacemaker HA Agent v2 for SQL Server on Linux (In Preview)
We are excited to introduce the next generation of high availability (HA) Agent for SQL Server on Linux: Pacemaker HA Agent v2. This release is a major step forward, designed to reduce planned and unplanned failover times, compared to the previous agent, based on internal engineering improvements. Why Pacemaker Is Required for SQL Server HA on Linux For users new to Linux, it’s important to understand how high availability works on this platform. On Windows Server, Always On availability groups use an underlying Windows Server Failover Cluster (WSFC) to: Monitor node health Detect failures Orchestrate automatic failovers Always On availability groups on Linux rely on an external cluster orchestrator for health monitoring and failover coordination, with Pacemaker HA Agent being one of the cluster orchestrators, responsible for: Monitoring node and application health Coordinating failover decisions Helping mitigate split‑brain scenarios through improved write‑lease evaluation Managing resources such as availability groups and listeners The Pacemaker HA Agent is the integration layer that allows Pacemaker to understand SQL Server health and manage availability groups safely. Evolution of the SQL Server Pacemaker HA Agent With SQL Server 2025 CU3 and later, Pacemaker HA Agent v2 is available in preview for Red Hat Enterprise Linux and Ubuntu through the mssql-server-ha package. Pacemaker HA agent v2 uses a service‑based architecture. The agent runs as a dedicated system service named mssql-pcsag, which is responsible for handling SQL Server–specific high availability operations and communication with Pacemaker. You can manage mssql-pcsag service by using standard system service controls to start, restart, status and stop this service by using the operating system's service manager (for example, systemctl). # Start the mssql-pcsag service sudo systemctl start mssql-pcsag # Restart the mssql-pcsag service sudo systemctl restart mssql-pcsag # Check the status of the mssql-pcsag service sudo systemctl status mssql-pcsag # Stop the mssql-pcsag service sudo systemctl stop mssql-pcsag Limitations of Pacemaker HA Agent v1 While the original agent enabled SQL Server HA on Linux, customers running production workloads encountered several challenges: Failover delays of 30 seconds to 2 minutes during planned or unplanned events Limited health detection, missing conditions such as I/O stalls and memory pressure Rigid failover behavior, unlike the flexible policies available on Windows (WSFC) Incomplete write‑lease handling, requiring custom logic No support for TLS1.3 for Pacemaker and SQL Server communications How Pacemaker HA Agent v2 Addresses These Gaps Pacemaker HA Agent v2 is a ground‑up improvement, designed to improve the reliability characteristics of SQL Server HA on Linux. 1. Faster & Smarter Failover Decisions The new agent introduces a service‑based health monitoring architecture, moving beyond basic polling. This allows SQL Server to report detailed diagnostic signals - improving detection speed and helping reduce failover delays in supported configurations. 2. Flexible Automatic Failover Policies inspired by the WSFC health model Pacemaker HA Agent v2 supports failure‑condition levels (1–5) and health‑check timeout model aligned with those available in Always On availability groups on Windows. This provides: Fine‑grained control over failover sensitivity, allowing administrators to tune when failover should occur. Improved detection of internal SQL Server conditions, such as memory pressure, internal deadlocks, orphaned spinlocks, and other engine‑level failures. Failover decisions are now driven by detailed diagnostics from sp_server_diagnostics, enabling faster and more accurate response to unhealthy states and providing enhanced resiliency capabilities for SQL Server AG on Linux. You can configure the failure condition level and health check timeout using the following commands: -- Setting failure condition level ALTER AVAILABILITY GROUP pacemakerag SET (FAILURE_CONDITION_LEVEL = 2); -- Setting health check timeout ALTER AVAILABILITY GROUP pacemakerag SET (HEALTH_CHECK_TIMEOUT = 60000); After applying the configuration, validate the setting using the sys.availability_groups DMV: 3. Robust Write Lease Validity Handling To prevent split‑brain scenarios, SQL Server on Linux uses an external write‑lease mechanism. In v1, lease information was not fully integrated into failover decisions. In v2, the agent actively evaluates the write-lease validity, before initiating transitions. This supports controlled role changes and improved data consistency behavior during failover events, depending on cluster configuration. 4. TLS 1.3 Support Pacemaker HA agent v2 includes design updates to support TLS 1.3–based communication for health checks and failover operations, when TLS 1.3 is enabled. Supported Versions & Distributions Pacemaker HA Agent v2 supports: SQL Server 2025 CU3 or later RHEL 9 or later Ubuntu 22.04 or higher. Preview upgrade & migration guidance for non-production environments New or existing non-prod deployments running SQL Server 2025 (17.x) can migrate from Pacemaker HA Agent v1 to v2 using following approach: Drop the existing AG resource sudo pcs resource delete <NameForAGResource> This temporarily pauses AG synchronization but does not delete the availability group (AG). After the resource is recreated, Pacemaker resumes management and AG synchronization automatically. Create a new AG resource using the v2 agent (ocf:mssql:agv2) sudo pcs resource create <NameForAGResource> ocf:mssql:agv2 ag_name=<AGName> meta failure-timeout=30s promotable notify=true Validate cluster health sudo pcs status Resume normal operations References Create and Configure an Availability Group for SQL Server on Linux - SQL Server | Microsoft Learn Thank You, Engineering: David Liao Attinder Pal Singh315Views2likes3CommentsCreating a Contained Availability Group and Enabling Database Creation via CAG Listener
A Contained Availability Group (CAG) is designed to simplify high availability and disaster recovery by encapsulating system databases (master, msdb) within the availability group itself. This means that logins, jobs, credentials, and other metadata are automatically replicated across replicas, eliminating the need for manual synchronization and reducing operational complexity. Starting with SQL Server 2025 CU1, you can create or restore databases directly through the CAG listener - without connecting to the physical instance - by enabling a session context key. Why Contained AGs Matter Self-contained HA unit: Each CAG has its own copy of master and msdb, making it independent of the physical SQL instance. Simplified failover: When the AG fails over, all associated metadata moves with it, ensuring applications continue to function without manual intervention. Improved automation: Supports scenarios where direct access to the SQL instance is restricted, enabling operations through the AG listener. Enhanced security: Reduces exposure by limiting instance-level access; operations can be scoped to the AG context. Streamlined management: Eliminates the need for login and job replication scripts across replicas. Step 1: Prepare the Database for Availability Group This example uses SQL Server on Linux; however, the steps are identical for SQL Server on Windows. In this walkthrough, an existing database named CAGDB is added to a Contained Availability Group (CAG). Before adding the database to the CAG, verify that it is configured for FULL recovery mode and perform a full database backup. ALTER DATABASE CAGDB SET RECOVERY FULL; GO BACKUP DATABASE CAGDB TO DISK = N'/var/opt/mssql/backups/CAGDB.bak' WITH INIT, COMPRESSION; GO Note: On Linux, ensure that the backup target directory exists and is owned by the mssql user. sudo mkdir -p /var/opt/mssql/backups sudo chown mssql:mssql /var/opt/mssql/backups Step 2: Create the Contained Availability Group On your Linux SQL Server nodes, run: CREATE AVAILABILITY GROUP [CAGDemo] WITH ( CLUSTER_TYPE = EXTERNAL, CONTAINED ) FOR DATABASE [CAGDB] REPLICA ON N'node1' WITH ( ENDPOINT_URL = N'tcp://node1:5022', AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, FAILOVER_MODE = EXTERNAL, SEEDING_MODE = AUTOMATIC ), N'node2' WITH ( ENDPOINT_URL = N'tcp://node2:5022', AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, FAILOVER_MODE = EXTERNAL, SEEDING_MODE = AUTOMATIC ); GO #Connect to secondary replicas and join the AG: ALTER AVAILABILITY GROUP [CAGDemo] JOIN WITH (CLUSTER_TYPE = EXTERNAL); ALTER AVAILABILITY GROUP [CAGDemo] GRANT CREATE ANY DATABASE; Step 3: Configure Listener and Connect Create a listener and connect using SSMS or sqlcmd: ALTER AVAILABILITY GROUP [CAGDemo] ADD LISTENER N'CAGDemoListener' ( WITH IP ( (N'*.*.*.*', N'255.255.255.0') ), PORT = 1453 ); GO Step 4: Connect to CAGDemoListener and attempt Database Creation (Failure) When connected through the listener: CREATE DATABASE TestCAGDB; Result:\ Msg 262, Level 14, State 1: CREATE DATABASE is not allowed in this context.\ This happens because database creation is blocked by default in a contained AG session. Step 5: Enable Database Creation in CAG Session or Instance master EXEC sp_set_session_context key = N'allow_cag_create_db', @value = 1; This enables database creation for your current session. Step 6: Retry Database Creation (Success) CREATE DATABASE TestCAGDB; Result: Database created successfully within the contained AG context. Users with dbcreator role in the CAG context can perform this action. Step 7: Backup the database. ALTER DATABASE TestCAGDB SET RECOVERY FULL; BACKUP DATABASE TestCAGDB TO DISK = N'/var/opt/mssql/data/backups/TestCAGDB.bak'; Step 8: Add database to the CAG ALTER AVAILABILITY GROUP [CAGDemo] ADD DATABASE [TestCAGDB]; Optional: To automate Steps 4 - 8, you can create a stored procedure (for example: [dbo].[sp_cag_create_db]) and execute it while connected through the Contained Availability Group (CAG) listener context. CREATE OR ALTER PROCEDURE [dbo].[sp_cag_create_db] @database_name sysname, @createdb_sql NVARCHAR(MAX) = NULL AS BEGIN SET NOCOUNT ON DECLARE @fIsContainedAGSession int EXECUTE @fIsContainedAGSession = sys.sp_MSIsContainedAGSession if (@fIsContainedAGSession = 1) BEGIN DECLARE @SQL NVARCHAR(MAX); EXEC sp_set_session_context = N'allow_cag_create_db', @value = 1; IF @createdb_sql IS NULL SET @SQL = 'CREATE DATABASE ' + QUOTENAME(@database_name); ELSE SET @SQL = @createdb_sql; PRINT @SQL EXEC sp_executesql @SQL; SET @SQL = 'ALTER DATABASE ' + QUOTENAME(@database_name) + ' SET RECOVERY FULL'; PRINT @SQL EXEC sp_executesql @SQL; SET @SQL = 'BACKUP DATABASE ' + QUOTENAME(@database_name) + ' TO DISK = N''NUL'''; PRINT @SQL EXEC sp_executesql @SQL; DECLARE _Name sysname; set _Name = (SELECT name FROM sys.availability_groups ags INNER JOIN sys.dm_exec_sessions des ON ags.group_id = des.contained_availability_group_id WHERE @@SPID = des.session_id); SET @SQL = 'use master; ALTER AVAILABILITY GROUP ' + QUOTENAME(@AG_Name) + ' add DATABASE ' + QUOTENAME (@database_name) PRINT @SQL EXEC sp_executesql @SQL; EXEC sp_set_session_context key = N'allow_cag_create_db', @value = 0; END ELSE BEGIN RAISERROR('This can only be used with a contained availability group connection.', 16, 1); END END GO At a high level, this procedure simplifies database onboarding into a CAG by orchestrating the following actions as a single workflow: Creating (or restoring) the database Setting the recovery model to FULL Taking the initial backup required for availability group seeding Adding the database to the target Contained Availability Group Example: Create a database Execute the following command to create NewTestDB and add it to the target CAG. EXEC [dbo].sp_cag_create_db @database_name = [NewTestDB]; Example: Restore a database The stored procedure also supports an optional parameter, @createdb_sql, which allows you to provide a custom SQL statement to create or restore a database (for example, restoring from a backup). Once the database backup file exists and is accessible to SQL Server, you can use this parameter to perform the operation. Important: When @createdb_sql is used, the procedure executes that SQL statement directly. Ensure the SQL statement is from a secured and trusted source. DECLARE @restoreSql NVARCHAR(MAX); SET @restoreSql = N' RESTORE DATABASE AdventureWorks2022 FROM DISK = ''/var/opt/mssql/backups/AdventureWorks2022.bak'' WITH MOVE ''AdventureWorks2022'' TO ''/var/opt/mssql/data/AdventureWorks2022.mdf'', MOVE ''AdventureWorks2022_log'' TO ''/var/opt/mssql/data/AdventureWorks2022_log.ldf'', RECOVERY; '; EXEC dbo.sp_cag_create_db @database_name = N'AdventureWorks2022', @createdb_sql = @restoreSql; Conclusion Contained Availability Groups represent a major step forward in simplifying high availability for SQL Server. By encapsulating system databases within the AG context, they eliminate the complexity of synchronizing logins, jobs, and credentials across replicas. With the new capability in SQL Server 2025 CU1 to create or restore databases directly through the AG listener using sp_set_session_context, organizations can streamline automation and reduce operational overhead. References What Is a Contained Availability Group Deploy a Pacemaker Cluster for SQL Server on Linux749Views1like0Commentsmssql-python 1.5: Apache Arrow, sql_variant, and Native UUIDs
We're excited to announce the release of mssql-python 1.5.0, the latest version of Microsoft's official Python driver for SQL Server, Azure SQL Database, and SQL databases in Fabric. This release delivers Apache Arrow fetch support for high-performance data workflows, first-class sql_variant and native UUID support, and a collection of important bug fixes. pip install --upgrade mssql-python Apache Arrow fetch support If you're working with pandas, Polars, DuckDB, or any Arrow-native data framework, this release changes how you get data out of SQL Server. The new Arrow fetch API returns query results as native Apache Arrow structures, using the Arrow C Data Interface for zero-copy handoff directly from the C++ layer to Python. This is a significant performance improvement over the traditional fetchall() path, which converts every value through Python objects. With Arrow, columnar data stays in columnar format end-to-end, and your data framework can consume it without any intermediate copies. Three methods for different workflows cursor.arrow() fetches the entire result set as a PyArrow Table: import mssql_python conn = mssql_python.connect( "SERVER=myserver.database.windows.net;" "DATABASE=AdventureWorks;" "UID=myuser;PWD=mypassword;" "Encrypt=yes;" ) cursor = conn.cursor() cursor.execute("SELECT * FROM Sales.SalesOrderDetail") # Get the full result as a PyArrow Table table = cursor.arrow() # Convert directly to pandas - zero-copy where possible df = table.to_pandas() # Or to Polars - also zero-copy import polars as pl df = pl.from_arrow(table) cursor.arrow_batch() fetches a single RecordBatch of a specified size, useful when you want fine-grained control over memory: cursor.execute("SELECT * FROM Production.TransactionHistory") # Process in controlled chunks while True: batch = cursor.arrow_batch(batch_size=10000) if batch.num_rows == 0: break # Process each batch individually process(batch.to_pandas()) cursor.arrow_reader() returns a streaming RecordBatchReader, which integrates directly with frameworks that accept readers: cursor.execute("SELECT * FROM Production.TransactionHistory") reader = cursor.arrow_reader(batch_size=8192) # Write directly to Parquet with streaming - no need to load everything into memory import pyarrow.parquet as pq pq.write_table(reader.read_all(), "output.parquet") # Or iterate batches manually for batch in reader: process(batch) How it works under the hood The Arrow integration is built directly into the C++ pybind11 layer. When you call any Arrow fetch method, the driver: Allocates columnar Arrow buffers based on the result set schema Fetches rows from SQL Server in batches using bound column buffers Converts and packs values directly into the Arrow columnar format Exports the result via the Arrow C Data Interface as PyCapsule objects PyArrow imports the capsules with zero copy Every SQL Server type maps to the appropriate Arrow type: INT to int32, BIGINT to int64, DECIMAL(p,s) to decimal128(p,s), DATE to date32, TIME to time64[ns], DATETIME2 to timestamp[us], UNIQUEIDENTIFIER to large_string, VARBINARY to large_binary, and so on. LOB columns (large VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX), XML, UDTs) are handled transparently by falling back to row-by-row GetData fetching while still assembling the result into Arrow format. Community contribution The Arrow fetch support was contributed by @ffelixg. This is a substantial contribution spanning the C++ pybind layer, the Python cursor API, and comprehensive tests. Thank you, Felix Graßl, for an outstanding contribution that brings high-performance data workflows to mssql-python. sql_variant type support SQL Server's sql_variant type stores values of various data types in a single column. It's commonly used in metadata tables, configuration stores, and EAV (Entity-Attribute-Value) patterns. Version 1.5 adds full support for reading sql_variant values with automatic type resolution. The driver reads the inner type tag from the sql_variant wire format and returns the appropriate Python type: cursor.execute(""" CREATE TABLE #config ( key NVARCHAR(50) PRIMARY KEY, value SQL_VARIANT ) """) cursor.execute("INSERT INTO #config VALUES ('max_retries', CAST(5 AS INT))") cursor.execute("INSERT INTO #config VALUES ('timeout', CAST(30.5 AS FLOAT))") cursor.execute("INSERT INTO #config VALUES ('app_name', CAST('MyApp' AS NVARCHAR(50)))") cursor.execute("INSERT INTO #config VALUES ('start_date', CAST('2026-01-15' AS DATE))") cursor.execute("SELECT value FROM #config ORDER BY key") rows = cursor.fetchall() # Each value comes back as the correct Python type assert rows[0][0] == "MyApp" # str assert rows[1][0] == 5 # int assert rows[2][0] == date(2026, 1, 15) # datetime.date assert rows[3][0] == 30.5 # float All 23+ base types are supported, including int, float, Decimal, bool, str, date, time, datetime, bytes, uuid.UUID, and None. Native UUID support Previously, UNIQUEIDENTIFIER columns were returned as strings, requiring manual conversion to uuid.UUID. Version 1.5 changes the default: UUID columns now return native uuid.UUID objects. import uuid cursor.execute("SELECT NEWID() AS id") row = cursor.fetchone() # Native uuid.UUID object - no manual conversion needed assert isinstance(row[0], uuid.UUID) print(row[0]) # e.g., UUID('550e8400-e29b-41d4-a716-446655440000') UUID values also bind natively as input parameters: my_id = uuid.uuid4() cursor.execute("INSERT INTO Users (id, name) VALUES (?, ?)", my_id, "Alice") Migration compatibility If you're migrating from pyodbc and your code expects string UUIDs, you can opt out at three levels: # Module level - affects all connections mssql_python.native_uuid = False # Connection level - affects all cursors on this connection conn = mssql_python.connect(conn_str, native_uuid=False) When native_uuid=False, UUID columns return strings as before. Row class export The Row class is now publicly exported from the top-level mssql_python module. This makes it easy to use in type annotations and isinstance checks: from mssql_python import Row cursor.execute("SELECT 1 AS id, 'Alice' AS name") row = cursor.fetchone() assert isinstance(row, Row) print(row[0]) # 1 (index access) print(row.name) # "Alice" (attribute access) Bug fixes Qmark false positive fix The parameter style detection logic previously misidentified? characters inside SQL comments, string literals, bracketed identifiers, and double-quoted identifiers as qmark parameter placeholders. A new context-aware scanner correctly skips over these SQL quoting contexts: # These no longer trigger false qmark detection: cursor.execute("SELECT [is this ok?] FROM t") cursor.execute("SELECT 'what?' AS col") cursor.execute("SELECT /* why? */ 1") NULL VARBINARY parameter fix Fixed NULL parameter type mapping for VARBINARY columns, which previously could fail when passing None as a binary parameter. Bulkcopy auth fix Fixed stale authentication fields being retained in the bulk copy context after token acquisition. This could cause Entra ID-authenticated bulk copy operations to fail on subsequent calls. Explicit module exports Added explicit __all__ exports from the main library module to prevent import resolution issues in tools like mypy and IDE autocompletion. Credential cache fix Fixed the credential instance cache to correctly reuse and invalidate cached credential objects, preventing unnecessary re-authentication. datetime.time microseconds fix Fixed datetime.time values incorrectly having their microseconds component set to zero when fetched from TIME columns. The road to 1.5 Release Date Highlights 1.0.0 November 2025 GA release - DDBC architecture, Entra ID auth, connection pooling, DB API 2.0 compliance 1.1.0 December 2025 Parameter dictionaries, Connection.closed property, Copilot prompts 1.2.0 January 2026 Param-as-dict, non-ASCII path handling, fetchmany fixes 1.3.0 January 2026 Initial BCP implementation (internal), SQLFreeHandle segfault fix 1.4.0 February 2026 BCP public API, spatial types, Rust core upgrade, encoding & stability fixes 1.5.0 April 2026 Apache Arrow fetch, sql_variant, native UUIDs, qmark & auth fixes Get started today pip install --upgrade mssql-python Documentation: github.com/microsoft/mssql-python/wiki Release notes: github.com/microsoft/mssql-python/releases Roadmap: github.com/microsoft/mssql-python/blob/main/ROADMAP.md Report issues: github.com/microsoft/mssql-python/issues Contact: mssql-python@microsoft.com We'd love your feedback. Try the new Arrow fetch API with your data workflows, let us know how it performs, and file issues for anything you run into. This driver is built for the Python data community, and your input directly shapes what comes next.381Views1like0CommentsMicrosoft ODBC Driver 18.6.2 for SQL
What Is the Microsoft ODBC Driver for SQL? The Microsoft ODBC Driver for SQL provides native connectivity from Windows, Linux, and macOS applications to SQL Server, Azure SQL Database, Azure SQL Managed Instance, and Microsoft Fabric. It is the recommended driver for new application development using the ODBC API, and it supports , Always Encrypted, distributed transactions, and modern authentication methods including Microsoft Entra ID (formerly Azure Active Directory). Whether you're building high-throughput data pipelines, managing enterprise databases, or developing cloud-native applications on Microsoft Fabric, the ODBC driver is a foundational component of the SQL Server connectivity stack. What's New in 18.6.2 Improved Vector Parameter Handling for Prepared Statements Version 18.6.2 improves the handling of output and input/output vector parameters when using prepared statements. This enhancement benefits applications that rely on parameterized queries with array bindings — a common pattern in batch processing and high-performance data access layers. Microsoft Fabric Redirection Support (Up to 10 Redirections) The driver now allows up to 10 server redirections per connection attempt, up from previous limits. This change directly supports Microsoft Fabric redirection scenarios, where connections may be transparently routed through multiple endpoints before reaching the target workspace. If your applications connect to Fabric SQL endpoints, this update ensures more reliable connectivity in complex routing topologies. Alpine Linux Packaging Improvements Architecture detection and packaging have been improved for Alpine Linux environments, making it easier to deploy the driver in lightweight, container-based workloads that use Alpine as a base image. Bug Fixes This release addresses several important issues reported by the community and identified through internal testing: Parameter Array Processing SQL_ATTR_PARAMS_PROCESSED_PTR accuracy — Fixed an issue where the number of processed parameter sets was not reported correctly when executing parameter arrays. Applications that inspect SQL_ATTR_PARAMS_PROCESSED_PTR after batch execution will now see the correct count. SQL_PARAM_IGNORE handling — Fixed SQL_ATTR_PARAMS_PROCESSED_PTR and row counting when SQL_PARAM_IGNORE is used within parameter arrays, ensuring that ignored parameters are accounted for properly. Crash Fixes SQLNumResultCols segmentation fault — Resolved a segfault that occurred when calling SQLNumResultCols in describe-only scenarios where no parameter bindings are present. Table-valued parameter (TVP) NULL handling — Fixed a segmentation fault triggered by NULL values in TVP arguments. Applications passing TVPs with nullable columns should no longer experience unexpected crashes. bcp_bind Consecutive Field Terminators (Known Issue from 18.6.1) bcp_bind fix — Corrected bcp_bind to properly handle consecutive field terminators without misinterpreting them as empty fields. This resolves a known issue introduced in version 18.6.1, where consecutive terminators were incorrectly interpreted as NULL values instead of empty strings. If you deferred upgrading to 18.6.1 because of this issue, 18.6.2 is the recommended target version. Linux Packaging Debian EULA acceptance — Fixed Debian package installation to correctly honor EULA acceptance and complete successfully, eliminating a friction point for automated deployments. RPM side-by-side installation — Fixed RPM packaging rules to allow installing multiple driver versions side by side, which is important for environments that need to maintain backward compatibility or perform staged rollouts. Distributed Transactions XA recovery — Fixed XA recovery to compute transaction IDs correctly, avoiding scenarios where recoverable transactions could be missed during the recovery process. This is a critical fix for applications using distributed transactions with XA transaction managers. Upgrading from Older Versions If you are upgrading from a version prior to 18.6.1, you will also benefit from the features introduced in that release: Vector data type support — Native support for the vector data type (float32), enabling AI and machine learning scenarios directly through ODBC. ConcatNullYieldsNull property — Connection-level control over null concatenation behavior. New platform support — Azure Linux 3.0 ARM, Debian 13, Red Hat 10, and Ubuntu 25.10. Version 18.6.2 builds on these additions with the stability and correctness fixes described above. Download & Installation Windows Platform Download Link x64 Download x86 Download ARM64 Download Linux & macOS Installation packages for supported Linux distributions and macOS are available on Microsoft Learn: Download ODBC Driver for SQL Server (Linux/macOS) Documentation & Release Notes For the full list of changes, platform support details, and known issues, see the official release notes: Windows Release Notes Linux & macOS Release Notes Bug Fixes Get Started We encourage all users to upgrade to version 18.6.2.1 to take advantage of the fixes and improvements in this release — particularly if you are using parameter arrays, table-valued parameters, bcp operations, or connecting to Microsoft Fabric endpoints. As always, we welcome your feedback. If you encounter issues, please report them through the SQL Server feedback channel or open an issue on the Microsoft ODBC Driver GitHub repository. Happy coding!718Views0likes0CommentsTroubleshooting Azure SQL Data Sync Failure: SQL Error 8106 During Bulk Insert
Azure SQL Data Sync is widely used to maintain consistency across distributed databases in hub–member topologies. However, synchronization may occasionally fail due to schema mismatches between participating databases — even when everything appears correctly configured at first glance. In this post, we’ll walk through a real-world troubleshooting scenario involving a Data Sync failure caused by a schema inconsistency related to an IDENTITY column, and how it was mitigated. Sample Error: sync_7726d6cb22124c0f901192c434f49106bd618f8ab16343b2adc03250f8367ff4\3953fb7d-1dba-4656-8150-83153d5d019b.batch. See the inner exception for more details. Inner exception: Failed to execute the command 'BulkInsertCommand' for table 'schema.table_name'; the transaction was rolled back. Ensure that the command syntax is correct. Inner exception: SqlException ID: e19b3677-d67e-4c8e-bc49-13d3df61ad0e, Error Code: -2146232060 - SqlError Number:8106, Message: SQL error with code 8106 For more information, provide tracing ID ‘92e76130-f80a-4372-9a48-ec0ede8b0288’ to customer support." Scenario Overview A synchronization operation began failing for a specific table within an Azure SQL Data Sync group. The failure was observed during the sync process when applying changes using a batch file. The error surfaced as part of a failed BulkInsertCommand execution on a synced table, causing the transaction to roll back. Further investigation revealed the following SQL exception: SqlError Number: 8106 Table does not have the identity property. Cannot perform SET operation. Initial Troubleshooting Steps Before identifying the root cause, the following actions were taken: The affected table was removed from the sync group. A sync operation was triggered. The table was re-added to the sync group. Sync was triggered again. Despite performing these steps, the issue persisted with the same error. This indicated that the failure was not related to sync metadata or temporary configuration inconsistencies. Root Cause Analysis After reviewing the table definitions across the sync topology, it was discovered that: The synchronized table had an IDENTITY column defined on one side of the topology (Hub or Member) but not on the other. This schema mismatch led to the sync service attempting to apply SET IDENTITY_INSERT operations during the bulk insert phase — which failed on the database where the column lacked the identity property. Azure SQL Data Sync relies on consistent schema definitions across all participating databases. Any deviation — particularly involving identity columns — can interrupt data movement operations. Mitigation Approach To resolve the issue, the following corrective steps were applied: Remove the affected table from the sync group and save the configuration. Refresh the sync schema. Recreate the table to include the appropriate IDENTITY property. Add the corrected table back to the sync group. Trigger a new sync operation. These steps ensured that the table definitions were aligned across all sync participants, allowing the synchronization process to proceed successfully. Best Practices to Avoid Similar Issues To prevent identity-related sync failures in Azure SQL Data Sync: ✅ Ensure table schemas are identical across all participating databases before onboarding them into a sync group. ✅ Pay special attention to: IDENTITY properties Primary keys Data types Nullable constraints ✅ Always validate schema consistency when: Adding new tables to a sync group Modifying existing table definitions Final Thoughts Schema mismatches — especially those involving identity columns — are a common but often overlooked cause of Data Sync failures. By ensuring consistent table definitions across your hub and member databases, you can significantly reduce the risk of synchronization errors and maintain reliable data movement across regions.Microsoft.Data.SqlClient 7.0 Is Here: A Leaner, More Modular Driver for SQL Server
Today we're shipping the general availability release of Microsoft.Data.SqlClient 7.0, a major milestone for the .NET data provider for SQL Server. This release tackles the single most requested change in the repository's history, introduces powerful new extensibility points for authentication, and adds protocol-level features for Azure SQL Hyperscale, all while laying the groundwork for a more modular driver architecture. If you take away one thing from this post: the core SqlClient package is dramatically lighter now. Azure dependencies have been extracted into a separate package, and you only pull them in if you need them. dotnet add package Microsoft.Data.SqlClient --version 7.0.0 The #1 Request: A Lighter Package For years, the most upvoted issue in the SqlClient repository asked the same question: "Why does my console app that just talks to SQL Server pull in Azure.Identity, MSAL, and WebView2?" With 7.0, it doesn't anymore. We've extracted all Azure / Microsoft Entra authentication functionality into a new Microsoft.Data.SqlClient.Extensions.Azure package. The core driver no longer carries Azure.Core, Azure.Identity, Microsoft.Identity.Client, or any of their transitive dependencies. If you connect with SQL authentication or Windows integrated auth, your bin folder just got dramatically smaller. For teams that do use Entra authentication, the migration is straightforward. Add one package reference and you're done: dotnet add package Microsoft.Data.SqlClient.Extensions.Azure No code changes. No configuration changes. You can also now update Azure dependency versions on your own schedule, independent of driver releases. This is something library authors and enterprise teams have been asking for. Pluggable Authentication with SspiContextProvider Integrated authentication in containers and cross-domain environments has always been a pain point. Kerberos ticket management, sidecar processes, domain trust configuration: the workarounds were never simple. Version 7.0 introduces a new public SspiContextProvider API on SqlConnection that lets you take control of the authentication handshake. You provide the token exchange logic; the driver handles everything else. var connection = new SqlConnection(connectionString); connection.SspiContextProvider = new MyKerberosProvider(); connection.Open(); This opens the door to scenarios the driver never natively supported: authenticating across untrusted domains, using NTLM with explicit credentials, or implementing custom Kerberos negotiation in Kubernetes pods. A sample implementation is available in the repository. Async Read Performance: Packet Multiplexing (Preview) One of the most community-driven features in 7.0 is packet multiplexing, a change to how the driver processes TDS packets during asynchronous reads. Originally contributed by community member Wraith2, this work delivers a significant leap in async read performance for large result sets. Packet multiplexing was first introduced in 6.1 and has been refined across the 7.0 preview cycle with additional bug fixes and stability improvements. In 7.0, it ships behind two opt-in feature switches so we can gather broader real-world feedback before making it the default: AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour", false); AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni", false); Setting both switches to false enables the new async processing path. By default, the driver uses the existing (compatible) behavior. We need your help. If your application performs large async reads (ExecuteReaderAsync with big result sets, streaming scenarios, or bulk data retrieval), please try enabling these switches and let us know how it performs in your environment. File your results on GitHub Issues to help us move this toward on-by-default in a future release. Enhanced Routing for Azure SQL Azure SQL environments with named read replicas and gateway-based load balancing can now take advantage of enhanced routing, a TDS protocol feature that lets the server redirect connections to a specific server and database during login. This is entirely transparent to your application. No connection string changes, no code changes. The driver negotiates the capability automatically when the server supports it. .NET 10 Ready SqlClient 7.0 compiles and tests against the .NET 10 SDK, so you're ready for the next major .NET release on day one. Combined with continued support for .NET 8, .NET 9, .NET Framework 4.6.2+, and .NET Standard 2.0 (restored in 6.1), the driver covers the full spectrum of active .NET runtimes. ActiveDirectoryPassword Is Deprecated: Plan Your Migration As Microsoft moves toward mandatory multifactor authentication across its services, we've deprecated SqlAuthenticationMethod.ActiveDirectoryPassword (the ROPC flow). The method still works in 7.0, but it's marked [Obsolete] and will generate compiler warnings. Now is the time to move to a stronger alternative: Scenario Recommended Authentication Interactive / desktop apps Active Directory Interactive Service-to-service Active Directory Service Principal Azure-hosted workloads Active Directory Managed Identity Developer / CI environments Active Directory Default Quality of Life Improvements Beyond the headline features, 7.0 includes a collection of improvements that make the driver more reliable and easier to work with in production. Better retry logic. The new SqlConfigurableRetryFactory.BaselineTransientErrors property exposes the built-in transient error codes, so you can extend the default list with your own application-specific codes instead of copy-pasting error numbers from source. More app context switches. You can now set MultiSubnetFailover=true globally, ignore server-provided failover partners in Basic Availability Groups, and control async multi-packet behavior, all without modifying connection strings. Better diagnostics on .NET Framework. SqlClientDiagnosticListener is now enabled for SqlCommand on .NET Framework, closing a long-standing observability gap. Connection performance fix. A regression where SPN generation was unnecessarily triggered for SQL authentication connections on the native SNI path has been resolved. Performance improvements. Allocation reductions across Always Encrypted scenarios, SqlStatistics timing, and key store providers. Upgrading from 6.x For most applications, upgrading is a package version bump: dotnet add package Microsoft.Data.SqlClient --version 7.0.0 If you use Microsoft Entra authentication, also add: dotnet add package Microsoft.Data.SqlClient.Extensions.Azure If you use ActiveDirectoryPassword, you'll see a compiler warning. Start planning your migration to a supported auth method. Review the full release notes in release-notes/7.0 for the complete list of changes across all preview releases. Thank You to Our Contributors Open-source contributions are central to SqlClient's development. We'd like to recognize the community members who contributed to the 7.0 release: edwardneal · ErikEJ · MatthiasHuygelen · ShreyaLaxminarayan · tetolv · twsouthwick · Wraith2 What's Next We're continuing to invest in performance, modularity, and modern .NET alignment. Stay tuned for updates on the roadmap, and keep the feedback coming. Your issues and discussions directly shape what we build. NuGet: Microsoft.Data.SqlClient 7.0.0 GitHub: dotnet/SqlClient Issues & Feedback: github.com/dotnet/SqlClient/issues Docs: Microsoft.Data.SqlClient on Microsoft Learn2.6KViews2likes5CommentsUnderstanding and Monitoring Class 2 Transactions in Azure SQL Database
During a recent customer engagement, we investigated sustained transaction log growth in Azure SQL Database without obvious large user transactions. The customer was familiar with PostgreSQL diagnostics and wanted to understand how similar insights can be obtained in Azure SQL Database—especially around Class 2 (system) transactions. This post summarizes what we discussed, explains why Azure SQL behaves differently, and walks through practical DMV‑based monitoring patterns you can use today. Azure SQL Database vs. PostgreSQL: Diagnostic Model Differences One of the first clarifications we made is that Azure SQL Database does not expose diagnostic settings equivalent to PostgreSQL’s system‑level log diagnostics. Azure SQL Database is a fully managed PaaS service, and many internal operations—such as checkpoints, version store cleanup, and background maintenance—are abstracted from direct control. Instead of low‑level engine logs, Azure SQL provides cumulative Dynamic Management Views (DMVs) that expose the effects of system activity rather than the internal implementation. What Are Class 2 Transactions? In Azure SQL Database, Class 2 transactions generally refer to system‑generated transactions, not directly initiated by user workloads. These commonly include: Checkpoint operations Version store cleanup Ghost record cleanup Background metadata maintenance Although they are not user‑driven, these transactions still generate transaction log activity, which can be surprising when log usage grows steadily without large user transactions. Key DMVs to Monitor Class 2 Activity 1. Transaction Log Usage SELECT * FROM sys.dm_db_log_space_usage; This DMV provides: Total log size Used log space Used log percentage If log usage grows steadily without large user transactions, it is often a signal that background system activity (Class 2 transactions) is responsible. Checkpoint Activity SELECT * FROM sys.dm_exec_requests WHERE command = 'CHECKPOINT'; Frequent checkpoints result in: More frequent log flushes Increased system log writes In Azure SQL Database, checkpoint frequency is system‑managed and cannot be tuned through configuration or diagnostic settings. Version Store Usage (Common Class 2 Contributor) SELECT * FROM sys.dm_tran_version_store_space_usage; High version store usage often leads to: Background cleanup tasks Increased system transactions Additional transaction log generation This is especially common in workloads using: Snapshot Isolation Read Committed Snapshot Isolation (RCSI) Long‑running transactions or readers Automating Monitoring with Azure Elastic Jobs Because these DMVs are cumulative, capturing them over time is key. During the call, we discussed automating data collection using Azure Elastic Jobs. Elastic Jobs allow you to: Schedule DMV snapshots Store historical trends Correlate spikes with workload patterns Microsoft provides full guidance on creating and managing Elastic Jobs using T‑SQL here: Create and manage Elastic Jobs using T‑SQL Index Management and Class 2 Impact Index maintenance can indirectly increase Class 2 activity by: Increasing version store usage Triggering additional background cleanup Instead of manual index tuning, we recommended enabling Query Performance Insight – Index recommendations in the Azure Portal. This allows Azure SQL Database to automatically: Suggest index creation Suggest index removal based on real workload patterns. Why Checkpoints Cannot Be Tuned A common question is whether checkpoint frequency can be reduced to lower system log activity. In Azure SQL Database: Checkpoints are engine‑managed There is no diagnostic or configuration setting to control their frequency This design ensures platform stability and predictable recovery behavior As a result, monitoring—not tuning—is the correct approach. Practical Takeaways From this case, the key lessons are: Not all transaction log growth is user‑driven Class 2 transactions are a normal part of Azure SQL Database DMVs provide the best visibility into system behavior Trend‑based monitoring is more valuable than point‑in‑time checks Automation via Elastic Jobs is essential for long‑term analysis Conclusion Class 2 transactions are often misunderstood because they operate quietly in the background. By using the right DMVs and collecting data over time, you can clearly distinguish expected system behavior from genuine workload issues. If you’re coming from PostgreSQL or on‑prem SQL Server, the key mindset shift is this: Azure SQL Database exposes outcomes, not internals—and that’s by design.