SPROC为每次调用返回唯一的计算INT

时间:2015-08-20 14:15:07

标签: sql tsql sql-server-2005

我正在我的应用程序中实现一个事件记录系统,以便从我的代码中保存一些事件类型,所以我创建了一个表来存储日志类型和增量ID:

|LogType|CurrentId|
|info   |    1    |
|error  |    5    |

还有一个用于保存具体日志记录的表

|LogType|IdLog|Message        |
|info   |1    |Process started|
|error  |5    |some error     |

因此,每次我需要保存新记录时,我都会调用SPROC来计算日志类型的新ID,基本上是:newId = (currentId + 1)。但我面临着这个计算的问题,因为如果多个进程同时调用SPROC“生成的Id”是相同的,那么我将获得具有相同Id的日志记录,并且每个记录必须是Id唯一的。

这是我为 SQL Server 2005 编写的SPROC:

ALTER PROCEDURE [dbo].[usp_GetLogId]
    @LogType VARCHAR(MAX)
AS
BEGIN

    SET NOCOUNT ON;

    BEGIN TRANSACTION

    BEGIN TRY

    DECLARE @IdCreated VARCHAR(MAX)

    IF EXISTS (SELECT * FROM TBL_ApplicationLogId WHERE LogType = @LogType)
    BEGIN
        DECLARE @CurrentId BIGINT
        SET @CurrentId = (SELECT CurrentId FROM TBL_ApplicationLogId WHERE LogType = @LogType)

        DECLARE @NewId BIGINT
        SET @NewId = (@CurrentId + 1)

        UPDATE TBL_ApplicationLogId
        SET CurrentId = @NewId
        WHERE LogType = @LogType

        SET @IdCreated = CONVERT(VARCHAR, @NewId)
    END
    ELSE
    BEGIN
        INSERT INTO TBL_ApplicationLogId VALUES(@LogType, 0)

        EXEC @IdCreated = usp_GetLogId @LogType
    END

    END TRY
    BEGIN CATCH

    DECLARE @ErrorMessage NVARCHAR(MAX)
    SET @ErrorMessage = ERROR_MESSAGE()

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;

    RAISERROR (@ErrorMessage, 16, 1)

    END CATCH

    IF @@TRANCOUNT > 0
        COMMIT TRANSACTION

    SELECT @IdCreated
END

非常感谢您帮助修复了每次调用时返回唯一身份验证功能。

它必须在 SQL Server 2005 上工作。感谢

3 个答案:

答案 0 :(得分:1)

你能用标识栏实现你想要的吗? 然后,您可以让SQL Server保证唯一性。

示例:

create table my_test_table
(
  ID int identity
 ,SOMEVALUE nvarchar(100)
);

insert into my_test_table(somevalue)values('value1');
insert into my_test_table(somevalue)values('value2');

select * from my_test_table

如果必须出于某种原因自行发布新ID值,请尝试使用序列,如下所示:

if object_id('my_test_table') is not null
begin
  drop table my_test_table;
end;
go

create table my_test_table
(
  ID int
 ,SOMEVALUE nvarchar(100)
);
go

if object_id('my_test_sequence') is not null
begin
  drop sequence my_test_sequence;
end;
go

CREATE SEQUENCE my_test_sequence
  AS INT  --other options are here: https://msdn.microsoft.com/en-us/library/ff878091.aspx
  START WITH 1
  INCREMENT BY 1
  MINVALUE 0
  NO MAXVALUE;
go

insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value1');
insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value2');
insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value3');

select * from my_test_table

还有一个编辑:我认为这是对现有存储过程的一种改进,给定了要求。直接在UPDATE中包含新值计算,最终直接从表中返回值(而不是从可能过时的变量中返回)并避免递归。

完整的测试脚本如下。

if object_id('STACKOVERFLOW_usp_getlogid') is not null
begin
  drop procedure STACKOVERFLOW_usp_getlogid;
end
go

if object_id('STACKOVERFLOW_TBL_ApplicationLogId') is not null
begin
  drop table STACKOVERFLOW_TBL_ApplicationLogId;
