#sql #sql-server #triggers #identity-column
#sql #sql-сервер #триггеры #идентификатор-столбец
Вопрос:
У меня хорошо известная проблема с триггером и @@IDENTITY
. Я создал новую таблицу аудита и триггер для вставки в нее строк аудита. Мы используем программное обеспечение, которое использует @@IDENTITY
, и это вызывает конфликт с идентификатором, сгенерированным, когда триггер вставляет новую строку. У меня нет доступа к используемому коду @@IDENTITY
.
Мне нужны идеи о том, как я могу самостоятельно сгенерировать значение идентификатора. Я не могу использовать GUID, потому что для меня важен порядок. Если я заменю столбец Id столбцом datetime со значением по умолчанию GETDATE()
, гарантирует ли это, что он будет уникальным?
Спасибо
Ответ №1:
GETDATE() не будет уникальным. Его точность такова, что несколько почти одновременных событий могут быть предоставлены в одно и то же время.
Если вы вынуждены генерировать собственные значения идентификаторов, чтобы не мешать @@IDENTITY, вы можете сделать следующее…
INSERT INTO
myTable (
id,
field1,
field2
)
SELECT
(SELECT ISNULL(MAX(id), 0) FROM myTable WITH(TABLOCKX)) 1,
@p1,
@p2
Это неявно связано с его собственной транзакцией и гарантирует уникальные значения.
Редактировать
Мой первоначальный комментарий должен был заключаться в том, что это НЕ будет работать при вставке нескольких записей, и вместо этого вам нужно будет перебирать исходные записи по отдельности, вставляя их по одному за раз.
Однако следующий пример может подойти вам для обработки наборов данных…
WITH
sorted_data AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY field1) AS set_id, -- DO NOT include a PARTITION here
*
FROM
inserted
)
INSERT INTO
myTable (
id,
field1,
field2
)
SELECT
(SELECT ISNULL(MAX(id), 0) FROM myTable WITH(TABLOCKX)) set_id,
@p1,
@p2
FROM
sorted_data
Это позволит генерировать уникальные идентификаторы для каждой строки и будет безопасно для параллельных процессов, использующих один и тот же код.
Редактировать
Я добавил WITH(TABLOCKX)
, чтобы другие процессы не читали таблицу во время ее обновления. Это не позволяет параллельным процессам устанавливать один и тот же MAX (id), а затем пытаться вставить повторяющиеся идентификаторы в новые записи.
(Единая структура запроса уже предотвращала изменение записей после их чтения, но не препятствовала другим процессам, считывающим из таблицы «между» считываемым MAX (id) и всеми новыми вставляемыми записями.)
Комментарии:
1. Безопасен ли ваш параллельный код? Даже если триггер вставляет несколько строк?
2. ДА;
This is implicitly within it's own transaction and will guarantee unique values.
3. Даже если триггер вставляет несколько строк?
4. Мне любопытно, потому что я не совсем уверен, как работает неявная транзакция, но что помешало бы генерировать перекрывающиеся наборы идентификаторов, если одновременно выполняются два оператора insert, поскольку в myTable нет блокировки при вычислении MAX (ID)?
5. Если значение
MAX(ID)
было получено в одном операторе, а затем использовано в другом операторе, создается условие гонки, если не используются транзакции и блокировка таблицы / строки.MAX(ID)
Однако, когда запрос получен в рамках подзапроса, весь запрос представляет собой один оператор и поэтому неявно уже находится в его собственной транзакции.
Ответ №2:
Я знаю, что вы, вероятно, не сможете ничего изменить, но проблема в том, что программное обеспечение использует @@IDENTITY, что не входит в область видимости. Вставки в ЛЮБУЮ таблицу изменят @@IDENTITY. Вместо этого программное обеспечение должно использовать функцию scope_identity() .