Эта таблица усекается или строки удаляются в какой-то момент? А как часто? Для меня имеет смысл, что строки не должны быть найдены в какой-то момент, так как вы запускаете «вставить, если не существует», и в этот момент два или более запросов могут попасть в базу данных, чтобы вставить те же данные ... только один будет ... другой ничего не должен делать, если строка была вставлена до того, как ее поиск "не существует", или потерпит неудачу, если строка была вставлена после поиска.
Сейчас у меня есть только база данных Oracle, чтобы выполнить некоторые тесты, и я могу воспроизвести эту проблему. Мой режим фиксации явный:
Создайте пустую таблицу, уникальное ограничение и выберите грант, вставьте другому пользователю:
CREATE TABLE just_a_test (val NUMBER(3,0));
ALTER TABLE just_a_test ADD CONSTRAINT foobar UNIQUE (val);
GRANT SELECT, INSERT ON just_a_test TO user2;
Сеанс БД для пользователя1:
INSERT INTO just_a_test
SELECT 10
FROM DUAL
WHERE NOT EXISTS
(
SELECT 1
FROM just_a_test
WHERE val = 10
)
;
-- no commit yet...
Сеанс БД на user2:
INSERT INTO user1.just_a_test
SELECT 10
FROM DUAL
WHERE NOT EXISTS
(
SELECT 1
FROM user1.just_a_test
WHERE val = 10
)
;
-- no commit yet, the db just hangs til the other session commit...
Итак, я фиксирую первую транзакцию, вставляя строку, а затем получаю следующую ошибку в сеансе user2:
"unique constraint violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
Теперь я выполняю откат второй транзакции и снова запускаю ту же самую вставку для user2, и теперь я получаю следующий вывод:
0 rows inserted.
Возможно, ваш сценарий такой же, как этот. Надеюсь, это поможет.
РЕДАКТИРОВАТЬ
Прости. Вы задали два вопроса, а я ответил только на один Could someone explain why/how this is happening?
. Поэтому я пропустил What would be an efficient way to safely insert in one go (in other words, in a single query)?
.
Что именно означает "безопасно" для вас? Допустим, вы выполняете INSERT / SELECT из множества строк, и только одна из них дублируется по сравнению с сохраненными. Для вашего уровня "безопасности" вы должны игнорировать все вставляемые строки или игнорировать только дублированные, сохраняя остальные?
Опять же, сейчас у меня нет SQL Server, чтобы попробовать, но похоже, что вы можете сказать SQL Server, запрещать ли вставлять все строки в случае какого-либо дублирования, или запрещать только дубли, оставляя другие , То же самое справедливо для вставки одной строки ... если это дублирование, сгенерируйте ошибку ... или просто проигнорируйте ее в другой руке.
Синтаксис должен выглядеть следующим образом, чтобы игнорировать только строки dup и не выдавать ошибок:
ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = ON);
По умолчанию эта опция выключена, что означает, что SQL Server выдает ошибку и также отбрасывает вставляемые строки, не содержащие дубликаты.
Таким образом, вы сохраните синтаксис INSERT / SELECT, который выглядит хорошо, imo.
Надеюсь, это поможет.
Источники:
https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-index-option-transact-sql?view=sql-server-2008
https://stackoverflow.com/a/11207687/1977836