[진행사항]

Always-on Availability group(AG) 구성의 SQL Server에서 Primary replica가 병렬 Index Rebuild 수행 시 Secondary도 동일한 수량의 병렬 Index Rebuild를 수행하는지를 문의주신 건으로 이해됩니다.

 

AG의 데이터 동기화는 Primary Replica에서 발생한 Transaction log Secondary Replica에서 Redo하는 단계로 진행됩니다.

참고로 SQL Server 2016 이후 Parallel redo thread(Instance당 최대 100, Database당 최대 16)를 지원합니다.

다만, Parallel redo thread의 개수는 Primary replica에서 Transaction log를 발생시킨 쿼리의 DoP와 관계 없이, 현재 Secondary replica에서 Transaction log Redo하는 부하 상황에 따라 달라지게 됩니다.

: SQL Server 2016/2017: Availability group secondary replica redo model and performance - Microsoft Community Hub

 

이 때문에, Primary replica에서 병렬 Index Rebuild 수행 시 Secondary replica에서 병렬로 Redo를 수행할 가능성은 높으나,
Index Rebuild
상황을 Redo하는 Secondary Replica Redo Thread 개수는, 일반적으로 Primary replica Index Rebuild Thread 개수보다 적게 됩니다.

 

예를 들어 다음과 같이 테스트환경에서 약 10GB 크기의 테이블에 대해, MAXDOP 4의 병렬 Index Rebuild를 수행하여 Secondary Replica에서 Redo thread를 모니터링 하면,
Primary Replica
DoP 4 Index Rebuild를 수행하고 있으나, 같은 시점의 Secondary Replica 3개의 Redo thread Transaction Redo 하고 있음을 확인할 수 있습니다.

Case Result
Test Scenario
- 10GB 크기의 테이블에 Maxdop=4 Index Rebuild를 수행
EXEC sys.sp_spaceused 'dbo.tbl_Index_ParallelBuild'
GO        


 
ALTER INDEX UCL__tbl_Index_ParallelBuild__01 ON dbo.tbl_Index_ParallelBuild REBUILD WITH (MAXDOP = 4)
GO
 
Primary Replica
- 첨부드리는 Monitor_IndexRebuild_ParallelRedo.sql 쿼리를 통해
74
번 세션에서 수행중인 Index Rebuild 수행 상황을 모니터링 하면,
2
개의 Consumer thread를 제외하고, 4개의 Thread Parallel Rebuild 수행중임을 확인할 수 있습니다.
Secondary Replica
- 첨부드리는 Monitor_IndexRebuild_ParallelRedo.sql 쿼리를 통해
AG
Redo 수행 상황을 모니터링 하면, AGDB01 데이터베이스에 대해 3개의 Parallel redo thread Index rebuild를 수행중임을 확인할 수 있습니다.

 

 

Monitor_IndexRebuild_ParallelRedo.sql
0.00MB

1.     파티션 테이블 압축시 서비스 영향도

-> 온라인으로 가능한지, IO 발생 및 압축진행중인 파티션 사용시 lock 발생 등의 위험요소가 궁금합니다.

-     파티션별 압축의 경우에도 전체 인덱싱과 마찬가지로 온라인 인덱싱을 지원합니다.

Online Indexing의 경우, 인덱싱 작업중 대상 테이블/파티션에 대한 CRUD가 가능하나, 아래와 같이 단계에 따라 작업 및 잠금을 획득하며, 이 과정에서 여러가지 형태의 성능 경합과 잠금이 발생합니다.

다만 해당 환경의 성능 여유가 충분한 상황이라면, 일반적으로 이와 같은 성능 경합이 심각한 성능 영향을 주지는 않습니다.

 

이와 관련하여서는 다음의 문서를 통해 조금 더 세부적인 내용을 확인하실 수 있습니다.

: How Online Index Operations Work - SQL Server | Microsoft Learn

Phase Source activity Source locks Architecture
Preparation
: Short phase
-  System metadata preparation to create the new empty index structure.
 
-  A snapshot of the table is defined. That is, row versioning is used to provide transaction-level read consistency.
 
-  Concurrent user write operations on the source are blocked for a short period.
No concurrent DDL operations are allowed except creating multiple nonclustered indexes.
-  S (Shared) on the table*
 
-  IS (Intent Shared)
 
-  INDEX_BUILD_INTERNAL_RESOURCE**
Build
: Main phase
-  The data is scanned, sorted, merged, and inserted into the target in bulk load operations.
 
-  Concurrent user select, insert, update, and delete operations are applied to both the pre-existing indexes and any new indexes being built.
-  IS
 
-  INDEX_BUILD_INTERNAL_RESOURCE**
Final
: Short phase
-  All uncommitted update transactions must complete before this phase starts.
 
-  Depending on the acquired lock, all new user read or write transactions are blocked for a short period until this phase is completed.
 
-  System metadata is updated to replace the source with the target.
 
-  The source is dropped if it is required. For example, after rebuilding or dropping a clustered index.
-  INDEX_BUILD_INTERNAL_RESOURCE**
 
-  S on the table if creating a nonclustered index.*
 
-  SCH-M (Schema Modification) if any source structure (index or table) is dropped.*

 

-     다음은 온라인 인덱싱 작업에 대한 일반적인 가이드라인입니다. Online Indexing 작업 중 발생할 수 있는 여러가지 이슈사항과 사전 고려사항을 정리하고 있으며,
Update
부하가 높은 환경에서 Online Indexing , 종종 발생하는 Deadlock(자동으로 해소)과 같은 증상도 해당 문서를 통해 확인할 수 있습니다.

: Guidelines for online index operations - SQL Server | Microsoft Learn

 

-> 온라인으로 절대 해서는 안되는 작업인지

-     온라인 수행에 문제는 없습니다. 다만 고객환경의 비즈니스로직에서의 성능 영향도를 파악하기 위해 고객사 내부적으로 충분한 테스트를 거쳐 검증하신 후 작업을 수행하실 것을 권장 드립니다.

 

-> 압축 진행중인 해당 파티션에만 영향을 주는 것인지

-     특정 파티션에 대해 압축을 수행하는 경우, 결과적으로는 해당 파티션에만 영향을 주는 것이 맞습니다.

하지만 압축을 진행하는 과정의 마지막 단계에서 Metadata를 업데이트 하며 Sch-M 잠금을 획득하므로, 동 시간대 대상 테이블에 대해 Sch-S/Sch-M을 요청하는 작업들과의 일시적인 성능 경합이 있을 수 있습니다.

 

2.     테이블/인덱스 압축시 내부 프로세스 및 모니터링 필수 요소

-> 압축대상 파티션을 바로 압축을 시도 하는지 혹은 별도의 데이터파일 여유 공간에 생성후 전환 (rebuild 처럼) 등등

-     인덱스의 파티션별 압축 작업 또한 마찬가지로 ALTER INDEX REBUILD 작업을 수행하며, 데이터파일 신규 공간을 할당 받아 Sort 및 인덱스를 생성한 후 기존의 개체를 대체하게 되므로 추가 공간을 사용하게 됩니다.

예를 들어 30GB 크기의 A인덱스의 1번 파티션을 압축하여 10GB로 압축이 수행된 경우, 작업 시작 시점에 Datafile 내 여유공간이 없었다면 데이터파일의 크기가 10GB 증가하게 됩니다.

 

-     Sort_in_tempdb 옵션을 사용하는 경우에도 새로운 공간에 신규 인덱스를 배치한 후 기존의 인덱스 개체를 제거하므로 추가 Disk 공간의 사용은 발생합니다.

 

-> 롤백이 발생하게 되나요?

-     Offline Indexing의 경우 작업을 중단하는 경우 해당 작업에 대한 Rollback이 발생하며, Online Indexing의 경우에도 작업 중단에 의해 메타데이터 업데이트를 위한 짧은 Rollback이 필요합니다.

-     작업 수행 중 다음의 내용을 모니터링 하실 수 있을 것으로 의견 드립니다.

     Data / Log Disk 사용률 모니터링

     Blocked Process 발생 여부 모니터링

-- sys.dm_exec_requests (Transact-SQL) - SQL Server | Microsoft Learn
SELECT session_id, status, blocking_session_id, wait_type, wait_time, wait_resource, transaction_id
FROM sys.dm_exec_requests WHERE status = N'suspended';
GO

 

     Data File 사용률 모니터링

SELECT  Database_Name   = db_name(database_id)
    ,   file_id
    ,   Filesize_MB     = total_page_count/128
    ,   Allocated_MB    = allocated_extent_page_count/128
    ,   UnAllocated_MB  = unallocated_extent_page_count/128
    FROM  sys.dm_db_file_space_usage
GO

 

     Log File 사용률 모니터링

SELECT  DB_Name            = db_name(database_id)
    ,   LogfileSize_MB     = total_log_size_in_bytes/1024/1024
    ,   LogfileUsed_MB     = used_log_space_in_bytes/1024/1024
    ,   LogfileUsed_PCT    = used_log_space_in_percent
  FROM  sys.dm_db_log_space_usage
GO

 

