Я читал о гарантиях согласованности данных 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 !?