SQL_Server_Deadlock_Analysis

Snippet Name: SQL_Server_Deadlock_Analysis

Tags:

Created Date: 2025-06-16 13:12:43
Last Modified Date: 2025-05-31 10:31:36

Dependencies:

DBA Notes:

Sample Output:

Title: SQL Server Deadlock Analysis
Author: user
in sql server, i am looking at deadlocks. if i know the database_id, the partition number, and the pageNumber, how can i get the object_name in a single query

Author: ChatGPT
To determine the object (such as a table or index) associated with a particular page in SQL Server, you can use the `DBCC PAGE` command combined with system catalog views. However, note that `DBCC PAGE` is generally used for manual inspection and its output isn't directly queryable. Therefore, to retrieve the object name based on `database_id`, `partition_number`, and `page_number` in a single query, we'll utilize system views instead.

Here???s a step-by-step approach using SQL Server system views:

1. **Convert `database_id` to `database_name`:** This can be achieved using the `DB_NAME()` function.
2. **Query system views for object information:** We'll use `sys.partitions`, `sys.allocation_units`, and `sys.objects` to find the object based on `partition_id`.

Below is the SQL script that can be used to find the object name using `database_id`, `partition_number`, and `pageNumber`:

```sql
DECLARE @DatabaseID INT = YOUR_DATABASE_ID;
DECLARE @PartitionNumber INT = YOUR_PARTITION_NUMBER;
DECLARE @PageNumber INT = YOUR_PAGE_NUMBER;
DECLARE @DatabaseName NVARCHAR(128);

-- Get the database name from the database ID
SET @DatabaseName = DB_NAME(@DatabaseID);

-- Construct and execute a dynamic SQL to query the object from the specific database
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = '
USE [' + @DatabaseName + '];

SELECT
    OBJECT_NAME(p.object_id) AS ObjectName,
    p.object_id,
    i.name AS IndexName,
    i.index_id,
    au.type_desc AS AllocationType,
    p.partition_number
FROM
    sys.partitions p
    JOIN sys.allocation_units au ON p.hobt_id = au.container_id
    LEFT JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id
WHERE
    p.partition_number = ' + CAST(@PartitionNumber AS NVARCHAR) + '
    AND EXISTS (
        SELECT 1 FROM sys.dm_db_database_page_allocations(@DatabaseID, p.object_id, NULL, NULL, ''DETAILED'')
        WHERE allocated_page_page_id = ' + CAST(@PageNumber AS NVARCHAR) + '
    )
';

-- Execute the dynamic SQL
EXEC sp_executesql @SQL;
```

### Explanation:
- **Step 1:** We convert the `database_id` to a `database_name` using `DB_NAME()`.
- **Step 2:** Using dynamic SQL, we switch to the target database using the `USE` statement. This is because `sys.partitions` and other system views are specific to the current database context.
- **Step 3:** We join `sys.partitions` to `sys.allocation_units` and optionally to `sys.indexes` to find details about the object and its indexes. `sys.dm_db_database_page_allocations` is used to filter the page in question. This function requires you to pass `database_id`, and it will return pages allocated to the database's objects.
- **Step 4:** The script checks for the existence of the specified page in the allocations for the object corresponding to the given partition.

Make sure to replace `YOUR_DATABASE_ID`, `YOUR_PARTITION_NUMBER`, and `YOUR_PAGE_NUMBER` with actual values.

This script should give you the object name associated with the specified page number and partition number. Remember, querying system tables and functions with administrative privileges is typically necessary, and the `sys.dm_db_database_page_allocations` function is available in SQL Server 2012 and later versions. Also, running some of these commands, especially on production systems, should be done with caution as they can be resource-intensive.

Author: user
this mySQL query is using an inner join of a group by query, but i think it can be changed into an IF EXISTS instead.: SELECT p1.store_id,
       (p1.ends_time_millis - p1.start_time_millis) AS milliseconds
FROM CDKS.performance p1,
(
    SELECT p2.store_id,
           p2.data_point_name,
           MAX(p2.extraction_datetime) AS extr_datetime
    FROM CDKS.performance p2
    GROUP BY p2.store_id,
             p2.data_point_name
) p2
WHERE p1.store_id = p2.store_id
      AND p1.data_point_name = p2.data_point_name
      AND p1.extraction_datetime = p2.extr_datetime
      AND p1.data_point_name = '';