-> 파티션 압축 작업을 진행하게 되면 로그 사용량의 변화는 어떻게 되는지 궁금합니다.

-     전체 인덱스를 한 번에 압축하는 경우와 한 개 파티션 씩 압축하는 경우 모두 거의 유사한 크기의 Transaction Log가 발생하는 것으로 확인됩니다.

인덱스 전체를 한 번에 압축한 경우: 트랜잭션 로그 2,514 MB 증가 한 개 파티션 씩 12회 압축한 경우: 트랜잭션 로그 2,514 MB 증가
BACKUP LOG [OnlineCompressionWithPartitions] TO DISK = 'NUL';
GO
 
-- Log Used: 63 MB
SELECT  DB_Name            = db_name(database_id)
    ,   LogfileSize_MB     = total_log_size_in_bytes/1024/1024
    ,   LogfileUsed_MB     = used_log_space_in_bytes/1024/1024
    ,   LogfileUsed_PCT    = used_log_space_in_percent
  FROM  sys.dm_db_log_space_usage
GO


 
-- Duration: 78 Sec
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION = ALL WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
GO
 
-- Log Used: 2,577 MB (2,514 MB increased)
SELECT  DB_Name            = db_name(database_id)
    ,   LogfileSize_MB     = total_log_size_in_bytes/1024/1024
    ,   LogfileUsed_MB     = used_log_space_in_bytes/1024/1024
    ,   LogfileUsed_PCT    = used_log_space_in_percent
  FROM  sys.dm_db_log_space_usage
GO

BACKUP LOG [OnlineCompressionWithPartitions] TO DISK = 'NUL';
GO
 
-- Log Used: 15 MB
SELECT  DB_Name            = db_name(database_id)
    ,   LogfileSize_MB     = total_log_size_in_bytes/1024/1024
    ,   LogfileUsed_MB     = used_log_space_in_bytes/1024/1024
    ,   LogfileUsed_PCT    = used_log_space_in_percent
  FROM  sys.dm_db_log_space_usage
GO


 
-- Duration: 77 Sec
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION =WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION = 10 WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION = 11 WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
ALTER INDEX CPK__Tbl_Partition     ON dbo.Tbl_Partition REBUILD PARTITION = 12 WITH(ONLINE = ON, DATA_COMPRESSION = PAGE, MAXDOP = 4)
 
-- Log Used: 2,529 MB (2,514 MB increased)
SELECT  DB_Name            = db_name(database_id)
    ,   LogfileSize_MB     = total_log_size_in_bytes/1024/1024
    ,   LogfileUsed_MB     = used_log_space_in_bytes/1024/1024
    ,   LogfileUsed_PCT    = used_log_space_in_percent
  FROM  sys.dm_db_log_space_usage
GO

 

##SQL Version :Microsoft SQL Server 2019 (RTM-CU17) (KB5016394) - 15.0.4249.2 (X64)

 

 

확인사항]

먼저 DTC실패(Error: 8509)기록된후 Rollback하는 과정에서 Dump가 생성으며, 해당 이슈는 이미 알려진 이슈로 CU21에서 FIX가 되었습니다.

해당 Dump in-memory Table사용과정에서 발생할수있으며, 고객사같은 경우는 DTC Transaction Rollback 되는 과정에서 해당 known issue로 인한 Dump가 생성었고 이로인한 가용성그룹이 Resolving상태로 빠지게됩니다.

이후 Leasetime out이 발생하나 FailureConditionLevel(2) Failover가 자동적으로 Triggered되지 않았습니다.

 

#ErrorLog.2
2024-04-02 14:01:03.93 spid62      Error: 8509, Severity: 16, State: 1.
2024-04-02 14:01:03.93 spid62      Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed: 0x8004d00e(XACT_E_NOTRANSACTION).
2024-04-02 14:01:35.11 spid156     Error: 8509, Severity: 16, State: 1.
2024-04-02 14:01:35.11 spid156     Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed: 0x8004d00e(XACT_E_NOTRANSACTION).
2024-04-02 14:02:01.15 spid853     Error: 8509, Severity: 16, State: 1.
2024-04-02 14:02:01.15 spid853     Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed: 0x8004d00e(XACT_E_NOTRANSACTION).
2024-04-02 14:02:35.74 spid35s     CImageHelper::Init () Version-specific dbghelp.dll is not used
2024-04-02 14:02:35.74 spid35s     Using 'dbghelp.dll' version '4.0.5'
2024-04-02 14:02:35.75 spid35s     **Dump thread - spid = 0, EC = 0x0000020BD57C1E10
..중략..
2024-04-02 14:02:35.75 spid35s     ***Stack Dump being sent to \SQLDump0017.txt
2024-04-02 14:02:35.75 spid35s     * *******************************************************************************
2024-04-02 14:02:35.75 spid35s     *
2024-04-02 14:02:35.75 spid35s     * BEGIN STACK DUMP:
2024-04-02 14:02:35.75 spid35s     *   04/02/24 14:02:35 spid 35
2024-04-02 14:02:35.75 spid35s     *
2024-04-02 14:02:35.75 spid35s     * Location:         "sql\\ntdbms\\hekaton\\engine\\core\\tx.cpp":6337
2024-04-02 14:02:35.75 spid35s     * Expression:         Dependencies.CommitDepCountOut >= 1
2024-04-02 14:02:35.75 spid35s     * SPID:                 35
2024-04-02 14:02:35.75 spid35s     * Process ID:         5652
2024-04-02 14:03:23.88 spid35s     Timeout waiting for external dump process 21796.
 
2024-04-02 14:03:23.88 spid35s     Error: 17066, Severity: 16, State: 1.
2024-04-02 14:03:23.88 spid35s     SQL Server Assertion: File: <"sql\\ntdbms\\hekaton\\engine\\core\\tx.cpp">, line=6337 Failed Assertion = 'Dependencies.CommitDepCountOut >= 1'. This error may be timing-related. If the error persists after rerunning the statement, use DBCC CHECKDB to check the database for structural integrity, or restart the server to ensure in-memory data structures are not corrupted.
..중략..
2024-04-02 14:03:23.88 Server      The state of the local availability replica in availability group 'L_AVG' has changed from 'PRIMARY_NORMAL' to 'RESOLVING_NORMAL'.  The state changed because the lease between the local availability replica and Windows Server Failover Clustering (WSFC) has expired.  For more information, see the SQL Server error log or cluster log.  If this is a Windows Server Failover Clustering (WSFC) availability group, you can also see the WSFC management console.
2024-04-02 14:03:23.88 spid189s    The availability group database "DATABASE_P03" is changing roles from "PRIMARY" to "RESOLVING" because the mirroring session or availability group failed over due to role synchronization. This is an informational message only. No user action is required.
2024-04-02 14:03:23.88 spid325s    The availability group database " DATABASE_03 _IF" is changing roles from "PRIMARY" to "RESOLVING" because the mirroring session or availability group failed over due to role synchronization. This is an informational message only. No user action is required.
2024-04-02 14:03:23.88 spid851s    The availability group database " DATABASE_03_HMI_2" is changing roles from "PRIMARY" to "RESOLVING" because the mirroring session or availability group failed over due to role synchronization. This is an informational message only. No user action is required.
2024-04-02 14:03:23.88 spid849s    The availability group database " DATABASE_03_P03" is changing roles from "PRIMARY" to "RESOLVING" because the mirroring session or availability group failed over due to role synchronization. This is an informational message only. No user action is required.
 
#ClusterLog
00002c40.00003588::2024/04/02-14:02:40.450 INFO  [RES] SQL Server Availability Group: [hadrag] SQL Server component 'system' health state has been changed from 'clean' to 'warning' at 2024-04-02 14:02:40.447
00002c40.00003588::2024/04/02-14:02:40.453 INFO  [RES] SQL Server Availability Group: [hadrag] SQL Server component 'query_processing' health state has been changed from 'clean' to 'warning' at 2024-04-02 14:02:40.447
00002c40.00000844::2024/04/02-14:02:57.490 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] Lease renewal failed with timeout error
00002c40.00000844::2024/04/02-14:02:57.490 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] The lease is expired. The lease should have been renewed by 2024/04/02-12:02:47.490
00002c40.00003858::2024/04/02-14:02:58.046 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] Availability Group lease is no longer valid
00002c40.00003858::2024/04/02-14:02:58.046 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] Resource Alive result 0.
00002c40.00003858::2024/04/02-14:02:58.046 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] Availability Group lease is no longer valid
00002c40.00003858::2024/04/02-14:02:58.046 ERR   [RES] SQL Server Availability Group <L_AVG>: [hadrag] Resource Alive result 0.
00002c40.00003858::2024/04/02-14:02:58.046 WARN  [RHS] Resource L_AVG IsAlive has indicated failure.
00001cec.00002838::2024/04/02-14:02:58.046 INFO  [RCM] HandleMonitorReply: FAILURENOTIFICATION for 'L_AVG', gen(0) result 1/0.
00002c40.00003858::2024/04/02-14:02:58.046 INFO  [RHS-WER] Scheduling WER ERROR report in 10.000. ReportId 2f1efff4-5677-4f46-8c8c-9162e4c155db;
00001cec.00002838::2024/04/02-14:02:58.046 INFO  [RCM] Res L_AVG: Online -> ProcessingFailure( StateUnknown )
00001cec.00002838::2024/04/02-14:02:58.046 INFO  [RCM] TransitionToState(L_AVG) Online-->ProcessingFailure.

 

