Я пытаюсь понять правильное использование инструкции multi()
и watch()
для доступа к базе данных Redis по redis-py
версии 3.5.3. Версия сервера Redis — сервер Redis v=5.0.5.
В частности, я написал и выполнил следующий код, где используется инструкция watch
с ключом keyWatch
:
r = redis.Redis()
def key_incr():
print('keyWatch before incr = ' + r.get('keyWatch').decode("utf-8"))
pipe = r.pipeline()
pipe.watch('keyWatch')
pipe.multi()
pipe.incr('keyWatch')
pipe.execute()
print('keyWatch after incr = ' + r.get('keyWatch').decode("utf-8"))
key_incr()
Предыдущий код может быть выполнен правильно, и его вывод (начальное значение keyWatch
равно 9
):
keyWatch before incr = 9
keyWatch after incr = 10
Если я уберу инструкцию multi()
из кода, она станет:
r = redis.Redis()
def key_incr():
print('keyWatch before incr = ' + r.get('keyWatch').decode("utf-8"))
pipe = r.pipeline()
pipe.watch('keyWatch')
# NOTE: here the multi() instruction is commented
#pipe.multi()
pipe.incr('keyWatch')
pipe.execute()
print('keyWatch after incr = ' + r.get('keyWatch').decode("utf-8"))
key_incr()
Его выполнение вызывает следующее исключение:
raise WatchError("Watched variable changed.")
redis.exceptions.WatchError: Watched variable changed.
Мне нужно, чтобы избежать этого, другие клиенты изменяют ключ keyWatch
внутри транзакции, но почему в моем примере кода исключение WatchError
возникает только в том случае, если инструкция multi()
отсутствует?
Спасибо
РЕДАКТИРОВАТЬ
По redis-cli monitor
(МОНИТОР в остальной части поста) я могу видеть запросы к серверу во время выполнения предыдущих 2 фрагментов кода.
В случае с инструкцией multi()
у меня следующие пожелания:
> redis-cli monitor
OK
1681733993.273545 [0 127.0.0.1:46342] "GET" "keyWatch"
1681733993.273790 [0 127.0.0.1:46342] "WATCH" "keyWatch"
1681733993.273934 [0 127.0.0.1:46342] "MULTI"
1681733993.273945 [0 127.0.0.1:46342] "INCRBY" "keyWatch" "1"
1681733993.273950 [0 127.0.0.1:46342] "EXEC"
1681733993.274279 [0 127.0.0.1:46342] "GET" "keyWatch"
На случай без инструкции multi()
у меня следующие пожелания:
> redis-cli monitor
OK
1681737498.462228 [0 127.0.0.1:46368] "GET" "keyWatch"
1681737498.462500 [0 127.0.0.1:46368] "WATCH" "keyWatch"
1681737498.462663 [0 127.0.0.1:46368] "INCRBY" "keyWatch" "1"
1681737498.463072 [0 127.0.0.1:46368] "MULTI"
1681737498.463081 [0 127.0.0.1:46368] "EXEC"
Также во втором случае присутствует инструкция MULTI
, но между ней и EXEC
нет запросов.
Исключение keyWatch
вызывается инструкцией EXEC
, поскольку МОНИТОР не показывает последний запрос "GET" "keyWatch"
(сравните с первым журналом МОНИТОРА, чтобы найти последний запрос "GET" "keyWatch"
).
Все это наводит меня на мысль, что исключение вызвано выполнением:
"INCRBY" "keyWatch" "1"
вне блокаMULTI/EXEC
.
Если кто-то может подтвердить это и объяснить лучше, поведение приветствуется.
Спасибо
Вот как это работает:
MULTI <- start queueing commands
INCR someKey <- queue this command
SET someOtherKey someValue <- queue this command
UNLINK someThirdKey <- queue this command
EXEC <- execute all the queued commands
Пока эти команды находятся в очереди, могут поступать другие команды. Эти другие команды могут изменить ключи, являющиеся частью транзакции, что может быть недопустимым. Введите СМОТРЕТЬ.
WATCH привык, ну, следить за этими ключами. Если они были изменены к моменту вызова EXEC, EXEC вернет ошибку. Затем вам нужно запустить код, чтобы повторить транзакцию (или, возможно, сгенерировать ошибку, в зависимости от ваших потребностей).
Если они не были изменены, то EXEC выполняет все команды из очереди, и жизнь продолжается.
Это работает следующим образом:
WATCH someKey someOtherKey <- watch these for changes
MULTI <- start queueing commands
INCR someKey <- queue this command
SET someOtherKey someValue <- queue this command
UNLINK someThirdKey <- queue this command
EXEC <- either error or execute queued commands
Обратите внимание, что вам не нужно отслеживать ключ как часть транзакции, как показано в моем примере выше. В этом случае мне все равно, если кто-то изменит то, что я собираюсь удалить.
Транзакции в Redis — это не то, что часто думают разработчики. Если вы хотите углубиться в детали, на сайте Redis есть руководство по транзакциям.
Я предполагаю, что redis-py ставится в очередь от вашего имени, когда вы вызываете .multi(), а затем, когда вы вызываете .exec(), он отправляет то, что было поставлено в очередь на клиенте. Если .multi() никогда не вызывается, redis-py имеет пустую очередь и поэтому отправляет ее.
Суть в том, что вызов EXEC без MULTI не имеет семантического смысла. EXEC предполагает, что был вызван MULTI. Вот как это работает.
Спасибо. Осталось понять, почему в моем коде возникает исключение, если я удаляю
multi()
инструкцию: в этом случае (сredis-cli monitor
) я вижу, что инструкцииMULTI/EXEC
выполняются несмотря на то, что инструкцииmulti()
нет, а междуMULTI/EXEC
есть невыполненные инструкции. Ваш ответ подсказывает мне, чтоEXEC
видит модификациюkeyWatch
и вызывает исключение. Вместо этого, если присутствуетmulti()
, модификацияkeyWatch
происходит междуMULTI/EXEC
, и это предотвращает Исключение. Это правильно?