# Transactions

All Cypher queries are run within transactions, which means that all modification
made by a single query are held in memory by the transaction until the query
is successfully executed. The changes are then committed and visible to all
other transactions, users and systems. In the case of an error, the transaction
is rolled back and no changes are committed.

These automatic transactions are also called implicit transactions.

You can also create explicit transactions to execute multiple Cypher queries
in sequence, then commit them or roll them back.

If you want to switch between [storage
modes](https://memgraph.com/docs/fundamentals/storage-memory-usage/), there can't be any active
transactions. Memgraph will log a warning message if it finds any active
transactions, so set the log level to `WARNING` to see them. No other
transactions will take place during the switch between modes.

During transaction execution, an important property of a database is the
isolation level that defines how or when the changes made by one operation
become visible to others.

## Explicit transactions

To start a transaction, run the `BEGIN;` query.

All the following queries will be executed as a part of a single transaction.

If any of the queries fails during execution, further queries will no longer be
successfully executed and it won't be possible to commit the transaction. 
As active constraints are checked upon commit, multi-query transactions will be
committed as long as the database adheres to the constraints upon the execution
of the final query. 

Commit successful transactions by executing the `COMMIT;` query. 

Roll back unsuccessful transactions by executing the `ROLLBACK;` query.

The following query will find to nodes and adjust a certain relationship
property in order to check the results of a
[max_flow](https://memgraph.com/docs/advanced-algorithms/available-algorithms/max_flow) algorithm, then
rollback the change as this query was a part of an what-if analysis:

```cypher
BEGIN;
 
MATCH (e:E)-[r:CONNECTED_TO]->(f:F)
SET r.flow = 17;
 
MATCH (a:A), (f:F)
CALL max_flow.get_flow(a, f, “flow”) YIELD max_flow
RETURN max_flow;
 
ROLLBACK;
```

## Optimistic vs. pessimistic approaches

In database management systems, there are two approaches to handling potential
conflicts. The optimistic approach checks for conflicts only during the commit.
If a conflict occurs, the transaction is rolled back. On the other hand, the
pessimistic approach entails making additional checks to prevent conflicts from
occurring in the first place.

The difference between the two approaches is relevant in several areas of
Memgraph’s operation: concurrency control and
[constraint](https://memgraph.com/docs/fundamentals/constraints) checking.

In constraint checking, Memgraph takes the optimistic approach. Multi-query
transactions will be committed as long as the database adheres to the
constraints upon the execution of the final query in the transaction.

## Manage transactions

Memgraph can return information about running transactions and allow you to
terminate them.

### Show transactions

To get information about all active transactions execute:

```cypher
SHOW TRANSACTIONS;
```

Each row in the result represents one transaction (or an in-progress snapshot
creation or garbage collection) and contains seven columns:

| Column | Type | Description |
|---|---|---|
| `username` | `String` | The user who started the transaction, or `""` if authentication is disabled. |
| `transaction_id` | `String` | Unique numeric identifier of the transaction. Use this value with `TERMINATE TRANSACTIONS`. |
| `query` | `List[String]` | Queries executed within the transaction so far. |
| `status` | `String` | Lifecycle phase of the transaction: `running`, `committing`, or `aborting`. Snapshot and garbage-collection rows always show `running`. |
| `metadata` | `Map` | Metadata supplied by the client when the transaction was opened. For in-progress snapshots and garbage collection it contains progress details (see below). |
| `start_time` | `ZonedDateTime` | UTC time at which the transaction started. |
| `elapsed_ms` | `Integer` | How long the transaction has been running, in milliseconds. |

```copy=false
memgraph> SHOW TRANSACTIONS;
+----------+-----------------------+-----------------------------------------------+--------------+----------+-------------------------------+------------+
| username | transaction_id        | query                                         | status       | metadata | start_time                    | elapsed_ms |
+----------+-----------------------+-----------------------------------------------+--------------+----------+-------------------------------+------------+
| ""       | "9223372036854794885" | ["UNWIND range(1,100) AS i CREATE(:L{p:i});"] | "committing" | {}       | 2026-05-12T14:32:18.412Z[UTC] | 47         |
| ""       | "9223372036854794896" | ["SHOW TRANSACTIONS"]                         | "running"    | {}       | 2026-05-12T14:32:18.451Z[UTC] | 8          |
+----------+-----------------------+-----------------------------------------------+--------------+----------+-------------------------------+------------+
```

#### Filter by status

You can limit the output to transactions in a specific lifecycle phase by
naming one or more statuses before the `TRANSACTIONS` keyword:

```cypher
SHOW RUNNING TRANSACTIONS;
SHOW COMMITTING TRANSACTIONS;
SHOW ABORTING TRANSACTIONS;
SHOW RUNNING, COMMITTING TRANSACTIONS;
```

When multiple statuses are listed (comma-separated) the result is their union —
rows matching any of the requested statuses are returned.
Omitting the status list is equivalent to requesting all three statuses.

#### Snapshot progress rows

While a snapshot is being created (triggered periodically, on exit, or
manually with `CREATE SNAPSHOT`), a synthetic row is included in the result
with `transaction_id` set to `"snapshot"`. The `metadata` map for these
rows contains:

| Key | Description |
|---|---|
| `phase` | Current phase of snapshot creation: `EDGES`, `VERTICES`, `INDICES`, `CONSTRAINTS`, or `FINALIZING`. |
| `items_done` | Number of objects serialized in the current phase so far. |
| `items_total` | Total number of objects expected in the current phase. |
| `db_name` | Name of the database whose snapshot is being created. |

The top-level `start_time` and `elapsed_ms` columns are populated for snapshot
rows as well, reflecting when the snapshot started.

```copy=false
memgraph> SHOW TRANSACTIONS;
+----------+----------------+---------------------+-----------+------------------------------------------------------------------+-------------------------------+------------+
| username | transaction_id | query               | status    | metadata                                                         | start_time                    | elapsed_ms |
+----------+----------------+---------------------+-----------+------------------------------------------------------------------+-------------------------------+------------+
| ""       | "snapshot"     | ["CREATE SNAPSHOT"] | "running" | {phase: "VERTICES", items_done: 142000, items_total: 500000, ... | 2026-05-12T14:32:17.205Z[UTC] | 1247       |
+----------+----------------+---------------------+-----------+------------------------------------------------------------------+-------------------------------+------------+
```

> **Info**
>
> Snapshot progress values are read from independent atomic counters and are not
> captured as a single consistent snapshot. `items_done`, `items_total`, and
> `phase` may reflect slightly different points in time, so treat them as
> best-effort estimates rather than exact figures. In particular, `items_done`
> may briefly read as `0` when the phase transitions, and `start_time` may be
> `null` if the snapshot was observed in the brief window before it recorded
> its start.

#### Garbage collection rows

Storage [garbage collection](https://memgraph.com/docs/fundamentals/storage-memory-usage) runs
continuously in the background to reclaim obsolete object versions. While a
collection is actively running, a synthetic row is included in the result with
`transaction_id` set to `"gc"` and `query` set to `["GARBAGE COLLECTION"]`.
This makes a slow, stuck, or blocking collection directly observable; a healthy
run finishes in well under a millisecond and is rarely caught. The `metadata`
map for these rows contains:

| Key | Description |
|---|---|
| `phase` | Current phase of the cycle: `unlink` (detach obsolete versions no active transaction can still see), `index_cleanup` (remove stale index and constraint entries — usually the most expensive phase), or `delete` (free the unlinked versions). |
| `trigger` | `periodic` for the background cycle, or `forced` for a `FREE MEMORY` query or a storage-mode switch. |
| `exclusive_lock` | `true` when the collection holds the storage lock exclusively and is therefore blocking all transactions. |
| `db_name` | Name of the database whose garbage is being collected. |

The top-level `start_time` and `elapsed_ms` columns reflect when the collection
started; a large `elapsed_ms` is the clearest signal of a stuck collection. One
`gc` row appears per database that currently has a collection running.

```copy=false
memgraph> SHOW TRANSACTIONS;
+----------+----------------+------------------------+-----------+-------------------------------------------------------------------------------------------+-------------------------------+------------+
| username | transaction_id | query                  | status    | metadata                                                                                  | start_time                    | elapsed_ms |
+----------+----------------+------------------------+-----------+-------------------------------------------------------------------------------------------+-------------------------------+------------+
| ""       | "gc"           | ["GARBAGE COLLECTION"] | "running" | {db_name: "memgraph", exclusive_lock: true, phase: "index_cleanup", trigger: "periodic"}  | 2026-05-12T14:32:17.881Z[UTC] | 395        |
+----------+----------------+------------------------+-----------+-------------------------------------------------------------------------------------------+-------------------------------+------------+
```

> **Info**
>
> Like snapshot rows, garbage-collection values are read from independent atomic
> counters rather than as a single consistent snapshot, so treat them as
> best-effort. During a `FREE MEMORY` query both your own transaction and the
> synthetic `gc` row may appear at once, distinguished by `trigger`.

> **Warning**
>
> Synthetic rows cannot be terminated. Background snapshot creation and garbage
> collection run outside the normal transaction lifecycle and cannot be
> interrupted via Cypher. Passing `"snapshot"` or `"gc"` to `TERMINATE
> TRANSACTIONS` raises an error because they are not valid transaction ids, and
> `TERMINATE TRANSACTIONS "*"` skips them.

Because snapshot and garbage-collection rows always have `status` `"running"`,
they are suppressed when you use `SHOW COMMITTING TRANSACTIONS` or `SHOW
ABORTING TRANSACTIONS`.

#### Permissions

By default, users can see and terminate only the transactions they have
started. For all other transactions, the user must have the
[**TRANSACTION_MANAGEMENT** privilege](https://memgraph.com/docs/database-management/authentication-and-authorization/role-based-access-control) which the admin
assigns with the following query:

```cypher
GRANT TRANSACTION_MANAGEMENT TO user;
```

The privilege to manage all the transactions running in Memgraph is revoked
using the following query:

```cypher
REVOKE TRANSACTION_MANAGEMENT FROM user;
```

> **Info**
>
> When Memgraph is first started there is only one explicit
> super-admin user that has all the privileges, including the
> **TRANSACTION_MANAGEMENT** privilege. The super-admin user is able to see all
> transactions.

If you are connecting to Memgraph using a client, you can pass additional
metadata when starting a transaction (if the client supports additional
metadata) which will be visible when running the `SHOW TRANSACTIONS;` query,
thus allowing you to identify each transaction precisely.

The Python example below demonstrates how to pass metadata for
both an implicit and explicit transaction:

```python
import neo4j

def main():
  driver = neo4j.GraphDatabase.driver("bolt://localhost:7687", auth=("user","pass"))

  s1 = driver.session()
  tx = s1.begin_transaction(metadata={"where":"in explicit tx", "my_uuid":1})
  tx.run("MATCH (n) RETURN n LIMIT 1")

  s2 = driver.session()
  query=neo4j.Query("SHOW TRANSACTIONS", metadata={"where":"in implicit tx", "my_uuid":2})
  print(s2.run(query).values())

  tx.close()
  s1.close()
  s2.close()

if __name__ == '__main__':
  main()
```

### Terminate transactions

To terminate one or more transactions, you need to open a new session and use
the following query:

```cypher
TERMINATE TRANSACTIONS "tid", "<tid2>", "<tid3>", ... ;
```

The `tid` is the transactional ID that can be seen using the `SHOW
TRANSACTIONS;` query.

The `TERMINATE TRANSACTIONS` query signalizes to the thread executing the
transaction that it should stop the execution. No violent interruption will
happen, and the whole system will stay in a consistent state. 

The result has two columns, `transaction_id` and `killed`. A transaction
reports `killed: true` only if it was actually found, the caller was allowed to
terminate it, and it was still running. Ids that do not match a running
transaction, and matches the caller is not authorized to terminate, both report
`killed: false` — an unauthorized match is indistinguishable from a missing id,
so the query never reveals that somebody else's transaction exists.

> **Warning**
>
> **Breaking change in Memgraph 3.13**: transaction ids must now parse as a whole
> number. Previously an id with trailing characters (for example
> `TERMINATE TRANSACTIONS "9223372036854794885abc"`) silently terminated the
> transaction matching the numeric prefix, and a completely unparseable id was
> reported back as a termination attempt on `18446744073709551615`. Both cases
> now raise an error instead.
>
> In the same release, naming a transaction you are not authorized to terminate
> reports `killed: false` instead of `true`. Previously such a query claimed a
> kill that never happened.

#### Terminate all transactions

Instead of copying ids out of `SHOW TRANSACTIONS` one at a time, you can
terminate everything at once with the `"*"` wildcard:

```cypher
TERMINATE TRANSACTIONS "*";
```

This terminates every transaction the caller is authorized to terminate,
following the same rules as the id list form:

- **Scope**: all transactions visible through `SHOW TRANSACTIONS`, across all
  databases — not just the caller's current database.
- **Authorization**: a transaction is terminated if the caller owns it, or holds
  the **TRANSACTION_MANAGEMENT** privilege on the database that transaction is
  running on. A user without the privilege terminates only its own
  transactions; the query does not fail, it simply returns fewer rows.
  Transactions the caller may not terminate are absent from the output rather
  than reported as `killed: false`, so no transaction id is leaked.
- **The caller's own transaction is skipped**, so the session issuing the sweep
  survives it and can read back the result.
- **Output**: the usual `transaction_id` and `killed` columns, one row per
  terminated transaction ordered by ascending transaction id (oldest first),
  and zero rows if nothing matched.
- Transactions that are already committing or aborting are skipped, the same as
  with the id list form.

The wildcard must be the only argument. Mixing it with ids, such as
`TERMINATE TRANSACTIONS "*", "9223372036854794885"`, raises an error.

A parameterized id is treated exactly like a literal one, so running
`TERMINATE TRANSACTIONS $id` with `$id = "*"` also terminates everything.

> **Info**
>
> System transactions (for example an in-flight `CREATE DATABASE` or `GRANT`) are
> reported as terminated but run to completion — they are not abortable. This
> applies to the id list form as well.

```copy=false
memgraph> TERMINATE TRANSACTIONS "*";
+-----------------------+-----------------------+
| transaction_id        | killed                |
+-----------------------+-----------------------+
| "9223372036854794885" | true                  |
| "9223372036854794891" | true                  |
+-----------------------+-----------------------+
2 rows in set (round trip in 0.001 sec)
```

### Terminate custom procedures

If you want to be able to [terminate custom procedures](https://memgraph.com/docs/custom-query-modules),
crucial parts of the code, such as `while` and `until` loops, or similar points
where the procedure might become costly, need to be preceded with
`CheckMustAbort()` function.

### Example

Managing transactions is done by establishing a new connection to the database.

**Memgraph Lab**

If you are using **Memgraph Lab**, you can vertically split screens and open another
    Query Execution section.

**mgconsole**

If you are using **mgconsole** on an instance running in a Docker container:

    1. Open a new terminal and find the CONTAINER ID of the Memgraph Docker container:

```
docker ps
```

    2. Run the client: 

```
docker exec -it CONTAINER_ID mgconsole
```

---

**Show and terminate transactions**

The output of the `SHOW TRANSACTIONS` command shows that a query is
currently being run as part of the transaction ID "9223372036854794885".

```copy=false
memgraph> SHOW TRANSACTIONS;
+----------+-----------------------+-------------------------------------------+-----------+----------+-------------------------------+------------+
| username | transaction_id        | query                                     | status    | metadata | start_time                    | elapsed_ms |
+----------+-----------------------+-------------------------------------------+-----------+----------+-------------------------------+------------+
| ""       | "9223372036854794885" | ["CALL infinite.get() YIELD * RETURN *;"] | "running" | {}       | 2026-05-12T14:32:00.000Z[UTC] | 18230      |
| ""       | "9223372036854794896" | ["SHOW TRANSACTIONS"]                     | "running" | {}       | 2026-05-12T14:32:18.230Z[UTC] | 0          |
+----------+-----------------------+-------------------------------------------+-----------+----------+-------------------------------+------------+
```

To terminate the transaction, run the following query:

```cypher
TERMINATE TRANSACTIONS "9223372036854794885";
```

Upon the transaction termination, the following confirmation will appear:

```copy=false
memgraph> TERMINATE TRANSACTIONS "9223372036854794885";
+-----------------------+-----------------------+
| transaction_id        | killed                |
+-----------------------+-----------------------+
| "9223372036854794885" | true                  |
+-----------------------+-----------------------+
1 row in set (round trip in 0.000 sec)
```

The following message will appear in the session in which the infinite query was being run:

```copy=false
memgraph> CALL infinite.get() YIELD * RETURN *; 
Client received exception: Transactions was asked to abort either because it was executing longer than time
specified or another user asked to abort it.
```

## Isolation levels

In database systems, isolation determines how transaction integrity is visible
to other users and systems.  

A lower isolation level allows many users to access the same data at the same
time but increases the number of concurrency effects (such as dirty reads or
lost updates). A higher isolation level secures data consistency but requires
more system resources and increases the chances that one transaction will block
another.

Memgraph currently supports three isolation levels, from the highest to the
lowest:
 - SNAPSHOT_ISOLATION (default) - guarantees that all reads made in a
   transaction will see a consistent snapshot of the database, and the
   transaction itself will successfully commit only if no updates it has made
   conflict with any concurrent updates made since that snapshot. Protects users
   from observing Dirty Read, Non-repeatable Read and Phantom phenomena as
   described in ANSI/ISO SQL-92 standard.
 - READ_COMMITTED - guarantees that any data read was committed at the moment it
   is read. It protects users from observing Dirty Read phenomenon as described in 
   ANSI/ISO SQL-92 standard.
 - READ_UNCOMMITTED - one transaction may read not yet committed changes made by
   other transactions. Doesn't protect users from any of the three phenomena
   described in ANSI/ISO SQL-92 standard. In order to not mess up data consistency,
   this isolation level should only be used in the read-only access mode.

| Phenomenon          | Description                                                                                                                                          | Disallowed by                                              |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Dirty Read          | Transaction reads an object that was never committed by other transaction.                                                                           | SNAPSHOT ISOLATION, READ COMMITTED                         |
| Non-repeatable Read | Transaction reads an object twice. When the txn reads the object 2nd time, it receives the modified value because other txn modified it.             | SNAPSHOT ISOLATION                                         |
| Phantom             | Transaction reads objects meeting a certain condition and then finds additional objects when reading 2nd time because another txn added new objects. | SNAPSHOT ISOLATION                                         |

Based on [Adya's classification of phenomena](https://pmg.csail.mit.edu/papers/adya-phd.pdf), we can further write:

| Phenomenon | Disallowed by                                        |
| ---------- | ---------------------------------------------------- |
| G0         | SNAPSHOT ISOLATION, READ COMMITTED, READ UNCOMMITTED | 
| G1a        | SNAPSHOT ISOLATION, READ COMMITTED                   |
| G1b        | SNAPSHOT ISOLATION, READ COMMITTED                   |
| G1c        | SNAPSHOT ISOLATION, READ COMMITTED                   |
| G1-predA   | SNAPSHOT ISOLATION, READ COMMITTED                   |
| G1-predB   | SNAPSHOT ISOLATION, READ COMMITTED                   |
| G_single   | SNAPSHOT ISOLATION                                   |
| G2_item    | NONE                                                 |

In terms of Adya's isolation levels, Memgraph supports: PL-1, PL-MSR (Monotonic Snapshot Reads), PL-2, PL-2', PL-2'', PL-2L, PL-CS (Cursor Stability) and PL-2+ (consistent view).

You can find tests for these phenomena [here](https://github.com/memgraph/memgraph/blob/master/tests/manual/test_isolation_level.py).

To check the current (runtime) isolation level of the database you are connected
to, run the following query and read the `storage_isolation_level` field:

```cypher
SHOW STORAGE INFO ON CURRENT DATABASE;
```

The unscoped `SHOW STORAGE INFO` only reports `global_isolation_level`, which is
the **startup-default** isolation level (the [`--isolation-level`](https://memgraph.com/docs/database-management/configuration)
flag) and does **not** reflect runtime `SET GLOBAL TRANSACTION ISOLATION LEVEL`
changes.

`IN_MEMORY_ANALYTICAL` storage modes offers no isolation levels and no ACID
guarantees. Multiple transactions can write data to Memgraph simultaneously. One
transaction can therefore see all the changes from other transactions.

`ON_DISK_TRANSACTIONAL` storage mode uses only snapshot isolation. 

### Set the isolation level

Memgraph can work in `IN_MEMORY_ANALYTICAL`, `IN_MEMORY_TRANSACTIONAL` or
`ON_DISK_TRANSACTIONAL` [storage mode](https://memgraph.com/docs/fundamentals/storage-memory-usage).
`IN_MEMORY_TRANSACTIONAL` is the default mode in which Memgraph runs on startup.

When Memgraph is running in the `IN_MEMORY_TRANSACTIONAL` mode you can change
the isolation level, change the `--isolation-level` [configuration
flag](https://memgraph.com/docs/configuration/configuration-settings#other) to any of the supported
values.

You can also change the initially set isolation level when Memgraph is running
using the following query:

```cypher
SET <scope> TRANSACTION ISOLATION LEVEL <isolation_level>
```

`<scope>` defines the scope to which the isolation level change should apply:
 - GLOBAL - apply the new isolation level globally
 - SESSION - apply the new isolation level only for the current session
 - NEXT - apply the new isolation level only for the next transaction in the current session

`<isolation_level>` defines the isolation level:
 - SNAPSHOT ISOLATION
 - READ COMMITTED
 - READ UNCOMMITTED
