Атомный UPSERT в SQL Server 2005

Когда используются пространства имен, они также должны использоваться в запросе XPath. Ваш запрос XPath будет работать только с элементами без пространства имен (как это можно проверить, удалив пространство имен из вашего XML).

Вот пример, показывающий, как вы создаете и передаете менеджер пространства имен:

var xml = ... XML from your post ...;

var xmlReader = XmlReader.Create( new StringReader(xml) ); // Or whatever your source is, of course.
var myXDocument = XDocument.Load( xmlReader );
var namespaceManager = new XmlNamespaceManager( xmlReader.NameTable ); // We now have a namespace manager that knows of the namespaces used in your document.
namespaceManager.AddNamespace( "prefix", "http://www.MyNamespace.ca/MyPath" ); // We add an explicit prefix mapping for our query.

var result = myXDocument.XPathSelectElement(
    "//prefix:Plugin/prefix:UI[1]/prefix:PluginPageCategory[1]/prefix:Page[1]/prefix:Group[1]/prefix:CommandRef[2]",
    namespaceManager
); // We use that prefix against the elements in the query.

Console.WriteLine(result); // <CommandRef ...> element is printed.

Надеюсь, это поможет.

48
задан Community 23 May 2017 в 12:13
поделиться

5 ответов

INSERT INTO <table>
SELECT <natural keys>, <other stuff...>
FROM <table>
WHERE NOT EXISTS
   -- race condition risk here?
   ( SELECT 1 FROM <table> WHERE <natural keys> )

UPDATE ...
WHERE <natural keys>
  • в первом INSERT есть состояние гонки. Ключ может не существовать во время внутреннего запроса SELECT, но существует во время INSERT, что приводит к нарушению ключа.
  • существует состояние гонки между INSERT и UPDATE. Ключ может существовать при проверке во внутреннем запросе INSERT, но исчезнет к моменту выполнения UPDATE.

Что касается второго условия гонки, можно утверждать, что ключ все равно был бы удален параллельным потоком, так что на самом деле это не потерянное обновление.

Оптимальное решение обычно состоит в том, чтобы попробовать наиболее вероятный случай и обработать ошибку в случае сбоя (конечно, внутри транзакции):

  • если ключ, вероятно, отсутствует, всегда вставляйте первым. Обработка нарушения уникального ограничения, откат к обновлению.
  • если ключ, вероятно, присутствует, всегда обновляйте первым. Вставить, если строка не найдена. Обработка возможного нарушения уникального ограничения, откат к обновлению.

Помимо корректности, этот шаблон также оптимален с точки зрения скорости: более эффективно пытаться вставить и обработать исключение, чем создавать ложные блокировки. Блокировки означают чтение логической страницы (что может означать чтение физической страницы), а ввод-вывод (даже логический) дороже, чем SEH.

Обновление @Peter

Почему ни один оператор не является «атомарным»? Допустим, у нас есть тривиальная таблица:

create table Test (id int primary key);

Теперь, если бы я запустил этот единственный оператор из двух потоков в цикле, он был бы «атомарным», как вы говорите, условие отсутствия гонки может существовать:

  insert into Test (id)
    select top (1) id
    from Numbers n
    where not exists (select id from Test where id = n.id); 

Тем не менее всего за пару секунд происходит нарушение первичного ключа:

Сообщение 2627, уровень 14, состояние 1, строка 4
Нарушение ограничения PRIMARY KEY 'PK__Test__24927208'. Невозможно вставить повторяющийся ключ в объект 'dbo.Test'.

Почему? Вы правы в том, что план запроса SQL сделает «правильную вещь» на DELETE ... FROM ... JOIN , на WITH cte AS (SELECT ... FROM) DELETE FROM cte и во многих других случаях.Но в этих случаях есть принципиальное различие: «подзапрос» относится к цели операции update или delete . Для таких случаев план запроса действительно будет использовать соответствующую блокировку, на самом деле такое поведение критично в определенных случаях, например, при реализации очередей Использование таблиц как очередей .

Но в исходном вопросе, а также в моем примере, подзапрос рассматривается оптимизатором запросов как подзапрос в запросе, а не как некий специальный запрос типа «сканирование для обновления», который требует специальной защиты от блокировки. В результате параллельный наблюдатель может наблюдать выполнение подзапроса как отдельную операцию, что нарушает «атомарное» поведение оператора. Если не будут приняты особые меры предосторожности, несколько потоков могут попытаться вставить одно и то же значение, оба убеждены, что они проверили, и значение еще не существует. Только один может добиться успеха, другой попадет в нарушение ПК. QED.

29
ответ дан 26 November 2019 в 19:04
поделиться

РЕДАКТИРОВАТЬ : Ремус верен, условная вставка с предложением где не гарантирует согласованного состояние между коррелированным подзапросом и вставкой таблицы.

Возможно, правильные подсказки таблицы могут привести к согласованному состоянию. INSERT

WITH (TABLOCKX, HOLDLOCK) вроде работает, но я понятия не имею, является ли это оптимальным уровнем блокировки для условной вставки.

В тривиальном тесте, подобном описанному Ремусом, TABLOCKX, HOLDLOCK показали ~ 5-кратный объем вставки без табличных подсказок и без ошибок PK или курса.

ОРИГИНАЛЬНЫЙ ОТВЕТ, НЕПРАВИЛЬНЫЙ:

Это атомарно?

Да, условная вставка с предложением where является атомарной, а ваш INSERT ... WHERE NOT EXISTS () ... UPDATE - это правильный способ выполнить UPSERT.

Я бы добавил IF @@ ROWCOUNT = 0 между INSERT и UPDATE:

INSERT INTO <table>
SELECT <natural keys>, <other stuff...>
WHERE NOT EXISTS
   -- no race condition here
   ( SELECT 1 FROM <table> WHERE <natural keys> )

IF @@ROWCOUNT = 0 BEGIN
  UPDATE ...
  WHERE <natural keys>
END

Отдельные операторы всегда выполняются внутри транзакции, либо их собственные ( autocommit и неявный ) или вместе с другими операторами ( явный ).

3
ответ дан 26 November 2019 в 19:04
поделиться

У меня есть один трюк замечено - это попытаться выполнить INSERT и, если это не удастся, выполнить UPDATE.

2
ответ дан 26 November 2019 в 19:04
поделиться

Вы можете использовать блокировки приложений: (sp_getapplock) http://msdn.microsoft.com/en-us/library/ms189823.aspx

2
ответ дан 26 November 2019 в 19:04
поделиться

Передавать подсказки обновления, блокировки строк и блокировки при проверке существования строки. Holdlock обеспечивает сериализацию всех вставок; rowlock разрешает одновременное обновление существующих строк.

Обновления могут по-прежнему блокироваться, если ваш ПК является bigint, поскольку внутреннее хеширование является вырожденным для 64-битных значений.

begin tran -- default read committed isolation level is fine

if not exists (select * from <table> with (updlock, rowlock, holdlock) where <PK = ...>
    -- insert
else
    -- update

commit
6
ответ дан 26 November 2019 в 19:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: