0

我有一个具有以下架构的表 [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

我想知道执行此类操作的正确方法是什么?一般来说,这是我想使用的模式,其中列数据在单列或多列中是唯一的,并且另一列用作键。

谢谢

4

3 回答 3

1

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

于 2009-01-20T22:45:29.567 回答
1

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

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

与先前版本的死锁很可能是由于缺少索引导致搜索过程需要很长时间。当您在 SERIALIZABLE 事务中加载服务器时,其他一切都必须等待操作完成。索引应该使操作快速,但只有测试会表明它是否足够快。请注意,您可以通过重新提交来响应失败的事务:在现实世界的情况下,希望负载将是瞬态的。

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

  • 找到名称,捕获并使用 ID。常见的
  • 未找到名称,按预期插入。常见的
  • 未找到名称,插入失败,因为在第一个精确匹配的毫秒内发布了另一个完全匹配。很稀少

我希望最后一种情况是真正的例外,因此使用异常来捕获这种非常罕见的情况比使用 SERIALIZABLE 更可取,这会产生严重的性能后果。

如果您确实期望在相同的名称中彼此在几毫秒内发布帖子是很常见的,那么将 SERIALIZABLE 事务与索引结合使用。在一般情况下它会更慢,但在找到这些帖子时会更快。

于 2009-01-20T22:53:03.060 回答
0

使用 Exists 函数可能会清理一些东西。

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
于 2009-01-20T22:44:31.923 回答