Author: ChatGPT
The query you provided uses a subquery in a join to select the latest records (based on `extraction_datetime`) for each `store_id` and `data_point_name` combination from the `performance` table. If you're considering simplifying this with an `EXISTS` clause, your intention might be to reduce complexity or possibly improve performance. However, switching to `EXISTS` isn't directly applicable for this scenario because your main query requires data (`ends_time_millis`, `start_time_millis`) from the matched row in the subquery.

The original query effectively selects rows from `p1` where there exists a maximum `extraction_datetime` for each combination of `store_id` and `data_point_name` (which matches `p1`), also ensuring it filters by a specific `data_point_name`.

Here's how you can rewrite the query using a `JOIN` without the subquery being used in the `FROM` clause, which could be more readable and might help in optimization by some SQL engines:

```sql
SELECT
    p1.store_id,
    (p1.ends_time_millis - p1.start_time_millis) AS milliseconds
FROM
    CDKS.performance p1
INNER JOIN (
    SELECT
        store_id,
        data_point_name,
        MAX(extraction_datetime) AS max_datetime
    FROM
        CDKS.performance
    WHERE
        data_point_name = ''
    GROUP BY
        store_id, data_point_name
) AS p2 ON p1.store_id = p2.store_id 
        AND p1.data_point_name = p2.data_point_name 
        AND p1.extraction_datetime = p2.max_datetime
WHERE
    p1.data_point_name = '';
```

### Explanation:
1. **Subquery**: The subquery remains almost the same but is more explicitly filtering for `data_point_name = ''` within it, potentially improving performance by reducing the groupings done only to those records with the empty data point name.

2. **Join Condition**: I've kept the `INNER JOIN` based on three conditions to make sure the row in `p1` is the latest for its `store_id` and `data_point_name`.

3. **Main Query WHERE Clause**: I added a filter to check `p1.data_point_name = ''` to ensure consistency with your original query, although it's somewhat redundant due to the subquery's similar filter.

### Using `EXISTS`:
If you still prefer using `EXISTS`, you must understand that `EXISTS` typically returns true or false and doesn't help in directly pulling data from the row causing the condition to be true. It would generally be used in scenarios where you just need to check the existence of a row under certain conditions without needing data from that row, which doesn't fit your requirement as you need to retrieve and calculate with specific column values.

However, if the `EXISTS` method is still interesting or there's a different scenario to apply it, I'd be happy to help you craft an example for that situation!

Author: user
can you convert these SQL Server drop index statements to have IF EXISTS...DROP logic: drop index [CIG__loanacct_statuses-status_code_no-acctrefno-effective_date] on dbo.loanacct_statuses; 
drop index [CIG_cif_cifnumber_IDX] on dbo.cif; 
drop index [CIG_IX_loanacct_collections_ptp_promise_date_amount_received] on dbo.loanacct_collections_ptp; 
drop index [CIG_loanacct_collection_officer_balance_IDX] on dbo.loanacct; 
drop index [CIG_loanacct_input_date_IDX] on dbo.loanacct; 
drop index [CIG_task_completion_date_IDX] on dbo.task; 
drop index [CIG-acctrefno-userdef20_SMS] on dbo.loanacct_collections; 
drop index [CIG-IX_campaign_list_detail-campaign_no] on dbo.campaign_list_detail; 
drop index [CIG-IX-loanacct_payment_history-nsf_flag-payment_type] on dbo.loanacct_payment_history; 
drop index [CIG-IX-loanacct_trans_history-transaction_reference_no] on dbo.loanacct_trans_history; 
drop index [CIG-IX-task-task_template_no-NLS_refno] on dbo.task; 
drop index [CIG-loanacct_collections-created_SMS] on dbo.loanacct_collections; 
drop index [dri_loanacct_restruc_acct_idx] on dbo.loanacct; 
drop index [idx_loanacct_statement_804197] on dbo.loanacct_statement; 

Author: ChatGPT
To modify SQL Server `DROP INDEX` statements to include a check for existence using `IF EXISTS`, you'll need to use the `DROP INDEX` statement inside a conditional `IF` check. SQL Server supports the `IF EXISTS` syntax directly in `DROP INDEX` statements starting from SQL Server 2016 (13.x). This makes it more straightforward and efficient to avoid errors due to non-existing indexes.

Here are your original `DROP INDEX` statements, converted to include `IF EXISTS` checks:

