БЕЗОПАСНОСТЬ ТРАНЗАКЦИИ! Для версий SQLServer до 2012 года ... (спасибо Мэтту Дж.)
В этом обсуждении упускается одна вещь - безопасность транзакций. Если вы получаете номер из последовательности, этот номер должен быть уникальным, и никакое другое приложение или код не может получить этот номер. В моем случае мы часто извлекаем уникальные номера из последовательностей, но фактическая транзакция может занимать значительное количество времени, поэтому мы не хотим, чтобы кто-либо еще получал такой же номер, прежде чем мы совершим транзакцию.
Нам нужно было подражать поведению последовательностей оракула , где число было зарезервировано при извлечении.
Мое решение состоит в том, чтобы использовать xp_cmdshell для получения отдельного сеанса / транзакции в базе данных, чтобы мы могли немедленно обновить последовательность для всей базы данных даже до завершения транзакции.
--it is used like this:
-- use the sequence in either insert or select:
Insert into MyTable Values (NextVal('MySequence'), 'Foo');
SELECT NextVal('MySequence');
--you can make as many sequences as you want, by name:
SELECT NextVal('Mikes Other Sequence');
--or a blank sequence identifier
SELECT NextVal('');
Для решения требуется одна таблица для хранения значений использованной последовательности и процедура , которая создает вторую автономную транзакцию , чтобы гарантировать, что параллельные сеансы не запутываются. Вы можете иметь столько уникальных последовательностей, сколько захотите, на них ссылаются по имени. Приведенный ниже пример кода изменен, чтобы не запрашивать пользователя и метку даты в таблице истории последовательности (для аудита), но я подумал, что менее сложный вариант лучше для примера; -).
CREATE TABLE SequenceHolder(SeqName varchar(40), LastVal int);
GO
CREATE function NextVAL(@SEQname varchar(40))
returns int
as
begin
declare @lastval int
declare @barcode int;
set @lastval = (SELECT max(LastVal)
FROM SequenceHolder
WHERE SeqName = @SEQname);
if @lastval is null set @lastval = 0
set @barcode = @lastval + 1;
--=========== USE xp_cmdshell TO INSERT AND COMMINT NOW, IN A SEPERATE TRANSACTION =============================
DECLARE @sql varchar(4000)
DECLARE @cmd varchar(4000)
DECLARE @recorded int;
SET @sql = 'INSERT INTO SequenceHolder(SeqName, LastVal) VALUES (''' + @SEQname + ''', ' + CAST(@barcode AS nvarchar(50)) + ') '
SET @cmd = 'SQLCMD -S ' + @@servername +
' -d ' + db_name() + ' -Q "' + @sql + '"'
EXEC master..xp_cmdshell @cmd, 'no_output'
--===============================================================================================================
-- once submitted, make sure our value actually stuck in the table
set @recorded = (SELECT COUNT(*)
FROM SequenceHolder
WHERE SeqName = @SEQname
AND LastVal = @barcode);
--TRIGGER AN ERROR
IF (@recorded != 1)
return cast('Barcode was not recorded in SequenceHolder, xp_cmdshell FAILED!! [' + @cmd +']' as int);
return (@barcode)
end
GO
COMMIT;
Теперь, чтобы заставить эту процедуру работать, вам нужно включить xp_cmdshell, есть много хороших описаний того, как это сделать, вот мои личные заметки, которые я сделал, когда пытался заставить вещи работать. Основная идея заключается в том, что вам необходимо включить xp_cmdshell в SQLServer Surface. Это конфигурация, и вам нужно установить учетную запись пользователя в качестве учетной записи, под которой будет запускаться команда xp_cmdshell, которая будет обращаться к базе данных, чтобы вставить порядковый номер и зафиксировать его.
--- LOOSEN SECURITY SO THAT xp_cmdshell will run
---- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO
---- To update the currently configured value for advanced options.
RECONFIGURE
GO
---- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
GO
---- To update the currently configured value for this feature.
RECONFIGURE
GO
—-Run SQLServer Management Studio as Administrator,
—- Login as domain user, not sqlserver user.
--MAKE A DATABASE USER THAT HAS LOCAL or domain LOGIN! (not SQL server login)
--insure the account HAS PERMISSION TO ACCESS THE DATABASE IN QUESTION. (UserMapping tab in User Properties in SQLServer)
—grant the following
GRANT EXECUTE on xp_cmdshell TO [domain\user]
—- run the following:
EXEC sp_xp_cmdshell_proxy_account 'domain\user', 'pwd'
--alternative to the exec cmd above:
create credential ##xp_cmdshell_proxy_account## with identity = 'domain\user', secret = 'pwd'
-—IF YOU NEED TO REMOVE THE CREDENTIAL USE THIS
EXEC sp_xp_cmdshell_proxy_account NULL;
-—ways to figure out which user is actually running the xp_cmdshell command.
exec xp_cmdshell 'whoami.exe'
EXEC xp_cmdshell 'osql -E -Q"select suser_sname()"'
EXEC xp_cmdshell 'osql -E -Q"select * from sys.login_token"'