해당 이슈가 Failover Trigger하는 부차적인 이슈가 있으며, 이로인한 failover를 반복적으로 수행되나 해당 역할을 Online시키지 못함에 따라 반복적으로 dump로 인한 failover가 수행되었습니다.

이과정에서 large transaction rollback및 반복적인 failover시도로인한 DB연결이슈(983)가 발생한 것으로 확인하였습니다.

 

2024/04/02-14:03:23.916 INFO  [RCM] rcm::RcmApi::MoveGroup: (Group:L_AVG Dest:1 Flags:0 MoveType:MoveType::Manual Cur.State:Pending, ContextSize:0)
2024/04/02-14:03:23.917 ERR   [RCM] rcm::RcmApi::OnlineResource: (5023)' because of 'The API call is not valid while resource is in the [Terminating to DelayRestartingResource] state.'


2024-04-02 14:03:23.93 spid725s    Error: 3624, Severity: 20, State: 1.
2024-04-02 14:03:23.93 spid725s    A system assertion check has failed. Check the SQL Server error log for details. Typically, an assertion failure is caused by a software bug or data corruption. To check for database corruption, consider running DBCC CHECKDB. If you agreed to send dumps to Microsoft during setup, a mini dump will be sent to Microsoft. An update might be available from Microsoft in the latest Service Pack or in a Hotfix from Technical Support.
2024-04-02 14:03:23.93 spid725s    Error: 3314, Severity: 21, State: 3.
2024-04-02 14:03:23.93 spid725s    During undoing of a logged operation in database 'tempdb' (page (1:139) if any), an error occurred at log record ID (17626:408077:603). Typically, the specific failure is logged previously as an error in the operating system error log. Restore the database or file from a backup, or repair the database.
..중략..
2024-04-02 14:04:22.71 Logon       Error: 983, Severity: 14, State: 1.
2024-04-02 14:04:22.71 Logon       Unable to access availability database 'DATABASE_P03' because the database replica is not in the PRIMARY or SECONDARY role. Connections to an availability database is permitted only when the database replica is in the PRIMARY or SECONDARY role. Try the operation again later.
2024-04-02 14:04:30.07 spid189s    Nonqualified transactions are being rolled back in database DATABASE_P03 for an Always On Availability Groups state change. Estimated rollback completion: 0%. This is an informational message only. No user action is required.
2024-04-02 14:04:32.07 spid189s    Nonqualified transactions are being rolled back in database DATABASE_P03 for an Always On Availability Groups state change. Estimated rollback completion: 0%. This is an informational message only. No user action is required.
2024-04-02 14:04:32.99 spid653s    Remote harden of transaction 'GhostCleanupTask' (ID 0x00000072638fd8cd 0001:3a728b14) started at Apr  2 2024  2:04PM in database 'DATABASE_P03' at LSN (170966:295662:202) failed.
2024-04-02 14:04:33.67 spid325s    Attempting to recover in-doubt distributed transactions involving Microsoft Distributed Transaction Coordinator (MS DTC). This is an informational message only. No user action is required.
2024-04-02 14:04:33.67 spid37s     Always On: DebugTraceVarArgs AR '[HADR] [Primary] operation on replicas [5777243E-1C2F-4399-8B8F-8F006F9FD4EE]->[CFF305E1-8C94-4DB3-85D7-0EDD5502E88E], database [DATABASE_P03_IF], remote endpoint [TCP://LES.es.net:5022], source operation [8909B701-63D0-4C65-BE06-CE7BC04DD049]: Transitioning from [CATCHUP] to [COMPLETED].'
2024-04-02 14:04:33.67 spid46s     Always On: DebugTraceVarArgs AR '[HADR] [Primary] operation on replicas [5777243E-1C2F-4399-8B8F-8F006F9FD4EE]->[7ED01BC6-97DF-41C4-84E9-737FE70CB40B], database [DATABASE_P03_IF], remote endpoint [TCP://LES.es.net:5022], source operation [79597AD0-EB94-4E28-ACC6-0E791C5BB0E0]: Transitioning from [CATCHUP] to [COMPLETED].'
2024-04-02 14:04:33.67 spid974s    [DbMgrPartnerCommitPolicy::SetReplicaInfoInAg] DbMgrPartnerCommitPolicy::SetSyncAndRecoveryPoint: CFF305E1-8C94-4DB3-85D7-0EDD5502E88E:10:4
2024-04-02 14:04:34.07 spid189s    Nonqualified transactions are being rolled back in database DATABASE_P03 for an Always On Availability Groups state change. Estimated rollback completion: 0%. This is an informational message only. No user action is required.

 

이후 고객사에서 LES Server재시작(2024-04-02 14:49)이후 정상적으로 'DATABASE_P03' Online되며 AG가 정상적으로 Online되었습니다.

2024-04-02 14:49:51.76 Server      SQL Server is terminating because of a system shutdown. This is an informational message only. No user action is required.
 
2024-04-02 15:00:39.69 spid46s     Always On Availability Groups connection with secondary database established for primary database 'DATABASE_P03' on the availability replica 'LES2' with Replica ID: {cff305e1-8c94-4db3-85d7-0edd5502e88e}. This is an informational message only. No user action is required.
2024-04-02 15:00:39.81 spid139s    Always On Availability Groups connection with secondary database established for primary database 'DATABASE_P03' on the availability replica 'LES2' with Replica ID: {cff305e1-8c94-4db3-85d7-0edd5502e88e}. This is an informational message only. No user action is required.
2024-04-02 15:00:52.81 spid79s     Always On: DebugTraceVarArgs AR '[HADR] [Primary] operation on replicas [5777243E-1C2F-4399-8B8F-8F006F9FD4EE]->[CFF305E1-8C94-4DB3-85D7-0EDD5502E88E], database [DATABASE_P03], remote endpoint [TCP://LES2.es.net:5022], source operation [719D2611-5566-404F-A02F-B08684AE5A07]: Transitioning from [CATCHUP] to [COMPLETED].'
2024-04-02 15:00:57.02 spid46s     [DbMgrPartnerCommitPolicy::SetReplicaInfoInAg] DbMgrPartnerCommitPolicy::SetSyncAndRecoveryPoint: CFF305E1-8C94-4DB3-85D7-0EDD5502E88E:11:4
2024-04-02 15:00:57.02 spid46s     DbMgrPartnerCommitPolicy::SetSyncState: CFF305E1-8C94-4DB3-85D7-0EDD5502E88E:11:4

 

[제안사항]

해당 이슈는 CU21에서 FIX되었으며, 최소 CU21 patch하시는 것을 제안드립니다.

Cumulative update 21 for SQL Server 2019 (KB5025808) - SQL Server | Microsoft Learn

 

 

다만, 기존 known issue들에 의 한 잠재적인 위험요소가 있기에 가장 마지막 버젼인 CU25로 업그레이드 하시는 것을 권장드립니다.

Download SQL Server® 2019 for Microsoft® Windows Latest Cumulative Update from Official Microsoft Download Center

일반적으로 백업은 DML blocking 하지 않으나 백업이 진행중일 때는 database/file shrink 할 수 없으며,

Primary에서 Log shrink 를 진행해도 Secondary에도 적용이 되기 때문에 위와 같은 Lock wait이 발생하게 됩니다.

Limitations and restrictions

Ÿ   The database can't be made smaller than the minimum size of the database. The minimum size is the size specified when the database was originally created, or the last explicit size set by using a file-size-changing operation, such as DBCC SHRINKFILE. For example, if a database was originally created with a size of 10 MB and grew to 100 MB, the smallest size the database could be reduced to is 10 MB, even if all the data in the database has been deleted.
Ÿ   You can't shrink a database while the database is being backed up. Conversely, you can't back up a database while a shrink operation on the database is in process.
Ÿ   You cannot shrink files while database Backups are happening.
Ÿ   You might also notice that these commands encounter a wait_type = LCK_M_U and a wait_resource = DATABASE: <id> [BULKOP_BACKUP_DB] when the status of these commands is viewed from the various dynamic management views (DMVs), such as from sys.dm_exec_requests or sys.dm_os_waiting_tasks.


Shrink a database - SQL Server | Microsoft Learn

MSSQLSERVER_3023 - SQL Server | Microsoft Learn

1. AG 설정 xml 파일 생성

 

아래 쿼리를 Primary, Secondary(Async relica)에서 각각 실행