```sql
-- Check and drop index if it exists
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG__loanacct_statuses-status_code_no-acctrefno-effective_date' AND object_id = OBJECT_ID(N'dbo.loanacct_statuses'))
    DROP INDEX [CIG__loanacct_statuses-status_code_no-acctrefno-effective_date] ON dbo.loanacct_statuses;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG_cif_cifnumber_IDX' AND object_id = OBJECT_ID(N'dbo.cif'))
    DROP INDEX [CIG_cif_cifnumber_IDX] ON dbo.cif;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG_IX_loanacct_collections_ptp_promise_date_amount_received' AND object_id = OBJECT_ID(N'dbo.loanacct_collections_ptp'))
    DROP INDEX [CIG_IX_loanacct_collections_ptp_promise_date_amount_received] ON dbo.loanacct_collections_ptp;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG_loanacct_collection_officer_balance_IDX' AND object_id = OBJECT_ID(N'dbo.loanacct'))
    DROP INDEX [CIG_loanacct_collection_officer_balance_IDX] ON dbo.loanacct;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG_loanacct_input_date_IDX' AND object_id = OBJECT_ID(N'dbo.loanacct'))
    DROP INDEX [CIG_loanacct_input_date_IDX] ON dbo.loanacct;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG_task_completion_date_IDX' AND object_id = OBJECT_ID(N'dbo.task'))
    DROP INDEX [CIG_task_completion_date_IDX] ON dbo.task;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-acctrefno-userdef20_SMS' AND object_id = OBJECT_ID(N'dbo.loanacct_collections'))
    DROP INDEX [CIG-acctrefno-userdef20_SMS] ON dbo.loanacct_collections;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-IX_campaign_list_detail-campaign_no' AND object_id = OBJECT_ID(N'dbo.campaign_list_detail'))
    DROP INDEX [CIG-IX_campaign_list_detail-campaign_no] ON dbo.campaign_list_detail;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-IX-loanacct_payment_history-nsf_flag-payment_type' AND object_id = OBJECT_ID(N'dbo.loanacct_payment_history'))
    DROP INDEX [CIG-IX-loanacct_payment_history-nsf_flag-payment_type] ON dbo.loanacct_payment_history;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-IX-loanacct_trans_history-transaction_reference_no' AND object_id = OBJECT_ID(N'dbo.loanacct_trans_history'))
    DROP INDEX [CIG-IX-loanacct_trans_history-transaction_reference_no] ON dbo.loanacct_trans_history;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-IX-task-task_template_no-NLS_refno' AND object_id = OBJECT_ID(N'dbo.task'))
    DROP INDEX [CIG-IX-task-task_template_no-NLS_refno] ON dbo.task;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'CIG-loanacct_collections-created_SMS' AND object_id = OBJECT_ID(N'dbo.loanacct_collections'))
    DROP INDEX [CIG-loanacct_collections-created_SMS] ON dbo.loanacct_collections;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'dri_loanacct_restruc_acct_idx' AND object_id = OBJECT_ID(N'dbo.loanacct'))
    DROP INDEX [dri_loanacct_restruc_acct_idx] ON dbo.loanacct;

IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'idx_loanacct_statement_804197' AND object_id = OBJECT_ID(N'dbo.loanacct_statement'))
    DROP INDEX [idx_loanacct_statement_804197] ON dbo.loanacct_statement;
```

These statements check if the index exists before attempting to drop it, preventing an error if the index does not exist. This approach is safer for scripts that might run in different environments or conditions where indexes might not have been created or could have been dropped previously. This method assumes you are using SQL Server 2016 or later, which supports this syntax directly. For earlier versions, the technique you would need to use involves querying the `sys.indexes` system catalog view, as shown above, to conditionally execute the `DROP INDEX` command.

Author: user
repeat for this collection of drop commands: /****** Object:  Index [general_ledger_accounts-gl_account]    Script Date: 11/9/2023 2:34:31 PM ******/
DROP INDEX [general_ledger_accounts-gl_account] ON [dbo].[general_ledger_accounts]
GO

/****** Object:  Index [IX_cif_loan_relationship_acctrefno]    Script Date: 11/9/2023 12:59:47 PM ******/
DROP INDEX [IX_cif_loan_relationship_acctrefno] ON [dbo].[cif_loan_relationship]
GO


/****** Object:  Index [IX_cif_loan_relationship_cifno]    Script Date: 11/9/2023 1:00:13 PM ******/
DROP INDEX [IX_cif_loan_relationship_cifno] ON [dbo].[cif_loan_relationship]
GO

/****** Object:  Index [IX_cif_loan_relationship_relationship_code_id]    Script Date: 11/9/2023 1:00:26 PM ******/
DROP INDEX [IX_cif_loan_relationship_relationship_code_id] ON [dbo].[cif_loan_relationship]
GO


