{
  "schema_version": 2,
  "id": "develop/using-commands/transactions",
  "title": "Transactions",
  "url": "https://redis.io/docs/latest/develop/using-commands/transactions/",
  "summary": "How transactions work in Redis",
  "aliases": [
    "/develop/interact/transactions",
    "/manual/transactions/",
    "/interact/transactions/",
    "/develop/reference/transactions/"
  ],
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-09-11T14:32:43+01:00",
  "page_type": "content",
  "content_hash": "5572bc3253a4a85cdaa175cc4de7fa30878e08ee339225cdefaf8fe7726a8ec9",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Redis Transactions allow the execution of a group of commands\nin a single step, they are centered around the commands \n[`MULTI`](https://redis.io/docs/latest/commands/multi), [`EXEC`](https://redis.io/docs/latest/commands/exec), [`DISCARD`](https://redis.io/docs/latest/commands/discard) and [`WATCH`](https://redis.io/docs/latest/commands/watch).\nRedis Transactions make two important guarantees:\n\n* All the commands in a transaction are serialized and executed\nsequentially. A request sent by another client will never be\nserved **in the middle** of the execution of a Redis Transaction.\nThis guarantees that the commands are executed as a single\nisolated operation.\n\n* The [`EXEC`](https://redis.io/docs/latest/commands/exec) command\ntriggers the execution of all the commands in the transaction, so\nif a client loses the connection to the server in the context of a\ntransaction before calling the [`EXEC`](https://redis.io/docs/latest/commands/exec) command none of the operations\nare performed, instead if the [`EXEC`](https://redis.io/docs/latest/commands/exec) command is called, all the\noperations are performed. When using the\n[append-only file](https://redis.io/docs/latest/operate/oss_and_stack/management/persistence#append-only-file) Redis makes sure\nto use a single write(2) syscall to write the transaction on disk.\nHowever if the Redis server crashes or is killed by the system administrator\nin some hard way it is possible that only a partial number of operations\nare registered. Redis will detect this condition at restart, and will exit with an error.\nUsing the `redis-check-aof` tool it is possible to fix the\nappend only file that will remove the partial transaction so that the\nserver can start again.\n\nStarting with version 2.2, Redis allows for an extra guarantee to the\nabove two, in the form of optimistic locking in a way very similar to a\ncheck-and-set (CAS) operation.\nThis is documented [later](#cas) on this page."
    },
    {
      "id": "usage",
      "title": "Usage",
      "role": "syntax",
      "text": "A Redis Transaction is entered using the [`MULTI`](https://redis.io/docs/latest/commands/multi) command. The command\nalways replies with `OK`. At this point the user can issue multiple\ncommands. Instead of executing these commands, Redis will queue\nthem. All the commands are executed once [`EXEC`](https://redis.io/docs/latest/commands/exec) is called.\n\nCalling [`DISCARD`](https://redis.io/docs/latest/commands/discard) instead will flush the transaction queue and will exit\nthe transaction.\n\nThe following example increments keys `foo` and `bar` atomically.\n\n[code example]\n\nAs is clear from the session above, [`EXEC`](https://redis.io/docs/latest/commands/exec) returns an\narray of replies, where every element is the reply of a single command\nin the transaction, in the same order the commands were issued.\n\nWhen a Redis connection is in the context of a [`MULTI`](https://redis.io/docs/latest/commands/multi) request,\nall commands will reply with the string `QUEUED` (sent as a Status Reply\nfrom the point of view of the Redis protocol). A queued command is\nsimply scheduled for execution when [`EXEC`](https://redis.io/docs/latest/commands/exec) is called."
    },
    {
      "id": "errors-inside-a-transaction",
      "title": "Errors inside a transaction",
      "role": "errors",
      "text": "During a transaction it is possible to encounter two kind of command errors:\n\n* A command may fail to be queued, so there may be an error before [`EXEC`](https://redis.io/docs/latest/commands/exec) is called.\nFor instance the command may be syntactically wrong (wrong number of arguments,\nwrong command name, ...), or there may be some critical condition like an out of\nmemory condition (if the server is configured to have a memory limit using the `maxmemory` directive).\n* A command may fail *after* [`EXEC`](https://redis.io/docs/latest/commands/exec) is called, for instance since we performed\nan operation against a key with the wrong value (like calling a list operation against a string value).\n\nStarting with Redis 2.6.5, the server will detect an error during the accumulation of commands.\nIt will then refuse to execute the transaction returning an error during [`EXEC`](https://redis.io/docs/latest/commands/exec), discarding the transaction.\n\n> **Note for Redis < 2.6.5:** Prior to Redis 2.6.5 clients needed to detect errors occurring prior to [`EXEC`](https://redis.io/docs/latest/commands/exec) by checking\nthe return value of the queued command: if the command replies with QUEUED it was\nqueued correctly, otherwise Redis returns an error.\nIf there is an error while queueing a command, most clients\nwill abort and discard the transaction. Otherwise, if the client elected to proceed with the transaction\nthe [`EXEC`](https://redis.io/docs/latest/commands/exec) command would execute all commands queued successfully regardless of previous errors.\n\nErrors happening *after* [`EXEC`](https://redis.io/docs/latest/commands/exec) instead are not handled in a special way:\nall the other commands will be executed even if some command fails during the transaction.\n\nThis is more clear on the protocol level. In the following example one\ncommand will fail when executed even if the syntax is right:\n\n[code example]\n\n[`EXEC`](https://redis.io/docs/latest/commands/exec) returned two-element [bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-string-reply) where one is an `OK` code and\nthe other an error reply. It's up to the client library to find a\nsensible way to provide the error to the user.\n\nIt's important to note that\n**even when a command fails, all the other commands in the queue are processed** – Redis will _not_ stop the\nprocessing of commands.\n\nAnother example, again using the wire protocol with `telnet`, shows how\nsyntax errors are reported ASAP instead:\n\n[code example]\n\nThis time due to the syntax error the bad [`INCR`](https://redis.io/docs/latest/commands/incr) command is not queued\nat all."
    },
    {
      "id": "what-about-rollbacks",
      "title": "What about rollbacks?",
      "role": "content",
      "text": "Redis does not support rollbacks of transactions since supporting rollbacks\nwould have a significant impact on the simplicity and performance of Redis."
    },
    {
      "id": "discarding-the-command-queue",
      "title": "Discarding the command queue",
      "role": "content",
      "text": "[`DISCARD`](https://redis.io/docs/latest/commands/discard) can be used in order to abort a transaction. In this case, no\ncommands are executed and the state of the connection is restored to\nnormal.\n\n[code example]\n<a name=\"cas\"></a>"
    },
    {
      "id": "optimistic-locking-using-check-and-set",
      "title": "Optimistic locking using check-and-set",
      "role": "content",
      "text": "[`WATCH`](https://redis.io/docs/latest/commands/watch) is used to provide a check-and-set (CAS) behavior to Redis\ntransactions.\n\n[`WATCH`](https://redis.io/docs/latest/commands/watch)ed keys are monitored in order to detect changes against them. If\nat least one watched key is modified before the [`EXEC`](https://redis.io/docs/latest/commands/exec) command, the\nwhole transaction aborts, and [`EXEC`](https://redis.io/docs/latest/commands/exec) returns a [Null reply](https://redis.io/docs/latest/develop/reference/protocol-spec#nil-reply) to notify that\nthe transaction failed.\n\nFor example, imagine we have the need to atomically increment the value\nof a key by 1 (let's suppose Redis doesn't have [`INCR`](https://redis.io/docs/latest/commands/incr)).\n\nThe first try may be the following:\n\n[code example]\n\nThis will work reliably only if we have a single client performing the\noperation in a given time. If multiple clients try to increment the key\nat about the same time there will be a race condition. For instance,\nclient A and B will read the old value, for instance, 10. The value will\nbe incremented to 11 by both the clients, and finally [`SET`](https://redis.io/docs/latest/commands/set) as the value\nof the key. So the final value will be 11 instead of 12.\n\nThanks to [`WATCH`](https://redis.io/docs/latest/commands/watch) we are able to model the problem very well:\n\n[code example]\n\nUsing the above code, if there are race conditions and another client\nmodifies the result of `val` in the time between our call to [`WATCH`](https://redis.io/docs/latest/commands/watch) and\nour call to [`EXEC`](https://redis.io/docs/latest/commands/exec), the transaction will fail.\n\nWe just have to repeat the operation hoping this time we'll not get a\nnew race. This form of locking is called _optimistic locking_.\nIn many use cases, multiple clients will be accessing different keys,\nso collisions are unlikely – usually there's no need to repeat the operation.\n\nStarting with version 8.4, Redis offers new atomic compare-and-set and compare-and-delete commands for string keys.\nA client can use a single ([`SET`](https://redis.io/docs/latest/commands/set) command with the `IFEQ`/`IFNE`/`IFDEQ`/`IFDNE` options to atomically update a string key only if its value has not changed since it was fetched.\nThis is much simpler and faster.\nSimilarly, a client can use a single [`DELEX`](https://redis.io/docs/latest/commands/delex) command for compare-and-delete: atomically deleting a string key if its value hasn't changed since it was fetched."
    },
    {
      "id": "watch-explained",
      "title": "WATCH explained",
      "role": "content",
      "text": "So what is [`WATCH`](https://redis.io/docs/latest/commands/watch) really about? It is a command that will\nmake the [`EXEC`](https://redis.io/docs/latest/commands/exec) conditional: we are asking Redis to perform\nthe transaction only if none of the [`WATCH`](https://redis.io/docs/latest/commands/watch)ed keys were modified. This includes\nmodifications made by the client, like write commands, and by Redis itself,\nlike expiration or eviction. If keys were modified between when they were\n[`WATCH`](https://redis.io/docs/latest/commands/watch)ed and when the [`EXEC`](https://redis.io/docs/latest/commands/exec) was received, the entire transaction will be aborted\ninstead.\n\n**NOTE**\n* In Redis versions before 6.0.9, an expired key would not cause a transaction\nto be aborted. [More on this](https://github.com/redis/redis/pull/7920)\n* Commands within a transaction won't trigger the [`WATCH`](https://redis.io/docs/latest/commands/watch) condition since they\nare only queued until the [`EXEC`](https://redis.io/docs/latest/commands/exec) is sent.\n\n[`WATCH`](https://redis.io/docs/latest/commands/watch) can be called multiple times. Simply all the [`WATCH`](https://redis.io/docs/latest/commands/watch) calls will\nhave the effects to watch for changes starting from the call, up to\nthe moment [`EXEC`](https://redis.io/docs/latest/commands/exec) is called. You can also send any number of keys to a\nsingle [`WATCH`](https://redis.io/docs/latest/commands/watch) call.\n\nWhen [`EXEC`](https://redis.io/docs/latest/commands/exec) is called, all keys are [`UNWATCH`](https://redis.io/docs/latest/commands/unwatch)ed, regardless of whether\nthe transaction was aborted or not.  Also when a client connection is\nclosed, everything gets [`UNWATCH`](https://redis.io/docs/latest/commands/unwatch)ed.\n\nIt is also possible to use the [`UNWATCH`](https://redis.io/docs/latest/commands/unwatch) command (without arguments)\nin order to flush all the watched keys. Sometimes this is useful as we\noptimistically lock a few keys, since possibly we need to perform a\ntransaction to alter those keys, but after reading the current content\nof the keys we don't want to proceed.  When this happens we just call\n[`UNWATCH`](https://redis.io/docs/latest/commands/unwatch) so that the connection can already be used freely for new\ntransactions."
    },
    {
      "id": "using-watch-to-implement-zpop",
      "title": "Using WATCH to implement ZPOP",
      "role": "content",
      "text": "A good example to illustrate how [`WATCH`](https://redis.io/docs/latest/commands/watch) can be used to create new\natomic operations otherwise not supported by Redis is to implement ZPOP\n([`ZPOPMIN`](https://redis.io/docs/latest/commands/zpopmin), [`ZPOPMAX`](https://redis.io/docs/latest/commands/zpopmax) and their blocking variants have only been added\nin version 5.0), that is a command that pops the element with the lower\nscore from a sorted set in an atomic way. This is the simplest\nimplementation:\n\n[code example]\n\nIf [`EXEC`](https://redis.io/docs/latest/commands/exec) fails (i.e. returns a [Null reply](https://redis.io/docs/latest/develop/reference/protocol-spec#nil-reply)) we just repeat the operation."
    },
    {
      "id": "redis-scripting-and-transactions",
      "title": "Redis scripting and transactions",
      "role": "content",
      "text": "Something else to consider for transaction like operations in redis are\n[redis scripts](https://redis.io/docs/latest/commands/eval) which are transactional. Everything\nyou can do with a Redis Transaction, you can also do with a script, and\nusually the script will be both simpler and faster."
    }
  ],
  "examples": [
    {
      "id": "usage-ex0",
      "language": "plaintext",
      "code": "> MULTI\nOK\n> INCR foo\nQUEUED\n> INCR bar\nQUEUED\n> EXEC\n1) (integer) 1\n2) (integer) 1",
      "section_id": "usage"
    },
    {
      "id": "errors-inside-a-transaction-ex0",
      "language": "plaintext",
      "code": "Trying 127.0.0.1...\nConnected to localhost.\nEscape character is '^]'.\nMULTI\n+OK\nSET a abc\n+QUEUED\nLPOP a\n+QUEUED\nEXEC\n*2\n+OK\n-WRONGTYPE Operation against a key holding the wrong kind of value",
      "section_id": "errors-inside-a-transaction"
    },
    {
      "id": "errors-inside-a-transaction-ex1",
      "language": "plaintext",
      "code": "MULTI\n+OK\nINCR a b c\n-ERR wrong number of arguments for 'incr' command",
      "section_id": "errors-inside-a-transaction"
    },
    {
      "id": "discarding-the-command-queue-ex0",
      "language": "plaintext",
      "code": "> SET foo 1\nOK\n> MULTI\nOK\n> INCR foo\nQUEUED\n> DISCARD\nOK\n> GET foo\n\"1\"",
      "section_id": "discarding-the-command-queue"
    },
    {
      "id": "optimistic-locking-using-check-and-set-ex0",
      "language": "plaintext",
      "code": "val = GET mykey\nval = val + 1\nSET mykey $val",
      "section_id": "optimistic-locking-using-check-and-set"
    },
    {
      "id": "optimistic-locking-using-check-and-set-ex1",
      "language": "plaintext",
      "code": "WATCH mykey\nval = GET mykey\nval = val + 1\nMULTI\nSET mykey $val\nEXEC",
      "section_id": "optimistic-locking-using-check-and-set"
    },
    {
      "id": "using-watch-to-implement-zpop-ex0",
      "language": "plaintext",
      "code": "WATCH zset\nelement = ZRANGE zset 0 0\nMULTI\nZREM zset element\nEXEC",
      "section_id": "using-watch-to-implement-zpop"
    }
  ]
}