select
AGNode.group_name
,AGNode.replica_server_name
,AGNode.node_name,ReplicaState.role,ReplicaState.role_desc
,ReplicaState.is_local
,DatabaseState.database_id
,db_name(DatabaseState.database_id) as database_name
,DatabaseState.group_database_id
,DatabaseState.is_commit_participant
,DatabaseState.is_primary_replica
,DatabaseState.synchronization_state_desc
,DatabaseState.synchronization_health_desc
,ClusterState.group_id
,ReplicaState.replica_id
from sys.dm_hadr_availability_replica_cluster_nodes AGNode
join sys.dm_hadr_availability_replica_cluster_states ClusterState
on AGNode.replica_server_name = ClusterState.replica_server_name
join sys.dm_hadr_availability_replica_states ReplicaState
on ReplicaState.replica_id = ClusterState.replica_id
join sys.dm_hadr_database_replica_states DatabaseState
on ReplicaState.replica_id=DatabaseState.replica_id
for XML RAW, ROOT('AGInfoRoot')

 

생성되는 링크 클릭

 

각각의 xml output primary.xml , secondary.xml 저장하여 전달

 

추가 Extended event 수집

문제가 발생하는 시간대를 특정지을 수는 없다고 하셨지만, 지연이벤트가 집중되는 시간대가 확인되시는 경우 해당 시간대에 수집하여 주시기 바랍니다.

아래 수집 경로 파일명도 원하시는 대로 변경하실 있으며, 서버 사용량에 맞게 file size rollover file 개수를 조정하시기 바랍니다.

보내드리는 스크립트는 500MB 파일이 4 생성되는 구조입니다.

 

Secondary 경우 지연 현상에 대해 확인이 필요한 Async 노드에서만 수집하시기 바랍니다.

-- 1.1. On Primary Replica
CREATE EVENT SESSION [AlwaysOn_Data_Movement_Tracing] ON SERVER
ADD EVENT sqlserver.hadr_apply_log_block,
ADD EVENT sqlserver.hadr_capture_filestream_wait,
ADD EVENT sqlserver.hadr_capture_log_block,
ADD EVENT sqlserver.hadr_capture_vlfheader,
ADD EVENT sqlserver.hadr_db_commit_mgr_harden,
ADD EVENT sqlserver.hadr_log_block_compression,
ADD EVENT sqlserver.hadr_log_block_decompression,
ADD EVENT sqlserver.hadr_log_block_group_commit ,
ADD EVENT sqlserver.hadr_log_block_send_complete,
ADD EVENT sqlserver.hadr_lsn_send_complete,
ADD EVENT sqlserver.hadr_receive_harden_lsn_message,
ADD EVENT sqlserver.hadr_send_harden_lsn_message,
ADD EVENT sqlserver.hadr_worker_pool_task,
 
ADD EVENT sqlserver.hadr_database_flow_control_action,
ADD EVENT sqlserver.hadr_transport_flow_control_action, 
ADD EVENT ucs.ucs_connection_flow_control,
 
ADD EVENT sqlserver.hadr_transport_receive_log_block_message,
ADD EVENT sqlserver.log_block_pushed_to_logpool,
ADD EVENT sqlserver.log_flush_complete ,
ADD EVENT sqlserver.recovery_unit_harden_log_timestamps
ADD TARGET package0.event_file(SET filename=N'c:\temp\AlwaysOn_Data_Movement_Tracing.xel',max_file_size=(500),max_rollover_files=(4))
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO
 
-- 1.2 On Secondary Replica
CREATE EVENT SESSION [AlwaysOn_Data_Movement_Tracing] ON SERVER
ADD EVENT sqlserver.hadr_apply_log_block,
ADD EVENT sqlserver.hadr_capture_filestream_wait,
ADD EVENT sqlserver.hadr_capture_log_block,
ADD EVENT sqlserver.hadr_capture_vlfheader,
ADD EVENT sqlserver.hadr_db_commit_mgr_harden,
ADD EVENT sqlserver.hadr_log_block_compression,
ADD EVENT sqlserver.hadr_log_block_decompression,
ADD EVENT sqlserver.hadr_log_block_group_commit ,
ADD EVENT sqlserver.hadr_log_block_send_complete,
ADD EVENT sqlserver.hadr_lsn_send_complete,
ADD EVENT sqlserver.hadr_receive_harden_lsn_message,
ADD EVENT sqlserver.hadr_send_harden_lsn_message,
ADD EVENT sqlserver.hadr_undo_of_redo_log_scan,
 
ADD EVENT sqlserver.hadr_database_flow_control_action,
ADD EVENT sqlserver.hadr_transport_flow_control_action, 
ADD EVENT ucs.ucs_connection_flow_control,
 
ADD EVENT sqlserver.hadr_transport_receive_log_block_message,
ADD EVENT sqlserver.log_block_pushed_to_logpool,
ADD EVENT sqlserver.log_flush_complete ,
ADD EVENT sqlserver.recovery_unit_harden_log_timestamps
ADD TARGET package0.event_file(SET filename=N'c:\temp\AlwaysOn_Data_Movement_Tracing.xel',max_file_size=(500),max_rollover_files=(4))
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO
 
 
--2. Start Session
ALTER EVENT SESSION [AlwaysOn_Data_Movement_Tracing] ON SERVER STATE=START;
 
 
--3. Stop Session
ALTER EVENT SESSION [AlwaysOn_Data_Movement_Tracing] ON SERVER STATE=STOP;

 

분석에 필요한 event SQL Server 2014 SP2 / 2016 SP1 이후 추가되었기 때문에 이전 버전에서는 해당 이벤트 수집이 어려운 , 참고 부탁드립니다.

KB3173156 - Update adds AlwaysOn extended events and performance counters in SQL Server 2014 or 2016 - Microsoft Support

 

Perfmon 수집

1) cmd를 관리자권한으로 실행한 뒤 아래 명령을 실행하면 성능 수집기가 생성됩니다.

  • C:\sqlperflogs 경로에 파일이 생성되고 5초 간격으로 카운터를 수집하도록 설정되어 있습니다옵션을 고객사 환경에 맞게 적절하게 변경하셔도 관계 없습니다.
  • M/d/yyyy 를 다음날로 설정하시면 다음날 00시부터 수집이 되며, 24시간(23시간 59 59) 간격으로 새로운 파일 생성이 됩니다.
  • 만약 날짜 상관 없이 수집을 바로 시작하고 24시간 간격으로 수집을 원하실 경우 아래, 명령어를 변경하신 후 start 해 주시기 바랍니다.
  • 로그를 수집한 후 파일 사이즈를 확인하고 문제가 재현되지 않을 경우 디스크 공간을 많이 차지하지 않도록 주기적으로 로그 파일을 수동 제거할 필요가 있습니다.

 

DEFAULT INSTANCE

Logman.exe create counter SQLPerfLog -o "c:\sqlperflogs\PerfLog.csv" -f csv -v mmddhhmm -b M/d/yyyy 12:00:00AM -r -rf 23:59:59 -si 00:00:05   -c "\PhysicalDisk(*)\*" "\LogicalDisk(*)\*" "\Memory\*" "\Processor(_Total)\*" "\Process(*)\*" "\Network Interface(*)\*" "\Paging File(*)\*" "\System\*" "\SQLServer:Access Methods\*" "\SQLServer:Backup Device\*" "\SQLServer:Broker Activation\*" "\SQLServer:Broker Statistics\*" "\SQLServer:Broker TO Statistics\*" "\SQLServer:Broker/DBM Transport\*" "\SQLServer:Buffer Manager\*" "\SQLServer:Buffer Node(*)\*" "\SQLServer:Catalog Metadata(*)\*" "\SQLServer:CLR\*" "\SQLServer:Cursor Manager by Type(*)\*" "\SQLServer:Cursor Manager Total\*" "\SQLServer:Database Mirroring\*" "\SQLServer:Database Replica(*)\*" "\SQLServer:Databases(*)\*" "\SQLServer:Deprecated Features(*)\*" "\SQLServer:Exec Statistics(*)\*" "\SQLServer:General Statistics\*" "\SQLServer:Latches\*" "\SQLServer:Locks(*)\*" "\SQLServer:Memory Manager\*" "\SQLServer:Plan Cache(*)\*" "\SQLServer:Replication Agents\*" "\SQLServer:Replication Dist.\*" "\SQLServer:Replication Logreader\*" "\SQLServer:Replication Merge\*" "\SQLServer:Replication Snapshot\*" "\SQLServer:Resource Pool Stats(*)\*" "\SQLServer:SQL Errors(*)\*" "\SQLServer:SQL Statistics\*"  "\SQLServer:Transactions\*" "\SQLServer:User Settable(*)\*" "\SQLServer:Wait Statistics(*)\*" "\SQLServer:Workload Group Stats(*)\*"

 

NAMED INSTANCE
MSSQL$NAMEDINS”를 고객사 NAMED INSTANCE이름으로 변경이 필요합니다.
Ex) MSSQL$NAMEDINS à MSSQL$DB2