/****** Object:  Index [XIF01PaymentGatewayPendingTrans]    Script Date: 11/9/2023 1:01:44 PM ******/
DROP INDEX [XIF01PaymentGatewayPendingTrans] ON [dbo].[PaymentGatewayPendingTrans]
GO

/****** Object:  Index [XIF02PaymentGatewayPendingTrans]    Script Date: 11/9/2023 1:02:00 PM ******/
DROP INDEX [XIF02PaymentGatewayPendingTrans] ON [dbo].[PaymentGatewayPendingTrans]

Author: ChatGPT
To ensure that the DROP INDEX commands execute only if the indexes exist, you can use the SQL Server 2016 or later `IF EXISTS` functionality directly in the DROP INDEX statement for each index you wish to drop. This simplifies error handling and ensures your script doesn't fail if an index has been previously removed or doesn't exist.

Here are the modified commands using the `IF EXISTS` syntax:

```sql
/****** Object:  Index [general_ledger_accounts-gl_account]    Script Date: 11/9/2023 2:34:31 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'general_ledger_accounts-gl_account' AND object_id = OBJECT_ID(N'[dbo].[general_ledger_accounts]'))
    DROP INDEX [general_ledger_accounts-gl_account] ON [dbo].[general_ledger_accounts];
GO

/****** Object:  Index [IX_cif_loan_relationship_acctrefno]    Script Date: 11/9/2023 12:59:47 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_cif_loan_relationship_acctrefno' AND object_id = OBJECT_ID(N'[dbo].[cif_loan_relationship]'))
    DROP INDEX [IX_cif_loan_relationship_acctrefno] ON [dbo].[cif_loan_relationship];
GO

/****** Object:  Index [IX_cif_loan_relationship_cifno]    Script Date: 11/9/2023 1:00:13 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_cif_loan_relationship_cifno' AND object_id = OBJECT_ID(N'[dbo].[cif_loan_relationship]'))
    DROP INDEX [IX_cif_loan_relationship_cifno] ON [dbo].[cif_loan_relationship];
GO

/****** Object:  Index [IX_cif_loan_relationship_relationship_code_id]    Script Date: 11/9/2023 1:00:26 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_cif_loan_relationship_relationship_code_id' AND object_id = OBJECT_ID(N'[dbo].[cif_loan_relationship]'))
    DROP INDEX [IX_cif_loan_relationship_relationship_code_id] ON [dbo].[cif_loan_relationship];
GO

/****** Object:  Index [XIF01PaymentGatewayPendingTrans]    Script Date: 11/9/2023 1:01:44 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'XIF01PaymentGatewayPendingTrans' AND object_id = OBJECT_ID(N'[dbo].[PaymentGatewayPendingTrans]'))
    DROP INDEX [XIF01PaymentGatewayPendingTrans] ON [dbo].[PaymentGatewayPendingTrans];
GO

/****** Object:  Index [XIF02PaymentGatewayPendingTrans]    Script Date: 11/9/2023 1:02:00 PM ******/
IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'XIF02PaymentGatewayPendingTrans' AND object_id = OBJECT_ID(N'[dbo].[PaymentGatewayPendingTrans]'))
    DROP INDEX [XIF02PaymentGatewayPendingTrans] ON [dbo].[PaymentGatewayPendingTrans];
GO
```

### Key Points
- **SQL Server Version:** These statements assume you're using SQL Server 2016 or later. For earlier versions, you need to handle the check manually as shown in the previous example.
- **`IF EXISTS`:** This check prevents errors if the index does not exist, ensuring the script runs smoothly.
- **`OBJECT_ID` and `sys.indexes`:** These system functions and views are used to check the existence of an index before attempting to drop it.

This modification ensures robustness in deployment scripts, particularly when they might be run multiple times or in different environments where the index configuration might vary.

Author: user
in SQL server, how can i check if a database is the read only copy of an always availability group

Author: ChatGPT
To check if a database in SQL Server is a read-only copy in an Always On Availability Group, you can use system views to determine the role of the replica and the database's read/write status. SQL Server provides several dynamic management views (DMVs) that help monitor and manage the health and status of Always On Availability Groups.

Here are the steps and SQL query to check if a specific database is part of an Always On Availability Group and if it is currently serving as a read-only replica:

1. **Query `sys.databases`:** This view contains information about the databases in an instance of SQL Server. It includes a column `is_read_only` which indicates whether the database is read-only.

2. **Query `sys.dm_hadr_availability_replica_states`:** This DMV provides the state information of availability replicas in the Availability Group. It includes a column `role_desc` which indicates whether the replica is primary or secondary.

