В прошлом уроке одна команда INCR решала задачу целиком. Но покупка книги в магазине это уже несколько действий: уменьшить остаток на складе и положить книгу в корзину покупателя. Хочется, чтобы они прошли вместе, и чтобы между ними никто не влез. Сегодня первое знакомство с двумя инструментами: транзакциями, которые склеивают команды, и пайплайнингом, который экономит время на сети. Оба подробно разберём ближе к концу раздела, а пока поймём идею и попробуем руками.
Список покупок на кассе
Представь кассу, где кассир не пробивает товары по одному, пока ты их называешь. Ты сначала выкладываешь всю корзину на ленту, кассир говорит «принято» на каждый товар, а пробивает всё разом, когда ты нажимаешь «оплатить». Пока пробивается твоя корзина, следующий покупатель ждёт: его товары не перемешаются с твоими.
Транзакция в Redis работает так же. Команды сначала копятся в очереди, а выполняются все подряд по одной кнопке.
MULTI и EXEC
Транзакцию открывает команда MULTI. После неё каждая команда отвечает QUEUED, «поставлено в очередь», а выполняется только по команде EXEC:
127.0.0.1:6379> SET stock:book:3 5
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECR stock:book:3
QUEUED
127.0.0.1:6379(TX)> RPUSH cart:user:7 book:3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 4
2) (integer) 1
Пока транзакция открыта, redis-cli подписывает приглашение пометкой (TX). EXEC вернул массив: по ответу на каждую команду из очереди, в том же порядке. Склад уменьшился до 4, в корзине появилась одна книга. Команда RPUSH кладёт значение в список, спискам посвящён следующий урок.
Главное обещание MULTI/EXEC: команды из очереди выполняются подряд, и чужие команды не встают между ними. Никто не увидит склад уже уменьшенным, а корзину ещё пустой.
Передумал до EXEC? Команда DISCARD выбрасывает очередь, ничего не выполнив:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET a 1
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> GET a
(nil)
Отката нет
Здесь Redis сильно отличается от транзакций в PostgreSQL из урока про ACID. Если команда упала уже во время EXEC, остальные всё равно выполнятся, и отменить сделанное нельзя:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET book:1:title Master
QUEUED
127.0.0.1:6379(TX)> INCR book:1:title
QUEUED
127.0.0.1:6379(TX)> SET book:1:year 1967
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> GET book:1:year
"1967"
Вторая команда упала, потому что в ключе текст, а третья всё равно записала год. В Redis нет ROLLBACK: транзакция гарантирует «подряд и без чужих», но не «всё или ничего».
Есть одно исключение. Если ошибка видна ещё до выполнения, например команда написана с неверным числом аргументов, Redis не примет транзакцию вовсе:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET x 1
QUEUED
127.0.0.1:6379(TX)> INCR
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379(TX)> SET y 2
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> GET x
(nil)
Ошибку в синтаксисе Redis замечает при постановке в очередь и отменяет всё. Ошибку в данных, вроде текста вместо числа, он видит только при выполнении, и тогда остальное проходит.
урок из курса
Урок из курса «Redis с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.