查询 INFORMATION_SCHEMA 时死锁 [英] Deadlock when querying INFORMATION_SCHEMA

查看:35
本文介绍了查询 INFORMATION_SCHEMA 时死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个进程可以根据已发布的元数据层中的更改动态更改我的 SQL2K5 表结构.

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

例如,如果需要添加一个新列并且表没有依赖关系 - 步骤将是:1. 使用 T-SQL 为任何索引创建脚本表中已经存在的主键 [这些脚本包含在下面]2. 放下桌子3.从具有新列的元层重新创建表4.执行步骤#1中创建的脚本5. 使用 BulkCopy 填充表格

For example, if a new column needs to be added and the table has NO dependancies - the steps would be: 1. Create scripts using T-SQL for any indexes & primary keys that already exist on the table [these scripts are included below] 2. Drop the table 3. Re-create the table from the meta-layer that has the new column 4. Execute the scripts created in step#1 5. Populate the table using BulkCopy

以上通过 .NET 程序集启动,每天在 3 个并发流中运行.

The above is initiated via a .NET assembly and runs in 3 concurrent streams on a daily basis.

我在第 1 步中收到死锁错误 - 当我访问 INFORMATION_SCHEMA 表以编写索引/键的脚本时.我在这些脚本中使用了 WITH(NOLOCK) 提示,认为当这些操作的 3 个流同时运行时,这应该可以防止任何锁定.一个表只能在 1 个流中处理(创建或脚本).

I am receiving a deadlock error in step #1 - when I access the INFORMATION_SCHEMA tables to script out the indexes/keys. I have used the hint WITH(NOLOCK) in these scripts thinking this should prevent any locking when 3 streams of these actions are running concurrently. A table can only be processed (the create or scripting) in 1 stream.

还有什么我需要做的吗???

Is there something more I need to do???

非常感谢任何评论.

[脚本]

ALTER Procedure [dbo].[s$spScriptPrimaryKeyForTable]
@Tablename varchar(100)
AS 


-- Get all existing primary keys
DECLARE cPK CURSOR FOR
SELECT TABLE_NAME, CONSTRAINT_NAME 
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WITH(NOLOCK)
WHERE upper(TABLE_NAME)=upper(@Tablename)
ORDER BY TABLE_NAME

DECLARE @PkTable SYSNAME
DECLARE @PkName SYSNAME

-- Loop through all the primary keys
OPEN cPK
FETCH NEXT FROM cPK INTO @PkTable, @PkName
WHILE (@@FETCH_STATUS = 0)
BEGIN
DECLARE @PKSQL NVARCHAR(4000) SET @PKSQL = ''
SET @PKSQL = 'ALTER TABLE ' + @PkTable + ' ADD CONSTRAINT ' + @PkName + ' PRIMARY KEY CLUSTERED ('

-- Get all columns for the current primary key
DECLARE cPKColumn CURSOR FOR
SELECT COLUMN_NAME 
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WITH(NOLOCK)
WHERE TABLE_NAME = @PkTable AND CONSTRAINT_NAME = @PkName
ORDER BY ORDINAL_POSITION
OPEN cPKColumn

DECLARE @PkColumn SYSNAME
DECLARE @PkFirstColumn BIT SET @PkFirstColumn = 1
-- Loop through all columns and append the sql statement
FETCH NEXT FROM cPKColumn INTO @PkColumn
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF (@PkFirstColumn = 1)
SET @PkFirstColumn = 0
ELSE
SET @PKSQL = @PKSQL + ', '

SET @PKSQL = @PKSQL + @PkColumn

FETCH NEXT FROM cPKColumn INTO @PkColumn
END
CLOSE cPKColumn
DEALLOCATE cPKColumn

SET @PKSQL = @PKSQL + ')'
-- Print the primary key statement
-- PRINT @PKSQL

FETCH NEXT FROM cPK INTO @PkTable, @PkName
END
CLOSE cPK
DEALLOCATE cPK


SELECT ISNULL(@PKSQL,' ')

================

ALTER Procedure [dbo].[s$spScriptIndexesForTable]
@Tablename varchar(100)

AS 

DECLARE @RetVal varchar(4000)
SET @RetVal = ''

-- Get all existing indexes, but NOT the primary keys 
DECLARE cIX CURSOR FOR 
SELECT OBJECT_NAME(SI.Object_ID), SI.Object_ID, SI.Name, SI.Index_ID 
FROM Sys.Indexes SI WITH(NOLOCK)
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC WITH(NOLOCK) ON SI.Name = TC.CONSTRAINT_NAME AND OBJECT_NAME(SI.Object_ID) = TC.TABLE_NAME 
WHERE TC.CONSTRAINT_NAME IS NULL 
AND OBJECTPROPERTY(SI.Object_ID, 'IsUserTable') = 1 
AND upper(OBJECT_NAME(SI.Object_ID))=upper(@Tablename)
ORDER BY OBJECT_NAME(SI.Object_ID), SI.Index_ID 

