列中唯一数据的正确模式是什么?

时间:2009-01-20 22:39:22

标签: concurrency unique

我有一个表[File],它具有以下架构

CREATE TABLE [dbo].[File]
(
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](256) NOT NULL,
 CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

我们的想法是FileID用作表的键,Name是表示文件的完全限定路径。

我一直在尝试创建一个存储过程,该过程将检查名称是否已被使用,如果是,则使用该记录,否则创建新记录。

但是当我强调测试代码时,许多线程一次执行存储过程我得到了不同的错误。

此版本的代码将创建死锁并在客户端上抛出死锁异常。

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION xact_File_Create
    SET XACT_ABORT ON

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

此版本的代码我最终在Name列中获得了具有相同数据的行。

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    BEGIN TRANSACTION xact_File_Create

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

我想知道采取这种行动的正确方法是什么?通常,这是我想要使用的模式,其中列数据在单列或多列中是唯一的,而另一列用作键。

由于

3 个答案:

答案 0 :(得分:1)

首先,在Name列上创建一个唯一索引。然后从客户端代码首先通过选择FileID并将Name放在where子句中来检查Name是否存在 - 如果是,则使用FileID。如果没有,请插入一个新的。

答案 1 :(得分:1)

如果您在“名称”字段上进行大量搜索,则可能需要将其编入索引(如果是主要搜索字段,则可能是唯一的,甚至可能是群集的)。由于您没有使用第一个选择中的@FileID,我只需从Name = @Name的文件中选择count(*)并查看它是否大于零(这将阻止SQL保留表中的任何锁定搜索阶段,因为没有选择列)。

您使用SERIALIZABLE级别是正确的课程,因为您的操作将影响后续查询成功或失败并且名称存在。没有该集的版本导致重复的原因是两个选择同时运行并且发现没有记录,因此两者都继续插入(创建副本)。

与先前版本的死锁很可能是由于缺少索引使搜索过程花费很长时间。在SERIALIZABLE事务中加载服务器时,其他所有内容都必须等待操作完成。索引应该使操作快速,但只有测试才能指示它是否足够快。请注意,您可以通过重新提交来响应失败的事务:在实际情况下,希望负载是瞬态的。

编辑:通过将您的表编入索引,但不使用SERIALIZABLE,您最终会遇到三种情况:

  • 找到名称,捕获并使用ID。 通用
  • 找不到名称,按预期插入。 通用
  • 找不到名称,插入失败,因为在第一个名字的毫秒内发布了另一个完全匹配。 非常罕见

我希望最后一个案例真的很特别,因此使用异常来捕获这种非常罕见的情况会比使用SERIALIZABLE更好,因为SERIALIZABLE会产生严重的性能影响。

如果您确实希望在相同的 new 名称之间以毫秒为单位发布帖子,那么请将SERIALIZABLE事务与索引结合使用。在一般情况下,它会更慢,但在找到这些帖子时会更快。

答案 2 :(得分:0)

使用Exists功能可能会稍微清理一下。

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name