returns 为每次调用计算的唯一 INT 的 SPROC
SPROC that returns unique calculated INT for each call
我在我的应用程序中实现了一个事件记录系统,用于从我的代码中保存一些事件类型,因此我创建了一个 table 来存储日志类型和一个增量 ID:
|LogType|CurrentId|
|info | 1 |
|error | 5 |
还有一个table保存具体的日志记录
|LogType|IdLog|Message |
|info |1 |Process started|
|error |5 |some error |
因此,每次我需要保存新记录时,我都会调用 SPROC 来计算日志类型的新 ID,基本上是:newId = (currentId + 1)
。但是我在计算时遇到了一个问题,因为如果多个进程同时调用 SPROC,"generated 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
非常感谢您帮助将存储过程修复为 return 每次调用的唯一 ID。
它必须在 SQL Server 2005 上运行。谢谢
你能用标识列实现你想要的吗?
那你可以让SQL服务器保证唯一性。
示例:
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
再编辑一下:我认为这是对现有存储过程的改进,满足要求。在更新中直接包含新值计算,最终 return 直接来自 table 的值(而不是来自可能过时的变量)并避免递归。
完整的测试脚本如下。
if object_id('Whosebug_usp_getlogid') is not null
begin
drop procedure Whosebug_usp_getlogid;
end
go
if object_id('Whosebug_TBL_ApplicationLogId') is not null
begin
drop table Whosebug_TBL_ApplicationLogId;
end
go
create table Whosebug_TBL_ApplicationLogId(CurrentID int, LogType nvarchar(max));
go
create PROCEDURE [dbo].[Whosebug_USP_GETLOGID](@LogType VARCHAR(MAX))
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
BEGIN TRY
DECLARE @IdCreated VARCHAR(MAX)
IF EXISTS (SELECT * FROM Whosebug_TBL_ApplicationLogId WHERE LogType = @LogType)
BEGIN
UPDATE Whosebug_TBL_APPLICATIONLOGID
SET CurrentId = CurrentID + 1
WHERE LogType = @LogType
END
ELSE
BEGIN
--first time: insert 0.
INSERT INTO Whosebug_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 Whosebug_TBL_APPLICATIONLOGID where LogType = @LogType;
IF @@TRANCOUNT > 0
begin
COMMIT TRANSACTION
END
end
go
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType2';
exec Whosebug_USP_GETLOGID 'TestLogType2';
exec Whosebug_USP_GETLOGID 'TestLogType2';
这里你能做的不多,但验证一下:
- table TBL_ApplicationLogId 按 LogType 列索引。
- @LogType sp参数与tableTBL_ApplicationLogId中的LogType列是相同的数据类型,所以它实际上可以使用它存在的索引if/when。
- 如果您遇到并发问题,也许在 select 和更新期间强制 table TBL_ApplicationLogId 上的锁定级别会有所帮助。只需在 table 名称后添加 (ROWLOCK),例如:TBL_ApplicationLogId (ROWLOCK)
您希望您的递增和读取是原子的,并保证没有其他进程可以在两者之间递增。
您还需要确保日志类型存在,并确保它是线程安全的。
这是我的处理方式,但建议您仔细阅读 SQL Server 2005 中的所有工作原理,因为我已经将近 8 年没有处理这些事情了。
这应该以原子方式完成这两个操作,并且也没有事务,以防止线程相互阻塞。 (不仅仅是性能,还可以避免在与其他代码交互时出现DEADLOCKs。)
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
我在我的应用程序中实现了一个事件记录系统,用于从我的代码中保存一些事件类型,因此我创建了一个 table 来存储日志类型和一个增量 ID:
|LogType|CurrentId|
|info | 1 |
|error | 5 |
还有一个table保存具体的日志记录
|LogType|IdLog|Message |
|info |1 |Process started|
|error |5 |some error |
因此,每次我需要保存新记录时,我都会调用 SPROC 来计算日志类型的新 ID,基本上是:newId = (currentId + 1)
。但是我在计算时遇到了一个问题,因为如果多个进程同时调用 SPROC,"generated Id" 是相同的,所以我得到具有相同 ID 的日志记录,并且每条记录都必须是唯一的 ID .
这是我为 SQL Server 2005:
编写的 SPROCALTER 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
非常感谢您帮助将存储过程修复为 return 每次调用的唯一 ID。
它必须在 SQL Server 2005 上运行。谢谢
你能用标识列实现你想要的吗? 那你可以让SQL服务器保证唯一性。
示例:
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
再编辑一下:我认为这是对现有存储过程的改进,满足要求。在更新中直接包含新值计算,最终 return 直接来自 table 的值(而不是来自可能过时的变量)并避免递归。
完整的测试脚本如下。
if object_id('Whosebug_usp_getlogid') is not null
begin
drop procedure Whosebug_usp_getlogid;
end
go
if object_id('Whosebug_TBL_ApplicationLogId') is not null
begin
drop table Whosebug_TBL_ApplicationLogId;
end
go
create table Whosebug_TBL_ApplicationLogId(CurrentID int, LogType nvarchar(max));
go
create PROCEDURE [dbo].[Whosebug_USP_GETLOGID](@LogType VARCHAR(MAX))
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
BEGIN TRY
DECLARE @IdCreated VARCHAR(MAX)
IF EXISTS (SELECT * FROM Whosebug_TBL_ApplicationLogId WHERE LogType = @LogType)
BEGIN
UPDATE Whosebug_TBL_APPLICATIONLOGID
SET CurrentId = CurrentID + 1
WHERE LogType = @LogType
END
ELSE
BEGIN
--first time: insert 0.
INSERT INTO Whosebug_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 Whosebug_TBL_APPLICATIONLOGID where LogType = @LogType;
IF @@TRANCOUNT > 0
begin
COMMIT TRANSACTION
END
end
go
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType1';
exec Whosebug_USP_GETLOGID 'TestLogType2';
exec Whosebug_USP_GETLOGID 'TestLogType2';
exec Whosebug_USP_GETLOGID 'TestLogType2';
这里你能做的不多,但验证一下:
- table TBL_ApplicationLogId 按 LogType 列索引。
- @LogType sp参数与tableTBL_ApplicationLogId中的LogType列是相同的数据类型,所以它实际上可以使用它存在的索引if/when。
- 如果您遇到并发问题,也许在 select 和更新期间强制 table TBL_ApplicationLogId 上的锁定级别会有所帮助。只需在 table 名称后添加 (ROWLOCK),例如:TBL_ApplicationLogId (ROWLOCK)
您希望您的递增和读取是原子的,并保证没有其他进程可以在两者之间递增。
您还需要确保日志类型存在,并确保它是线程安全的。
这是我的处理方式,但建议您仔细阅读 SQL Server 2005 中的所有工作原理,因为我已经将近 8 年没有处理这些事情了。
这应该以原子方式完成这两个操作,并且也没有事务,以防止线程相互阻塞。 (不仅仅是性能,还可以避免在与其他代码交互时出现DEADLOCKs。)
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