Согласованность одновременных обновлений в Oracle, когда предложение WHERE зависит от старого значения - PullRequest
3 голосов
/ 08 марта 2019

Я читал о гарантиях согласованности данных Oracle и поддерживаемых уровнях изоляции транзакций (например, здесь: https://docs.oracle.com/database/121/CNCPT/consist.htm#CNCPT121), и я чувствую, что получаю много информации высокого уровня, но я не уверен как это относится к моему конкретному вопросу.

Я собираюсь описать упрощенную версию моего варианта использования и ищу убедительные ответы, желательно со ссылками, на то, как мне нужно структурировать свою транзакцию, чтобы получить желаемый результат. (Пожалуйста, не зацикливайтесь на синтаксисе или нормализации данных или даже типах данных в моем вопросе; это соломенный человек - так что, если вы понимаете, о чем я, продолжайте и сосредоточитесь на проблеме параллелизма.):)

Сценарий (упрощенно):

Многие пользователи (десятки тысяч) одновременно играют в онлайн-игру. Все игроки являются членами двух команд, красных или синих. Каждый раз, когда игрок заканчивает игру, мы хотим зарегистрировать пользователя, его командную принадлежность, временную метку и счет. Мы также хотим собрать самый высокий результат, когда-либо достигнутый каждой командой. Наша модель данных выглядит так:

// each game is logged in a table that looks kind of like this:
GAMES {
 time NUMBER,
 userid NUMBER,
 team NUMBER,
 score NUMBER
}
// high scores are tracked here, assume initial 0-score was inserted at time 0
HIGH_SCORES {
 team NUMBER,
 highscore NUMBER
}

Итак, для каждого полученного мной отчета о результатах я выполняю транзакцию, которая выглядит следующим образом

BEGIN
  UPDATE HIGH_SCORES set highscore=:1 WHERE team=:2 and :1>highscore;
  INSERT into GAMES (time, userid, team, score) VALUES (:1,:2,:3,:4);
COMMIT

Инвариант, который я хочу сохранить, состоит в том, что в любой момент времени высокий балл для каждой команды, как показано в таблице HIGH_SCORES, будет самым высоким баллом, который я бы нашел, если бы я просканировал таблицу GAMES и нашел рекорд. трудный путь.

Мое понимание уровня изоляции READ_COMMITED предполагает, что это не даст мне то, что я хочу:

Конфликт записи в транзакциях чтения совершенных

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

Возможны следующие варианты:

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

  • Если блокирующая транзакция фиксирует и снимает свои блокировки, то ожидающая транзакция переходит к запланированному обновлению новой измененной строки.

Мне кажется, что если красная команда (команда 1) имеет высокий балл 100 и два игрока одновременно предоставляют лучшие результаты, многопоточный сервер может иметь две транзакции базы данных, которые начинаются одновременно:

# Transaction A
UPDATE HIGHSCORES set highscore=150 where team=1 and 150>highscore;
INSERT into GAMES (time, userid, team, score) VALUES (9999,100,1,150);

и

# Transaction B
UPDATE HIGHSCORES set highscore=125 where team=1 and 125>highscore;
INSERT into GAMES (time, userid, team, score) VALUES (9999,101,1,125);

Таким образом (в режиме READ_COMMITED) вы можете получить следующую последовательность: (см. Таблицу 9-2 в ссылке на Oracle, на которую есть ссылки выше)

A updates highscore for red team row; oracle locks this row

B still sees the 100 score and so tries to update red team highscore; 
  oracle Blocks trasaction B because that row is now locked with a conflicting write

A inserts into the games table;

A commits;

B is unblocked, and completes the update, clobbering the 150 with a 125 and my invariant condition will be broken.

Первый вопрос - это правильное понимание READ_COMMITED?

Мое чтение SERIALIZABLE, однако:

Oracle Database разрешает сериализуемой транзакции изменять строку, только если изменения в строке, сделанные другими транзакциями, уже были зафиксированы, когда началась сериализуемая транзакция. База данных генерирует ошибку, когда сериализуемая транзакция пытается обновить или удалить данные, измененные другой транзакцией, зафиксированной после начала сериализуемой транзакции.