DECLARE @IxTable SYSNAME 
DECLARE @IxTableID INT 
DECLARE @IxName SYSNAME 
DECLARE @IxID INT 

-- Loop through all indexes 
OPEN cIX 
FETCH NEXT FROM cIX INTO @IxTable, @IxTableID, @IxName, @IxID 
WHILE (@@FETCH_STATUS = 0) 
BEGIN 
DECLARE @IXSQL NVARCHAR(4000) 
--SET @PKSQL = '' 
SET @IXSQL = 'CREATE ' 

-- Check if the index is unique 
IF (INDEXPROPERTY(@IxTableID, @IxName, 'IsUnique') = 1) 
SET @IXSQL = @IXSQL + 'UNIQUE ' 
-- Check if the index is clustered 
IF (INDEXPROPERTY(@IxTableID, @IxName, 'IsClustered') = 1) 
SET @IXSQL = @IXSQL + 'CLUSTERED ' 

SET @IXSQL = @IXSQL + 'INDEX ' + @IxName + ' ON [' + @IxTable + '] (' 

-- Get all columns of the index 
DECLARE cIxColumn CURSOR FOR 
SELECT SC.Name,IC.[is_included_column],IC.is_descending_key 
FROM Sys.Index_Columns IC WITH(NOLOCK)
JOIN Sys.Columns SC WITH(NOLOCK) ON IC.Object_ID = SC.Object_ID AND IC.Column_ID = SC.Column_ID 
WHERE IC.Object_ID = @IxTableID AND Index_ID = @IxID 
ORDER BY IC.Index_Column_ID,IC.is_included_column 

DECLARE @IxColumn SYSNAME 
DECLARE @IxIncl bit 
DECLARE @Desc bit 
DECLARE @IxIsIncl bit set @IxIsIncl = 0 
DECLARE @IxFirstColumn BIT SET @IxFirstColumn = 1 

-- Loop throug all columns of the index and append them to the CREATE statement 
OPEN cIxColumn 
FETCH NEXT FROM cIxColumn INTO @IxColumn, @IxIncl, @Desc 
WHILE (@@FETCH_STATUS = 0) 
BEGIN 

IF (@IxFirstColumn = 1) 
BEGIN 
SET @IxFirstColumn = 0 
END 
ELSE 
BEGIN 
--check to see if it's an included column 
IF ((@IxIsIncl = 0) AND (@IxIncl = 1)) 
BEGIN 
SET @IxIsIncl = 1 
SET @IXSQL = @IXSQL + ') INCLUDE (' 
END 
ELSE 
BEGIN 
SET @IXSQL = @IXSQL + ', ' 
END 
END 

SET @IXSQL = @IXSQL + '[' + @IxColumn + ']' 
--check to see if it's DESC 
IF @Desc = 1 
SET @IXSQL = @IXSQL + ' DESC' 

FETCH NEXT FROM cIxColumn INTO @IxColumn, @IxIncl, @Desc 
END 
CLOSE cIxColumn 
DEALLOCATE cIxColumn 

SET @IXSQL = @IXSQL + ')' 

-- Print out the CREATE statement for the index 
--SELECT 'IXSQL: ' + @IXSQL
IF @RetVal IS NULL
SET @RetVal = ''
--SELECT 'Retval: ' + @RetVal
SET @RetVal = @RetVal + @IXSQL + ' ' 

FETCH NEXT FROM cIX INTO @IxTable, @IxTableID, @IxName, @IxID 
END 

CLOSE cIX 
DEALLOCATE cIX 

SELECT ISNULL(@RetVal,' ')

推荐答案

  1. INFORMATION_SCHEMA 视图就是 - 视图.您无法更新它们,因此它们不太可能导致任何死锁.如果您想确定真正的来源(我认为这与您的更改有关,或与您未显示的光标内的其他代码有关,或与调用这些过程结合调用的其他代码 - 因为选择反对视图然后选择变量不能成为原因),我建议阅读 Gail Shaw 关于解释死锁的博文.

尽管 (1) 我仍然建议使用比 INFORMATION_SCHEMA 更现代的目录视图.例如,可以从 sys.key_constraints 导出相同的信息.

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.

您正在使用默认光标选项;并且您正在嵌套游标.如果你最终仍然使用游标,你应该养成使用较少资源密集型游标的习惯(例如 LOCAL STATIC FORWARD_ONLY READ_ONLY).

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).

您实际上不需要游标来执行此操作.下面是我将如何重写 PK 表脚本:

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

有了这个函数,ScriptIndexes 过程就很简单了:

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

请注意,我的解决方案不假定 PK 是聚集的(您的 PK 脚本硬编码 CLUSTERED 但您的索引脚本假定任何索引都可以聚集).我还忽略了其他属性,例如文件组、分区或过滤索引(无论如何,2005 年都不支持).

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).

这篇关于查询 INFORMATION_SCHEMA 时死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