Logman.exe create counter SQLPerfLog -o "c:\sqlperflogs\PerfLog.csv" -f csv -v mmddhhmm -b M/d/yyyy 12:00:00AM  -r -rf 23:59:59 -si 00:00:05  -c  "\PhysicalDisk(*)\*" "\LogicalDisk(*)\*" "\Memory\*" "\Processor(_Total)\*" "\Process(*)\*" "\Network Interface(*)\*" "\Paging File(*)\*" "\System\*" "\MSSQL$NAMEDINS:Backup Device\*" "\MSSQL$NAMEDINS:Broker Activation\*" "\MSSQL$NAMEDINS:Broker Statistics\*" "\MSSQL$NAMEDINS:Broker TO Statistics\*" "\MSSQL$NAMEDINS:Broker/DBM Transport\*" "\MSSQL$NAMEDINS:Buffer Manager\*" "\MSSQL$NAMEDINS:Buffer Node(*)\*" "\MSSQL$NAMEDINS:Catalog Metadata(*)\*" "\MSSQL$NAMEDINS:CLR\*" "\MSSQL$NAMEDINS:Cursor Manager by Type(*)\*" "\MSSQL$NAMEDINS:Cursor Manager Total\*" "\MSSQL$NAMEDINS:Database Mirroring\*" "\MSSQL$NAMEDINS:Database Replica(*)\*" "\MSSQL$NAMEDINS:Databases(*)\*" "\MSSQL$NAMEDINS:Deprecated Features(*)\*" "\MSSQL$NAMEDINS:Exec Statistics(*)\*" "\MSSQL$NAMEDINS:General Statistics\*" "\MSSQL$NAMEDINS:Latches\*" "\MSSQL$NAMEDINS:Locks(*)\*" "\MSSQL$NAMEDINS:Memory Manager\*" "\MSSQL$NAMEDINS:Plan Cache(*)\*" "\MSSQL$NAMEDINS:Replication Agents\*" "\MSSQL$NAMEDINS:Replication Dist.\*" "\MSSQL$NAMEDINS:Replication Logreader\*" "\MSSQL$NAMEDINS:Replication Merge\*" "\MSSQL$NAMEDINS:Replication Snapshot\*" "\MSSQL$NAMEDINS:Resource Pool Stats(*)\*" "\MSSQL$NAMEDINS:SQL Errors(*)\*" "\MSSQL$NAMEDINS:SQL Statistics\*"  "\MSSQL$NAMEDINS:Transactions\*" "\MSSQL$NAMEDINS:User Settable(*)\*" "\MSSQL$NAMEDINS:Wait Statistics(*)\*" "\MSSQL$NAMEDINS:Workload Group Stats(*)\*" "\SQLAgent$NAMEDINS:Alerts\*" "\SQLAgent$NAMEDINS:Jobs(*)\*" "\SQLAgent$NAMEDINS:JobSteps(*)\*"

 

 

-- Database Mirroring 항목 추가

 

DEFAULT INSTANCE

Logman.exe update counter [수집중인Perflog]  -c "\SQLServer:Availability Replica\*" "\PhysicalDisk(*)\*" "\LogicalDisk(*)\*" "\Memory\*" "\Processor(_Total)\*" "\Process(*)\*" "\Network Interface(*)\*" "\Paging File(*)\*" "\System\*" "\SQLServer:Access Methods\*" "\SQLServer:Backup Device\*" "\SQLServer:Broker Activation\*" "\SQLServer:Broker Statistics\*" "\SQLServer:Broker TO Statistics\*" "\SQLServer:Broker/DBM Transport\*" "\SQLServer:Buffer Manager\*" "\SQLServer:Buffer Node(*)\*" "\SQLServer:Catalog Metadata(*)\*" "\SQLServer:CLR\*" "\SQLServer:Cursor Manager by Type(*)\*" "\SQLServer:Cursor Manager Total\*" "\SQLServer:Database Mirroring\*" "\SQLServer:Database Replica(*)\*" "\SQLServer:Databases(*)\*" "\SQLServer:Deprecated Features(*)\*" "\SQLServer:Exec Statistics(*)\*" "\SQLServer:General Statistics\*" "\SQLServer:Latches\*" "\SQLServer:Locks(*)\*" "\SQLServer:Memory Manager\*" "\SQLServer:Plan Cache(*)\*" "\SQLServer:Replication Agents\*" "\SQLServer:Replication Dist.\*" "\SQLServer:Replication Logreader\*" "\SQLServer:Replication Merge\*" "\SQLServer:Replication Snapshot\*" "\SQLServer:Resource Pool Stats(*)\*" "\SQLServer:SQL Errors(*)\*" "\SQLServer:SQL Statistics\*"  "\SQLServer:Transactions\*" "\SQLServer:User Settable(*)\*" "\SQLServer:Wait Statistics(*)\*" "\SQLServer:Workload Group Stats(*)\*"

 

NAMED INSTANCE

MSSQL$NAMEDINS”를 고객사 NAMED INSTANCE이름으로 변경이 필요합니다.

Ex) MSSQL$NAMEDINS à MSSQL$DB2

Logman.exe update counter [수집중인Perflog]  -c "\MSSQL$NAMEDINS:Availability Replica\*"   "\PhysicalDisk(*)\*" "\LogicalDisk(*)\*" "\Memory\*" "\Processor(_Total)\*" "\Process(*)\*" "\Network Interface(*)\*" "\Paging File(*)\*" "\System\*" "\MSSQL$NAMEDINS:Backup Device\*" "\MSSQL$NAMEDINS:Broker Activation\*" "\MSSQL$NAMEDINS:Broker Statistics\*" "\MSSQL$NAMEDINS:Broker TO Statistics\*" "\MSSQL$NAMEDINS:Broker/DBM Transport\*" "\MSSQL$NAMEDINS:Buffer Manager\*" "\MSSQL$NAMEDINS:Buffer Node(*)\*" "\MSSQL$NAMEDINS:Catalog Metadata(*)\*" "\MSSQL$NAMEDINS:CLR\*" "\MSSQL$NAMEDINS:Cursor Manager by Type(*)\*" "\MSSQL$NAMEDINS:Cursor Manager Total\*" "\MSSQL$NAMEDINS:Database Mirroring\*" "\MSSQL$NAMEDINS:Database Replica(*)\*" "\MSSQL$NAMEDINS:Databases(*)\*" "\MSSQL$NAMEDINS:Deprecated Features(*)\*" "\MSSQL$NAMEDINS:Exec Statistics(*)\*" "\MSSQL$NAMEDINS:General Statistics\*" "\MSSQL$NAMEDINS:Latches\*" "\MSSQL$NAMEDINS:Locks(*)\*" "\MSSQL$NAMEDINS:Memory Manager\*" "\MSSQL$NAMEDINS:Plan Cache(*)\*" "\MSSQL$NAMEDINS:Replication Agents\*" "\MSSQL$NAMEDINS:Replication Dist.\*" "\MSSQL$NAMEDINS:Replication Logreader\*" "\MSSQL$NAMEDINS:Replication Merge\*" "\MSSQL$NAMEDINS:Replication Snapshot\*" "\MSSQL$NAMEDINS:Resource Pool Stats(*)\*" "\MSSQL$NAMEDINS:SQL Errors(*)\*" "\MSSQL$NAMEDINS:SQL Statistics\*"  "\MSSQL$NAMEDINS:Transactions\*" "\MSSQL$NAMEDINS:User Settable(*)\*" "\MSSQL$NAMEDINS:Wait Statistics(*)\*" "\MSSQL$NAMEDINS:Workload Group Stats(*)\*" "\SQLAgent$NAMEDINS:Alerts\*" "\SQLAgent$NAMEDINS:Jobs(*)\*" "\SQLAgent$NAMEDINS:JobSteps(*)\*"

 

2) 성능 수집을 중지하려면 아래 명령어를 입력합니다.

Logman.exe stop SQLPerfLog

 

파라미터 변경이 필요하신 경우 아래 링크 참고 부탁 드립니다

logman create counter | Microsoft Learn

 

 logman 명령은 perfmon (성능 모니터)에서 로그 수집기의 중지 조건이나 스케줄을 GUI로도 편집하실 수 있습니다.

 

 

 

(optional) lsn 정보 수집

아래 내용을 수집 기간 동안 단위로 수집하여 주시면 분석에 도움이 있습니다.

다만 서버에 부하가 있을 경우 수집하지 않으셔도 됩니다.

select GETDATE(), database_id, recovery_lsn, truncation_lsn, last_hardened_lsn, last_sent_lsn, last_received_lsn,  
   last_redone_lsn, last_sent_time, last_received_time, last_redone_time 