end
go

create table STACKOVERFLOW_TBL_ApplicationLogId(CurrentID int, LogType nvarchar(max));
go

create PROCEDURE [dbo].[STACKOVERFLOW_USP_GETLOGID](@LogType VARCHAR(MAX))
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRANSACTION
  BEGIN TRY
    DECLARE @IdCreated VARCHAR(MAX)
    IF EXISTS (SELECT * FROM STACKOVERFLOW_TBL_ApplicationLogId WHERE LogType = @LogType)
    BEGIN
      UPDATE STACKOVERFLOW_TBL_APPLICATIONLOGID
      SET CurrentId = CurrentID + 1
      WHERE LogType = @LogType
    END
    ELSE
    BEGIN
      --first time: insert 0.
      INSERT INTO STACKOVERFLOW_TBL_ApplicationLogId(CurrentID,LogType) VALUES(0,@LogType);
    END
  END TRY
  BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(MAX)
    SET @ErrorMessage = ERROR_MESSAGE()
    IF @@TRANCOUNT > 0
    begin
      ROLLBACK TRANSACTION;
    end
    RAISERROR(@ErrorMessage, 16, 1);
  END CATCH
  select CurrentID from STACKOVERFLOW_TBL_APPLICATIONLOGID where LogType = @LogType;

  IF @@TRANCOUNT > 0
  begin
    COMMIT TRANSACTION
  END
end
go

exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';

答案 1 :(得分:0)

你可以在这里做的不多,但要验证:

  • 表TBL_ApplicationLogId由列LogType索引。
  • @LogType sp参数与表TBL_ApplicationLogId中的列LogType具有相同的数据类型,因此它实际上可以使用索引if / when它。
  • 如果您遇到并发问题,可能在选择和更新期间强制表TBL_ApplicationLogId上的锁级别可以提供帮助。只需在表名后添加(ROWLOCK),例如:TBL_ApplicationLogId(ROWLOCK)

答案 2 :(得分:0)

您希望增量和读取是原子的,并保证其他进程之间不能增量。

您还需要确保日志类型存在,并再次确保它是线程安全的。

以下是我将如何进行此操作,但建议您阅读SQL Server 2005中的所有工作方式,因为我近8年来没有处理过这些问题。

这应该以原子方式完成两个动作,也没有事务,以防止线程相互阻塞。 (不仅仅是性能,还要在与其他代码交互时避免 DEADLOCK 。)

ALTER PROCEDURE [dbo].[usp_GetLogId]
    @LogType VARCHAR(MAX)
AS
BEGIN


    SET NOCOUNT ON;


    -- Hold our newly created id in a temp table, so we can use OUTPUT

    DECLARE @new_id TABLE (id BIGINT);


    -- I think this is thread safe, doing all things in a single statement
    ----> Check that the log-type has no records
    ----> If so, then insert an initialising row
    ----> Output the newly created id into our temp table

    INSERT INTO
        TBL_ApplicationLogId (
            LogType,
            CurrentId
        )
    OUTPUT
        INSERTED.CurrentID
    INTO
        @new_id
    SELECT
        @LogType, 1
    FROM
        TBL_ApplicationLogId
    WHERE
        LogType = @LogType
    GROUP BY
        LogType
    HAVING
        COUNT(*) = 0
    ;


    -- I think this is thread safe, doing all things in a single statement
    ----> Ensure we don't already have a new id created
    ----> Increment the current id
    ----> Output it to our temp table

    UPDATE
        TBL_ApplicationLogId
    SET
        CurrentId = CurrentId + 1
    OUTPUT
        INSERTED.CurrentID
    INTO
        @new_id
    WHERE
        LogType = @LogType
        AND NOT EXISTS (SELECT * FROM @new_id)
    ;


    -- Select the result from our temp table
    ----> It must be populated either from the INSERT or the UPDATE

    SELECT
        MAX(id)    -- MAX used to tell the system that it's returning a scalar
    FROM
        @new_id
    ;


END