У меня есть, на первый взгляд, очень простая проблема. Я хочу иметь возможность получить уникальное значение ключа с префиксом. У меня есть таблица, которая содержит столбцы «Префикс» и «Next_Value».
Таким образом, можно подумать, что вы просто запускаете транзакцию, получаете следующее значение из этой таблицы, увеличиваете следующее значение в таблице и делаете коммит, объединяете префикс к значению, и вам гарантирован набор уникальных буквенно-цифровых ключей.
Однако под нагрузкой, когда различные серверы запускают этот хранимый процесс через ADO.NET, я обнаружил, что время от времени он возвращает один и тот же ключ разным клиентам. Это впоследствии вызывает ошибку, конечно, когда ключ используется в качестве первичного ключа!
Я наивно предполагал, что BEGIN TRAN ... COMMIT TRAN обеспечивает атомарность доступа к данным внутри области. Изучив это, я обнаружил уровни изоляции транзакций и добавил SERIALIZABLE как наиболее ограничивающий - без радости.
Create proc [dbo].[sp_get_key]
@prefix nvarchar(3)
as
set tran isolation level SERIALIZABLE
declare @result nvarchar(32)
BEGIN TRY
begin tran
if (select count(*) from key_generation_table where prefix = @prefix) = 0 begin
insert into key_generation_table (prefix, next_value) values (@prefix,1)
end
declare @next_value int
select @next_value = next_value
from key_generation_table
where prefix = @prefix
update key_generation_table
set next_value = next_value + 1
where prefix = @prefix
declare @string_next_value nvarchar(32)
select @string_next_value = convert(nvarchar(32),@next_value)
commit tran
select @result = @prefix + substring('000000000000000000000000000000',1,10-len(@string_next_value)) + @string_next_value
select @result
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRAN
DECLARE @ErrorMessage NVARCHAR(400);
DECLARE @ErrorNumber INT;
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
DECLARE @ErrorLine INT;
SELECT @ErrorMessage = N'{' + convert(nvarchar(32),ERROR_NUMBER()) + N'} ' + N'%d, Line %d, Text: ' + ERROR_MESSAGE();
SELECT @ErrorNumber = ERROR_NUMBER();
SELECT @ErrorSeverity = ERROR_SEVERITY();
SELECT @ErrorState = ERROR_STATE();
SELECT @ErrorLine = ERROR_LINE();
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END CATCH
Вот таблица генерации ключей ...
CREATE TABLE [dbo].[Key_Generation_Table](
[prefix] [nvarchar](3) NOT NULL,
[next_value] [int] NULL,
CONSTRAINT [PK__Key_Generation_T__236943A5] PRIMARY KEY CLUSTERED
(
[prefix] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]