Deadlock when querying INFORMATION_SCHEMA

前端 未结 1 986
梦毁少年i
梦毁少年i 2021-01-28 02:31

I have a process which dynamically alters my SQL2K5 table structure according to changes in a published meta-data layer.

For example, if a new column needs to be added a

相关标签:
1条回答
  • 2021-01-28 03:00
    1. INFORMATION_SCHEMA views are just that - views. You can't update them so they are unlikely to cause any deadlocks. If you want to determine the real source (which I assume has something to do with your alters, or other code within the cursor that you didn't show, or other code you're calling in combination with calling these procedures - since selects against views and then selecting variables can't be the cause), I suggest reading Gail Shaw's blog post on interpreting deadlocks.

    2. In spite of (1) I still suggest using more modern catalog views than INFORMATION_SCHEMA. The same information can be derived from, for example, sys.key_constraints.

    3. You're using the default cursor options; and you're nesting cursors. If you end up still using cursors, you should get in the habit of using a less resource intensive cursor (e.g. LOCAL STATIC FORWARD_ONLY READ_ONLY).

    4. You don't actually need a cursor to do this. Here is how I would re-write the PK table script:

      CREATE PROCEDURE dbo.ScriptPKForTable
          @TableName SYSNAME
      AS
      BEGIN
          SET NOCOUNT ON;
      
          DECLARE 
            @pkName    SYSNAME,
            @clustered BIT,
            @object_id INT,
            @sql       NVARCHAR(MAX);
      
          SELECT
            @object_id = OBJECT_ID(UPPER(@TableName));
      
          SELECT
            @pkName = kc.name,
            @clustered = CASE i.[type] 
              WHEN 1 THEN 1 ELSE 0 END
          FROM 
              sys.key_constraints AS kc
          INNER JOIN 
              sys.indexes AS i
              ON kc.parent_object_id = i.[object_id]
              AND kc.unique_index_id = i.index_id
          WHERE
              kc.parent_object_id = @object_id
              AND kc.[type] = 'pk';
      
          SET @sql = N'ALTER TABLE ' + QUOTENAME(@TableName)
            + ' ADD CONSTRAINT ' + @pkName 
            + ' PRIMARY KEY ' + CASE @clustered 
            WHEN 1 THEN 'CLUSTERED' ELSE '' END + ' (';
      
          SELECT
            @sql = @sql + c.name + ','
          FROM 
            sys.index_columns AS ic
          INNER JOIN
            sys.indexes AS i 
            ON ic.index_id = i.index_id
            AND ic.[object_id] = i.[object_id]
          INNER JOIN 
            sys.key_constraints AS kc
            ON i.[object_id] = kc.[parent_object_id]
            AND kc.unique_index_id = i.index_id
          INNER JOIN 
            sys.columns AS c
            ON i.[object_id] = c.[object_id]
            AND ic.column_id = c.column_id
          WHERE
            kc.[type] = 'PK'
            AND kc.parent_object_id = @object_id
          ORDER BY key_ordinal;
      
          SET @sql = LEFT(@sql, LEN(@sql) - 1) + ');';
      
          SELECT COALESCE(@sql, ' ');
      END
      GO
      

    As for the index creation script, I think there is a better way to do this (again without explicit cursors, not that avoiding the cursor is the goal, but the code is going to be a LOT cleaner). First you need a function to build either key or include columns from the index:

    CREATE FUNCTION dbo.BuildIndexColumns
    (
        @object_id        INT,
        @index_id         INT,
        @included_columns BIT
    )
    RETURNS NVARCHAR(MAX)
    AS
    BEGIN
      DECLARE @s NVARCHAR(MAX);
    
      SELECT @s = N'';
    
      SELECT @s = @s + c.name + CASE ic.is_descending_key
        WHEN 1 THEN ' DESC' ELSE '' END + ',' 
        FROM sys.index_columns AS ic
        INNER JOIN sys.columns AS c
        ON ic.[object_id] = c.[object_id]
        AND ic.column_id = c.column_id
        WHERE c.[object_id] = @object_id
        AND ic.[object_id] = @object_id
        AND ic.index_id = @index_id
        AND ic.is_included_column = @included_columns
        ORDER BY ic.key_ordinal;
    
      IF @s > N''
        SET @s = LEFT(@s, LEN(@s)-1);
    
      RETURN (NULLIF(@s, N''));
    END
    GO
    

    With that function in place, a ScriptIndexes procedure is pretty easy:

    CREATE PROCEDURE dbo.ScriptIndexesForTable
        @TableName SYSNAME
    AS
    BEGIN
      SET NOCOUNT ON;
    
      DECLARE
          @sql       NVARCHAR(MAX),
          @object_id INT;
    
      SELECT @sql = N'', @object_id = OBJECT_ID(UPPER(@TableName));
    
      SELECT @sql = @sql + 'CREATE '
          + CASE i.is_unique WHEN 1 THEN 'UNIQUE ' ELSE '' END
          + CASE i.[type] WHEN 1 THEN 'CLUSTERED ' ELSE '' END
          + ' INDEX ' + i.name + ' ON ' + QUOTENAME(@TableName) + ' (' 
          + dbo.BuildIndexColumns(@object_id, i.index_id, 0)
          + ')' + COALESCE(' INCLUDE(' 
          + dbo.BuildIndexColumns(@object_id, i.index_id, 1)
          + ')', '') + ';' + CHAR(13) + CHAR(10)
      FROM
          sys.indexes AS i
      WHERE
          i.[object_id] = @object_id
          -- since this will be covered by ScriptPKForTable:
          AND i.is_primary_key = 0
      ORDER BY i.index_id;
    
      SELECT COALESCE(@sql, ' ');
    END
    GO
    

    Note that my solution does not assume the PK is clustered (your PK script hard-codes CLUSTERED but then your index script assumes that any of the indexes could be clustered). I also ignore additional properties such as filegroup, partitioning, or filtered indexes (not supported in 2005 anyway).

    0 讨论(0)
提交回复
热议问题