Предположим, что serializable также не будет работать правильно в вышеописанном сценарии, единственное отличие состоит в том, что транзакция B получит ошибку, и у меня будут варианты отката или повторной попытки. Это выполнимо, но кажется излишне трудным.

Второй вопрос - это правильное понимание SERIALIZABLE?

... И если это так, я сбит с толку. Это кажется простой, обычной вещью, которую хочется сделать. В коде я мог бы достичь этого тривиально, поставив мьютекс на тестирование и обновление высокой оценки каждой команды.

Третий и самый важный вопрос: как мне получить Oracle (или любую базу данных SQL, если на то пошло) то, что я хочу здесь?

ОБНОВЛЕНИЕ: дальнейшее чтение предполагает, что мне, вероятно, нужно сделать некоторую явную блокировку таблицы, как в (https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9015.htm) - но я не совсем точно понимаю, что мне нужно. HALP !?

Ответы [ 2 ]

3 голосов
/ 08 марта 2019

это правильное понимание READ_COMMITED?

Не совсем.Ваш сценарий - , а не , что показано в таблице 9-2 в документации, на которую вы ссылаетесь.Ваш сценарий в основном соответствует таблице 9-4.

Разница в том, что версия 9-2 (показывающая потерянное обновление) не проверяет обновляемое значение - она ​​не фильтрует существующую зарплату, который является колонкой, которую он обновляет.Версия 9-4 обновляет номер телефона, но рассматривает существующее значение для этого столбца как часть обновления, и заблокированное обновление в итоге не обновляет строки, поскольку перечитывает вновь измененное значение, которое теперь нене соответствует фильтру.

По существу, заблокированное обновление перезапускается при удалении блокировки предыдущей блокировки, поэтому оно перечитывает вновь принятые данные и использует это, чтобы решить, нужно ли обновлять строку сейчас..

Как говорится в этом документе :

Блокировки соответствуют следующим важным требованиям к базе данных:

  • Согласованность

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

  • Целостность

Данные и структуры должныотразить все изменения, внесенные в них, в правильной последовательности.

База данных Oracle обеспечивает параллелизм, согласованность и целостность данных между транзакциями посредством механизма блокировкимеханизмов надзора.Блокировка происходит автоматически и не требует никаких действий пользователя.

Последние два предложения означают, что вам не нужно беспокоиться об этом, и довольно просто проверить поведение, обновив вручную ту же строку втаблица из двух сеансов.

И при автоматические блокировки :

Блокировка DML, также называемая блокировкой данных, гарантирует целостность данных, к которым одновременно обращается несколькопользователи.Например, блокировка DML не позволяет двум клиентам купить последнюю копию книги, доступную у интернет-продавца книг.Блокировки DML предотвращают деструктивное вмешательство в одновременные конфликтующие операции DML или DDL.

В вашем случае, когда он перезапускает обновление, заблокированное в вашей транзакции B, он не находит строку для команды 1, где highscore меньше 125. Данные, которые оператор выполняет в отношении , включают зафиксированное обновление, обновленное из сеанса A, даже если это подтверждение произошло после того, как B впервые идентифицировал и запросил блокировку строки, которая - в этот момент -соответствует его фильтру.Поэтому обновлять его нечего, и обновление из сеанса А не теряется.

3 голосов
/ 08 марта 2019

Ух, длинный вопрос.Короткий ответ: READ_COMMITTED - это все, что вам нужно.

Вы не получите потерянное обновление, потому что UPDATE, выполненное транзакцией B, перезапустит после фиксации транзакции A.UPDATE будет согласованным по чтению с момента перезапуска, а не с момента отправки.

То есть в вашем примере транзакция B обновит 0 строк в HIGH_SCORES.

Хороший пример этого есть в главе 9 руководства Oracle Concepts, демонстрирующей, как Oracle защищает приложения от потерянных обновлений.

Существует хорошее объяснение того, как и почемуOracle внутренне перезапустит операторы UPDATE для согласованности чтения Тома Кайта, здесь: https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:11504247549852

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...