我有一个根据已发布的元数据层的变化动态改变我的SQL2K5表结构的过程。
例如,如果需要添加新列并且表没有依赖项,则步骤为: 1.使用T-SQL为任何索引创建脚本&表中已存在的主键[这些脚本包含在下面] 2.放下桌子 3.从具有新列的元图层重新创建表 4.执行步骤1中创建的脚本 5.使用BulkCopy填充表格
以上内容是通过.NET程序集启动的,每天在3个并发流中运行。
我在步骤#1中收到死锁错误 - 当我访问INFORMATION_SCHEMA表以编写索引/键时。我在这些脚本中使用了提示WITH(NOLOCK),认为这可以防止在这些操作的3个流同时运行时发生任何锁定。表只能在1个流中处理(创建或编写脚本)。
我还需要做些什么吗?
任何评论都非常感谢。
[脚本]
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,' ')
答案 0 :(得分:1)
INFORMATION_SCHEMA视图就是 - 视图。您无法更新它们,因此它们不太可能导致任何死锁。如果你想确定真正的来源(我假设它与你的改变有关,或光标中你没有显示的其他代码,或者你正在调用的其他代码与调用这些程序 - 因为选择反对视图然后选择变量不是原因),我建议阅读Gail Shaw's blog post on interpreting deadlocks。
尽管(1)我仍然建议使用比INFORMATION_SCHEMA更多的现代目录视图。例如,可以从sys.key_constraints派生相同的信息。
您正在使用默认光标选项;而你正在嵌套游标。如果最终仍然使用游标,则应养成使用资源较少的游标(例如LOCAL STATIC FORWARD_ONLY READ_ONLY)的习惯。
您实际上并不需要游标来执行此操作。以下是我将如何重写PK表脚本:
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
至于索引创建脚本,我认为有一种更好的方法可以做到这一点(同样没有明确的游标,不是避免光标是目标,但代码将变得更加清洁)。首先,您需要一个函数来构建索引中的键或包含列:
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过程非常简单:
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年不支持)。