Skip to main content

Waiting for Data Changes

An ALTER TABLE mutation can return before changed data becomes visible. If an application updates an event and immediately reads it, it may still see the old value.

Reproduce the Scenario

CREATE TABLE event_log (id String, event_name String)
ENGINE = MergeTree ORDER BY id;

INSERT INTO event_log VALUES ('E1', 'login');
jdbcTemplate.executeUpdate(
"ALTER TABLE event_log UPDATE event_name = ? WHERE id = ? SETTINGS mutations_sync = 0",
new Object[] { "archived", "E1" });

This explicitly submits an asynchronous mutation. A successful return means the command was accepted, not that all matching data has already changed.

Wait Before Reading

If the next step needs the new value on this server, submit the change with mutations_sync = 1:

jdbcTemplate.executeUpdate(
"ALTER TABLE event_log UPDATE event_name = ? WHERE id = ? SETTINGS mutations_sync = 1",
new Object[] { "archived", "E1" });

String name = jdbcTemplate.queryForObject(
"SELECT event_name FROM event_log WHERE id = ?",
new Object[] { "E1" }, String.class);

After successful completion, name is archived. This example uses a single-server MergeTree table. See mutation synchronization for replica waiting options.

Update and Delete Return Values

A successful update or delete does not mean the JDBC return value is the number of matching rows. For example, changing the condition above to WHERE event_name = 'login' may match several records; executeUpdate() does not reliably report that count or distinguish an unmatched condition.

The Builder API and BaseMapper also use the driver's return value. To verify a change, wait for completion and query the target records; do not use return value == 1 as a business check that exactly one record changed.

Inspect Progress and Failures

For asynchronous work, query the table's mutation records rather than inferring completion from an update count:

List<Map<String, Object>> tasks = jdbcTemplate.queryForList("""
SELECT mutation_id, command, is_done, parts_to_do, latest_fail_reason
FROM system.mutations
WHERE database = currentDatabase() AND table = ?
ORDER BY create_time DESC, mutation_id DESC
""", new Object[] { "event_log" });

Check is_done for completion, parts_to_do for remaining parts, and latest_fail_reason for the latest failure. Match the command and mutation ID to the operation being inspected; the newest row may belong to another writer. See system.mutations.

Note

Waiting for completion does not make several mutations one transaction. If a request times out, inspect its status before resubmitting.

Command errors

Invalid commands report errors to the caller, but MergeTree sorting and primary keys do not enforce uniqueness. Repeated IDs therefore do not cause duplicate-key errors. Batch calls cannot rely on such errors to detect duplicates; see Duplicate Records.

Transaction support

See Transaction Support for transaction APIs, propagation, and isolation limits.