3. **Query `sys.availability_groups`:** This view provides information about the Availability Groups.

4. **Query `sys.availability_replicas`:** This view gives details about the replicas in each Availability Group.

Here is a SQL query that combines information from these views to determine if a database is a read-only replica in an Availability Group:

```sql
SELECT 
    db.name AS DatabaseName,
    db.is_read_only AS IsReadOnly,
    ag.name AS AvailabilityGroupName,
    ar.replica_server_name AS ReplicaServerName,
    rs.role_desc AS ReplicaRole
FROM 
    sys.databases db
    INNER JOIN sys.dm_hadr_database_replica_states drs ON db.database_id = drs.database_id
    INNER JOIN sys.availability_replicas ar ON drs.group_id = ar.group_id AND drs.replica_id = ar.replica_id
    INNER JOIN sys.availability_groups ag ON ar.group_id = ag.group_id
    INNER JOIN sys.dm_hadr_availability_replica_states rs ON ar.replica_id = rs.replica_id
WHERE 
    db.name = 'YourDatabaseName' -- Specify your database name here
    AND rs.role_desc = 'SECONDARY'
    AND db.is_read_only = 1;
```

### Explanation:
- **DatabaseName:** Name of the database.
- **IsReadOnly:** Shows if the database is in read-only mode (`1` if true).
- **AvailabilityGroupName:** Name of the Availability Group.
- **ReplicaServerName:** Name of the server where the replica is located.
- **ReplicaRole:** The role of the replica (e.g., PRIMARY, SECONDARY).

This query checks if the specified database (`YourDatabaseName`) is part of an Always On Availability Group, if it is on a secondary replica, and if it is set to read-only mode. Remember to replace `'YourDatabaseName'` with the actual name of the database you are checking.

This query is useful in environments where databases are replicated across multiple servers for failover clustering and availability purposes, and you need to confirm if a particular database is currently serving read-only queries as part of its configuration in the Availability Group.

Author: user
i have an AAG that i know is the secondary, but the is_read_only flags are still 0; is that because the database is not a readable secodnary, and just a disaster recovery?

Author: ChatGPT
Yes, that is a possible reason. In SQL Server's Always On Availability Groups (AAG), a secondary replica can be configured in multiple ways, and one of those configurations is as a non-readable secondary, often used purely for disaster recovery purposes.

Here???s a brief rundown on how this configuration can affect the `is_read_only` flag:

1. **Read-Only Access Settings:** In the configuration of Always On Availability Groups, each secondary replica can be set up either to allow read-only access or not. This setting is defined by the `secondary_role_allow_connections` property of the availability replica. The possible settings are:
   - **`NO`**: No connections are allowed to the secondary databases. The databases on this replica are not accessible for read access.
   - **`READ_ONLY`**: Allows read-only access to the secondary databases. This setting requires that the secondary replica also has the read-only routing configured if you want to automate read-only connection routing to this replica.
   - **`ALL`**: Allows all connections as long as the secondary database is not the primary replica.

2. **`is_read_only` Flag:** The `is_read_only` column in `sys.databases` reflects whether a database is set to read-only mode. However, in the context of Always On Availability Groups, this flag being set to `0` (indicating read-write) on a secondary replica does not necessarily mean it can perform write operations. The databases on secondary replicas are always inherently read-only in terms of data modifications (you cannot directly modify the data on the secondary). This flag being `0` might simply indicate that the replica is not intended for read-only access (e.g., for reporting or offloading queries).

3. **Disaster Recovery Configuration:** If the secondary replica is intended for disaster recovery, it might not allow any connections (set as `NO`), making it a non-readable secondary. This would mean the databases would technically be in a "not accessible" state until a failover makes them primary.

To check the configuration of your secondary replica regarding read access, you can query the `sys.availability_replicas` view, particularly looking at the `secondary_role_allow_connections_desc` column. Here???s how you can do this:

```sql
SELECT 
    ar.replica_server_name,
    ar.secondary_role_allow_connections_desc
FROM 
    sys.availability_replicas ar
    JOIN sys.dm_hadr_availability_replica_states rs
        ON ar.replica_id = rs.replica_id
WHERE
    rs.role_desc = 'SECONDARY';
```

This query will show you how the secondary replicas are configured to handle connections, which can help determine if they are set up for disaster recovery (no connections allowed) or if they allow read-only access. If the query results show that the connections are set to `NO`, it explains why `is_read_only` is still `0`, as the database does not allow any connections, not even for read purposes.


Leave a Reply