from sys.dm_hadr_database_replica_states

 

 

  • 읽기 전용 보조 복제본의 장기 실행 활성 트랜잭션 ---- > 읽기 가능한 보조 복제본  에 장기 실행 트랜잭션이 있는 경우 동일한 데이터에 액세스할 때 보조 복제본의 로컬 다시 실행 스레드가 차단 될 수 있습니다 . 

 

 

  • 흐름 제어로 인해 높은 로그 전송 큐 크기 및 낮은 로그 전송 속도 ---- >   SQL AG의 성능이 특정 SQL AG 데이터베이스 또는 SQL AG 복제본 수준에서 흐름 제어 게이트를 트리거하면 로그 데이터 전송이 일시적으로 종료되고 제한됩니다. 초등부터 중등까지. 흐름 제어는 일반적으로 서버 과부하 또는 느린 네트워크로 인해 발생할 수 있습니다. AG에서 흐름 제어 게이트에 진입하는 것을 지속적으로 관찰한 경우 흐름 제어를 유발하는 환경 조건을 해결해야 합니다. 아래 성능 모니터 카운터를 확인하여 SQL AG 데이터베이스 또는 SQL AG 복제본이 흐름 제어 게이트를 트리거했는지 확인할 수 있습니다. 

 

 

  • 공유 REDO 대상   ----- 이 문제는 보조 복제본이 여러 개 있는 경우에만 발생합니다. 하나의 보조 복제본이 다시 실행 프로세스를 완료하는 데 매우 느린 경우 다시 실행 프로세스를 통해 허용되는 최대 LSN에서 다른 보조 복제본이 더 이상 진행되지 않도록 제한합니다. 추적 플래그 9559를 사용하여 문제를 완화할 수 있습니다.  (문서 https://learn.microsoft.com/en-us/archive/blogs/alwaysonpro/recovery-on-secondary-lagging-shared-redo-target )

 

  • 보조 복제본의 병렬 다시 실행 스레드 부족  --- 보조 복제본에 병렬 다시 실행 스레드가 부족하면 TF3478을 사용하여 총 CPU 수에 따라 병렬 다시 실행 스레드의 최대 수가 증가하도록 허용할 수 있습니다. 기본적으로 SQL Server 인스턴스는 보조 복제본에 대한 병렬 다시 실행을 위해 최대 100개의 스레드를 사용합니다. 각 데이터베이스는 총 CPU 코어 수의 최대 절반을 사용하지만 데이터베이스당 스레드는 16개를 초과할 수 없습니다. 단일 인스턴스에 필요한 총 스레드 수가 100을 초과하면 SQL Server는 나머지 모든 데이터베이스에 대해 단일 다시 실행 스레드를 사용합니다. 

 

  • 디스크 또는 I/O 하위 시스템 대기 시간 --> 보조 복제본의 디스크 또는 I/O 하위 시스템에 심각한 병목 현상이 있는 경우 다시 실행 속도가 영향을 받으며 보조 복제본에서는 매우 작습니다. 보조 복제본을 읽을 수 없고 SQL 데이터베이스 파일 스토리지에 대해 기본 및 이 SQL AG의 사용 전용 하드웨어와 유사한 하드웨어가 있는 경우 기본 복제본에 도달하기 전에 보조 복제본에서 이를 볼 가능성이 적습니다. 그러나 보조 복제본에 읽기 전용 워크로드의 양이 많은 경우 IO 하위 시스템에 추가 IO 오버헤드가 발생할 수 있습니다. 빈번한 데이터베이스 백업 및 VM 스냅샷은 추가 I/O 오버
  • 헤드의 또 다른 잠재적인 원인이 될 수 있습니다. 

 

 

 

 

https://techcommunity.microsoft.com/t5/sql-server-support-blog/common-causes-and-troubleshooting-solutions-for-sql-ag-data/ba-p/2963083

 

Common Causes and Troubleshooting Solutions for SQL AG Data Synchronization Latency

This article summarizes the common causes , solutions and troubleshooting mechanism for SQL Availability Group (AG) data synchronization latency between..

techcommunity.microsoft.com

 

USE [master]
GO
CREATE FUNCTION [dbo].[ufn_LogicalDiskDrives]()
RETURNS @DriveList Table
  (
   [DriveLetter]  CHAR(1)
  ,[VolumeName]  VARCHAR(255)
  ,[FileSystem]  VARCHAR(50)
  ,[TotalSize] BIGINT
  ,[AvailableSpace]   BIGINT
  ,[FreeSpace] BIGINT 
  )
AS
BEGIN
--Written by Percy Reyes
  DECLARE @DriveLetter_ASCII_Code  INT
  DECLARE @FileSystemInstance  INT
  DECLARE @DriveCount  INT
  DECLARE @DriveCollection  INT
  DECLARE @Drive  INT
  DECLARE @Property  NVARCHAR(100)    
  DECLARE @DriveLetter  VARCHAR(1)  
  DECLARE @TotalSize BIGINT
  DECLARE @AvailableSpace  BIGINT
  DECLARE @FreeSpace  BIGINT  
  DECLARE @FileSystem VARCHAR(128)
  DECLARE @VolumeName VARCHAR(128)
  DECLARE @IsReady VARCHAR(5)
  
  --Creating a File System Object for getting files or disk info.
  exec sp_OACreate 'Scripting.FileSystemObject', @FileSystemInstance OUT
  --Getting the collection of drives
  exec sp_OAGetProperty @FileSystemInstance,'Drives', @DriveCollection OUT
  --Getting the count of drives from collection
  exec sp_OAGetProperty @DriveCollection,'Count', @DriveCount OUT

  --starting from Drive "A" (ASCII 65) 
  SET @DriveLetter_ASCII_Code = 65
  --to "Z" (ASCII 90)
  WHILE @DriveLetter_ASCII_Code <= 90
  BEGIN
  ---Creating the instance drive from Drive Collection 
        SET @Property = 'item("'+CHAR(@DriveLetter_ASCII_Code)+'")'
        exec sp_OAGetProperty @DriveCollection,@Property, @Drive OUT
  -- Getting the drive letter property
        exec sp_OAGetProperty @Drive,'DriveLetter', @DriveLetter OUT

  IF @DriveLetter = CHAR(@DriveLetter_ASCII_Code)
        BEGIN   
        -- Getting more properties from each drive   
     exec sp_OAGetProperty @Drive,'VolumeName', @VolumeName OUT 
     exec sp_OAGetProperty @Drive,'FileSystem', @FileSystem OUT
     exec sp_OAGetProperty @Drive,'TotalSize', @TotalSize OUT 
              exec sp_OAGetProperty @Drive,'AvailableSpace', @AvailableSpace OUT
              exec sp_OAGetProperty @Drive,'FreeSpace', @FreeSpace OUT          
              exec sp_OAGetProperty @Drive,'IsReady'  , @IsReady OUT; 
            
   IF @IsReady='True'
     INSERT INTO @DriveList ( [DriveLetter],[TotalSize], [AvailableSpace],[FreeSpace],[FileSystem] ,[VolumeName] )
     VALUES( @DriveLetter,@TotalSize,@AvailableSpace,@FreeSpace,@FileSystem,@VolumeName)
                             
            END
   -- forward next drive
            SET @DriveLetter_ASCII_Code = @DriveLetter_ASCII_Code +1
  END 
  
  EXEC sp_OADestroy @Drive 
  EXEC sp_OADestroy @DriveCollection

  RETURN
END

 

새 기능을 사용할 수 있도록 "Ole 자동화 절차" 구성 설정을 활성화해야 합니다. 그렇지 않으면 실패합니다.

 

EXECUTE sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'Ole Automation Procedures', 1
RECONFIGURE WITH OVERRIDE
GO

 

ufn_LogicalDiskDrives 테스트

SELECT * FROM [dbo].[ufn_LogicalDiskDrives]() 

 

새 저장 프로시저 sp_fixeddrives는 모든 데이터베이스에서 호출할 수 있도록 시스템 개체로 표시됩니다. 이 저장 프로시저는 ufn_LogicalDiskDrives 함수에서 반환된 결과를 사용하고 총 데이터 파일 크기 및 총 로그 파일 크기 정보를 최종 결과에 추가합니다. 다음은 T-SQL 생성 코드입니다.

 

select ISNULL(DF.DriveLetter, LF.DriveLetter) DriveLetter,  DF.DataSize_GB, LF.LogSize_GB from  (
   select left(physical_name, 1) DriveLetter, CAST(sum(size)/128.0/1024 AS DECIMAL(10,2)) DataSize_GB
   from sys.master_files
   where type_desc='ROWS'
   group by left(physical_name, 1), type_desc 
   ) DF
  full outer join 
  (
   select left(physical_name, 1) DriveLetter, CAST(sum(size)/128.0/1024  AS DECIMAL(10,2)) LogSize_GB
   from sys.master_files 
   where type_desc='LOG'
   group by left(physical_name, 1), type_desc
   ) LF
  on DF.DriveLetter=LF.DriveLetter

 

결과는 다음과 같습니다.

마지막으로 저장 프로시저 sp_fixeddrives의 T-SQL 생성 코드는 다음과 같습니다.

 

USE [master]
GO
CREATE PROC dbo.sp_fixeddrives
WITH ENCRYPTION
AS
BEGIN
 SET NOCOUNT ON 
 -----Reporting Drive details ....
 select LDD.DriveLetter ,[VolumeName] ,[FileSystem]  
 ,CAST(TotalSize/1024.0/1024/1024.0 AS DECIMAL(10,2)) [Capacity_GB]
 ,CAST(FreeSpace/1024.0/1024/1024.0 AS DECIMAL(10,2)) [FreeSpace_GB]
 ,CAST((CAST(FreeSpace/1024.0/1024/1024.0 AS DECIMAL(10,2))/CAST(TotalSize/1024.0/1024/1024.0 AS DECIMAL(10,2)) ) *100 AS DECIMAL(10,2)) [Free %]
 , ISNULL( DBFiles.DataSize_GB,0) DataSize_GB,ISNULL( DBFiles.LogSize_GB,0) LogSize_GB
 from master.[dbo].[ufn_LogicalDiskDrives]() LDD
 full outer join 
 (
 select ISNULL(DF.DriveLetter, LF.DriveLetter) DriveLetter,  DF.DataSize_GB, LF.LogSize_GB from
  (
   select left(physical_name, 1) DriveLetter, CAST(sum(size)/128.0/1024 AS DECIMAL(10,2)) DataSize_GB
   from sys.master_files
   where type_desc='ROWS'
   group by left(physical_name, 1), type_desc 
   ) DF
  full outer join 
  (
   select left(physical_name, 1) DriveLetter, CAST(sum(size)/128.0/1024  AS DECIMAL(10,2)) LogSize_GB
   from sys.master_files 
   where type_desc='LOG'
   group by left(physical_name, 1), type_desc
   ) LF
  on DF.DriveLetter=LF.DriveLetter
  
 )  DBFiles
 ON DBFiles.DriveLetter =LDD.DriveLetter
 SET NOCOUNT OFF
END

 

저장 프로시저를 시스템 개체로 표시합니다.

 

USE [master]
GO
EXEC sys.sp_MS_marksystemobject 'sp_fixeddrives'

 

다음은 코드를 실행한 후의 출력입니다.

EXEC sp_fixeddrives

 

가용성 복제본 모니터링

sys.availability_replicas

각 SQL Server Always On 가용성 그룹에 대한 기존 가용성 복제본에 대한 정보를 얻습니다. 예를 들어 내 랩 환경에는 [demoag] 및 [SQLAGDemo]라는 두 개의 가용성 그룹이 있습니다. 각 가용성 그룹에는 2개의 복제본이 있으므로 출력에 4개의 행이 표시됩니다.

1
2
3
select replica_server_name,endpoint_url, availability_mode_desc,
failover_mode_desc,secondary_role_allow_connections_desc,create_date,
backup_priority,read_only_routing_url,seeding_mode_desc,read_write_routing_url   from sys.availability_replicas

이 DMV 출력에 유용한 열은 다음과 같습니다.

  • endpoint_url: SQL Server Always On 가용성 그룹은 기본 및 보조 복제본 통신에 데이터베이스 미러링 엔드포인트를 사용합니다. SQL Server는 가용성 그룹을 정의하면 엔드포인트를 자동으로 구성합니다. 이 열에서 엔드포인트 URL을 얻습니다.
  • availability_mode 및 availability_mode_desc: AG가 동기 또는 비동기 모드로 구성되는지 여부를 지정합니다.
    • 0: 비동기 커밋
    • 1: 동기 커밋
    • 4: 구성 전용: 이 모드에서는 기본 복제본이 보조 복제본으로 데이터를 보내지 않습니다. AG 구성 메타데이터만 동기화합니다.
  • failover_mode 및 failover_mode_desc: 가용성 복제본의 장애 조치 모드는 사용하는 가용성 모드에 따라 다릅니다.
    • 동기식 모드: 자동 및 수동 장애 조치
    • 비동기식 모드: 수동 장애 조치
    이 DMV에서 장애 조치 모드에 대해 다음 값을 얻습니다.
    • 0: 자동 장애 복구
    • 1: 수동 장애 조치
  • secondary_role_allow_connections: 읽기 전용 워크로드에 보조 복제본을 사용할 수 있습니다. 이 열에 대해 다음 값을 볼 수 있습니다.
    • 0: 연결 없음
    • 1: 읽기 전용
    • 2: 읽기 전용 액세스를 위한 모든 연결
  • create_date: AG 복제본의 생성된 데이터입니다.
  • backup_priority: 가용성 그룹 구성에서 복제본의 백업 우선 순위를 구성할 수 있습니다. 기본적으로 각 복제본에는 50개의 백업 우선 순위가 있습니다. 0에서 100까지의 값을 사용할 수 있습니다.

 

 

Understanding backups on AlwaysOn Availability Groups - Part 1

In this blog post, which is a first article in series, we will provide you a basic overview of Understanding backups on AlwaysOn Availability Groups

www.sqlshack.com

 

  • read_only_routing_url: 가용성 그룹에 대한 읽기 라우팅 URL을 구성한 경우 이 열에서 해당 URL을 가져옵니다. 제 경우에는 구성하지 않았습니다. 따라서 NULL 값을 보여줍니다.

 

 

How to Configure Read-Only Routing for an Availability Group in SQL Server 2016

In this article we will try to explain how to configure read-only routing for an Availability Group in SQL Server 2016 and how we should work with them.

www.sqlshack.com

 

  • seeding_mode: 직접 시드의 경우 자동 값을 표시하고 그렇지 않으면 수동 시드를 얻습니다.
  • read_write_routing_url: SQL Server 2019에서는 모든 읽기-쓰기 연결에 대해 보조 복제본에서 주 복제본으로 연결을 리디렉션할 수 있습니다. 사용자가 보조 복제본에도 연결하는 경우 내부 연결이 기본 복제본으로 다시 라우팅되기 때문에 오류 메시지가 표시되지 않습니다.

 

  • 읽기-쓰기 URL을 구성하지 않았습니다. 따라서 출력에 NULL 값을 제공합니다.

자세한 내용은 Microsoft 문서 를 참조하십시오.

 

Redirect read/write connections to primary replica - SQL Server Always On

Learn how to redirect read/write connections to the primary replica of an Always On availability group regardless of the server specified in the connection string.

learn.microsoft.com

sys.dm_hadr_availability_replica_cluster_nodess

WSFC에서 AG의 가용성 복제본에 대한 정보를 제공합니다. 복제본 상태에 관계없이 가용성 복제본에 대한 정보를 얻습니다.

출력에서 복제본 서버와 인스턴스 이름을 볼 수 있습니다.

sys.dm_hadr_availability_replica_states

로컬 복제본, 원격 복제본 및 해당 동기화 상태에 대한 세부 정보를 얻는 데 유용한 DMV입니다. 다음은 이 DMV의 중요한 열입니다.

  • role 및 role_desc: 이 열에서 SQL Server Always On 가용성 그룹의 복제본 역할을 얻습니다.
    • 0= 해결 중 - 해결 중 상태에서 Windows Server 장애 조치 클러스터는 장애 조치 중간 또는 실패 상태일 수 있습니다. WSFC에 쿼럼이 없으면 AG도 상태 확인에 표시됩니다.
    • 1: 기본(AG 기본 복제본)
    • 2: 보조( AG 보조 복제본)
  • Operational_state 및 Operational_state_desc : 이 열에서 복제본의 현재 작동 상태를 가져옵니다. 이 열에 다음 값을 가질 수 있습니다.
    • 0- 보류 중인 장애 조치
    • 1- 보류 중:
    • 2- 온라인
    • 3- 오프라인
    • 4-실패
    • 5- No Quorum으로 인한 실패

    • 기본: 온라인, 보류 중, 실패
    • 보조: 온라인, 실패 및 NULL
    • 해결 중: 오프라인, Pending_failover, 실패 및 쿼럼 없음으로 실패
  • recovery_health 및 recovery_health_desc: 이 DMV에 대한 ONLINE_IN_PROGRESS 및 ONLINE 값을 가져옵니다. DMV sys.dm_hadr_database_replica_states의 database_state 열에 대한 롤업입니다.
  • synchronization_health 및 synchronization_health_desc: 동기 또는 비동기 커밋에서 모든 가용성 그룹 데이터베이스의 동기화 상태를 가져옵니다. 이러한 열에 가능한 값은 다음과 같습니다.
    • 0: 건강하지 않음
    • 1: 부분적으로 건강함
    • 2: 건강한

가용성 데이터베이스 모니터링

이 범주에서는 SQL Server Always On 가용성 그룹의 가용성 데이터베이스와 관련된 동적 관리 보기를 살펴봅니다.

sys.dm_hadr_auto_page_repair

SQL Server Always On 가용성 그룹은 자동 페이지 복구 기능을 제공합니다. 손상된 페이지의 경우 아래 이미지와 같이 다른 복제본에서 페이지를 수신합니다.

 

Automatic Page Repair in SQL Server Always On Availability Groups

This article gives an overview of Automatic Page Repair in SQL Server Always On Availability Groups.

www.sqlshack.com

sys.dm_hadr_database_replica_states

기본 및 보조 복제본 동기화, 로그 시퀀스 번호, 데이터베이스 상태, suspend\resume에 대한 정보를 얻는 데에도 유용한 DMV입니다. AG 대시보드에서 기본, 보조 복제본 상태에 대한 정보를 볼 수 있습니다. 대시보드는 이 DMV에서 정보를 가져와 그래픽 형식으로 표시합니다.

기본 복제본과 보조 복제본 모두에서 AG 대시보드를 실행할 수 있습니다. 기본 복제본의 AG 대시보드에는 모든 복제본에 대한 정보가 표시됩니다. 그러나 보조 복제본에서 시작하면 연결된 보조 복제본에 대한 정보만 가져옵니다.

아래 이미지에서 AG 대시보드와 DMV 출력을 볼 수 있습니다.

  • 기본 복제본의 AG 대시보드:
  • 보조 복제본의 AG 대시보드:
1
2
3
4
select db_name(database_id) as [Database],is_primary_replica,
synchronization_state_desc,database_state_desc,is_suspended,suspend_reason_desc,
recovery_lsn,truncation_lsn,last_sent_lsn,last_sent_time,last_received_lsn,last_received_time,last_hardened_lsn,log_send_queue_size,log_send_rate,redo_queue_size,
redo_rate,end_of_log_lsn,last_commit_lsn,last_commit_time,secondary_lag_seconds from sys.dm_hadr_database_replica_states

  • database_id: AG 데이터베이스의 데이터베이스 ID입니다. db_name() 함수를 사용하여 데이터베이스 이름을 얻을 수 있습니다.
  • is_primary_replica: 기본 복제본에 대해 값 1을 반환하고 보조 복제본에 대해 0을 반환합니다.
  • synchronization_state 및 synchronization_state_desc: AG 데이터베이스의 데이터 이동 상태를 나타냅니다.
    • 0: 동기화 안 함 - 주 복제본이 해당 보조 복제본으로 트랜잭션을 보내지 않으면 상태가 동기화 안 됨으로 표시됩니다. 연결 문제, 일시 중단된 데이터 이동, 장애 조치 때문일 수 있습니다.
    • 1: 동기화 중 – 비동기 데이터 커밋에서 AG 데이터베이스의 상태를 동기화 중으로 가져옵니다.
    • 2: 동기화됨: 기본 복제본과 보조 복제본이 동기 데이터 커밋을 위해 동기화된 경우 동기화됨 값을 얻습니다.
    • 3: 되돌리기: 보조 데이터베이스가 복구의 Redo 단계에 있음을 보여줍니다.
    • 4: 초기화 중: 보조 데이터베이스 복구의 실행 취소 단계를 나타냅니다.
  • database_state_desc: 이 열에서 sys.databases와 유사한 데이터베이스의 상태를 얻습니다. 다음 값을 가질 수 있습니다.
    • 온라인
    • 복원 중
    • 복구 중
    • 복구 대기 중
    • Suspect
    • Emergency
    • 오프라인
    • NULL
  • is_suspended: 기본 복제본과 연결된 보조 복제본 간에 데이터베이스 이동이 일시 중단되면 플래그를 1로 변경합니다.
    • 0: 재개
    • 1: 일시 중단됨
  • Suspend_reason : 여기에서 데이터 이동을 중지한 이유를 업데이트합니다. 사용자가 시작했거나 강제 장애 조치 시나리오로 인한 것일 수 있습니다.
    • 0: 사용자가 시작한 정지 활동(SUSPEND_FROM_USER)
    • 1: 강제 장애 조치로 인해(SUSPEND_FROM_PARTNER)
    • 2: Redo: Redo 단계의 오류로 인해(SUSPEND_FROM_REDO)
    • 3: 캡처: 기본 복제본 로그 캡처 프로세스의 오류로 인해.
    • 4:적용: 로그 작성 오류로 인해(SUSPEND_FROM_APPLY)
    • 5: 재시작: 데이터베이스 재시작으로 인해(SUSPEND_FROM_RESTART)
    • 6:Undo: Undo 단계의 오류로 인해(SUSPEND_FROM_Undo)
    • 7: 재검증: 로그 변경의 불일치로 인해(SUSPEND_FROM_REVALIDATION)
    • 8: 오류: 보조 복제본 LNS 지점의 오류로 인해(SUSPEND_FROM_XRF_UPDATE)
  • secondary_lag_seconds: 이 열을 사용하여 주 복제본과 보조 복제본 간의 지연을 확인할 수 있습니다.
  • DMV를 사용하여 동기화 지연을 계산하는 가용성 그룹 동기화 지연 측정 문서를 참조할 수 있습니다.

이 외에도 AG 대시보드와 유사한 기본 복제본과 보조 복제본 간의 데이터 전송 각 단계에 대한 LSN 정보를 제공합니다. 복제본 간의 동기화 문제를 해결하는 데 유용합니다.

SQL Server Always On 가용성 그룹을 모니터링하기 위한 충분한 정보를 얻기 위해 여러 DMV를 결합할 수 있습니다.

아래 쿼리는 다음 DMV를 조인합니다.

  • sys.dm_hadr_database_replica_states,
  • sys.availability_databases_cluster
  • sys.availability_groups
  • sys.availability_groups
  • sys.availability_replicas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SELECT
    ar.replica_server_name,
    adc.database_name,
    ag.name AS ag_name,
    case HDRS.is_primary_replica
    when 1 then 'Primary Replica'
    else 'Secondary Replica'
    end as Replica,
    HDRS.synchronization_state_desc,
    HDRS.synchronization_health_desc,
    HDRS.recovery_lsn,
    HDRS.truncation_lsn,
    HDRS.last_sent_lsn,
    HDRS.last_sent_time,
    HDRS.last_received_lsn,
    HDRS.last_received_time,
    HDRS.last_hardened_lsn,
    HDRS.last_hardened_time,
    HDRS.last_redone_lsn,
    HDRS.last_redone_time,
    HDRS.log_send_queue_size,
    HDRS.log_send_rate,
    HDRS.redo_queue_size,
    HDRS.redo_rate,
    HDRS.filestream_send_rate,
    HDRS.end_of_log_lsn,
    HDRS.last_commit_lsn,
    HDRS.last_commit_time
FROM sys.dm_hadr_database_replica_states AS HDRS
INNER JOIN sys.availability_databases_cluster AS adc
    ON HDRS.group_id = adc.group_id AND
    HDRS.group_database_id = adc.group_database_id
INNER JOIN sys.availability_groups AS ag
    ON ag.group_id = HDRS.group_id
INNER JOIN sys.availability_replicas AS ar
    ON HDRS.group_id = ar.group_id AND
    HDRS.replica_id = ar.replica_id

 

DECLARE @SERVER_ROLE nvarchar(50)

 
SELECT

    @SERVER_ROLE = A.ROLE_DESC

FROM sys.dm_hadr_availability_replica_states AS A

    INNER JOIN sys.availability_groups AS B ON A.GROUP_ID = B.GROUP_ID

WHERE A.IS_LOCAL = 1


IF @SERVER_ROLE = 'PRIMARY'

BEGIN
	PRINT ' Go To the next step.'
END

ELSE
	BEGIN
		RAISERROR ('Do not go to the next step.', 16, 1)
	END
-- Description: Turns a query into a formatted HTML table. Useful for emails. 
-- Any ORDER BY clause needs to be passed in the separate ORDER BY parameter.
-- =============================================
CREATE PROC [dbo].[spQueryToHtmlTable] 
(
  @query nvarchar(MAX), --A query to turn into HTML format. It should not include an ORDER BY clause.
  @orderBy nvarchar(MAX) = NULL, --An optional ORDER BY clause. It should contain the words 'ORDER BY'.
  @html nvarchar(MAX) = NULL OUTPUT --The HTML output of the procedure.
)
AS
BEGIN   
  SET NOCOUNT ON;

  IF @orderBy IS NULL BEGIN
    SET @orderBy = ''  
  END

  SET @orderBy = REPLACE(@orderBy, '''', '''''');

  DECLARE @realQuery nvarchar(MAX) = '
    DECLARE @headerRow nvarchar(MAX);
    DECLARE @cols nvarchar(MAX);    

    SELECT * INTO #dynSql FROM (' + @query + ') sub;

    SELECT @cols = COALESCE(@cols + '', '''''''', '', '''') + ''['' + name + ''] AS ''''td''''''
    FROM tempdb.sys.columns 
    WHERE object_id = object_id(''tempdb..#dynSql'')
    ORDER BY column_id;

    SET @cols = ''SET @html = CAST(( SELECT '' + @cols + '' FROM #dynSql ' + @orderBy + ' FOR XML PATH(''''tr''''), ELEMENTS XSINIL) AS nvarchar(max))''    

    EXEC sys.sp_executesql @cols, N''@html nvarchar(MAX) OUTPUT'', @html=@html OUTPUT

    SELECT @headerRow = COALESCE(@headerRow + '''', '''') + ''<th>'' + name + ''</th>'' 
    FROM tempdb.sys.columns 
    WHERE object_id = object_id(''tempdb..#dynSql'')
    ORDER BY column_id;

    SET @headerRow = ''<tr>'' + @headerRow + ''</tr>'';

    SET @html = ''<table border="1">'' + @headerRow + @html + ''</table>'';    
    ';

  EXEC sys.sp_executesql @realQuery, N'@html nvarchar(MAX) OUTPUT', @html=@html OUTPUT
END
GO
DECLARE @html nvarchar(MAX);
EXEC spQueryToHtmlTable @html = @html OUTPUT,  @query = N'SELECT * FROM dbo.People', @orderBy = N'ORDER BY FirstName';

EXEC msdb.dbo.sp_send_dbmail
    @profile_name = 'Foo',
    @recipients = 'bar@baz.com;',
    @subject = 'HTML email',
    @body = @html,
    @body_format = 'HTML',
    @query_no_truncate = 1,
    @attach_query_result_as_file = 0;

+ Recent posts