Skip to content

Commit 613a23a

Browse files
fix: error occurred when retrying transaction on empty table (#18703)
* fix: error occurred when retrying transaction on empty table * add stateless test case * refine stateless test * fix typo --------- Co-authored-by: dantengsky <[email protected]>
1 parent 52cfb4b commit 613a23a

File tree

3 files changed

+69
-35
lines changed

3 files changed

+69
-35
lines changed

src/query/storages/fuse/src/retry/commit.rs

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -153,48 +153,50 @@ async fn try_rebuild_req(
153153
if let Some(txn_begin_timestamp) =
154154
txn_mgr.get_table_txn_begin_timestamp(latest_table.get_id())
155155
{
156-
let Some(latest_snapshot_timestamp) = latest_snapshot.timestamp() else {
157-
return Err(ErrorCode::UnresolvableConflict(format!(
156+
if let Some(latest_snapshot) = latest_snapshot.as_ref() {
157+
let Some(latest_snapshot_timestamp) = latest_snapshot.timestamp else {
158+
return Err(ErrorCode::UnresolvableConflict(format!(
158159
"Table {} snapshot lacks required timestamp. This table was created with a significantly outdated version that is no longer directly supported by the current version and requires migration.
159160
Please contact us at https://www.databend.com/contact-us/ or email [email protected]",
160161
tid
161162
)));
162-
};
163-
164-
// By enforcing txn_begin_timestamp >= latest_snapshot_timestamp, we ensure that
165-
// vacuum operations won't remove table data (segment, blocks, etc.) that newly
166-
// created in the current active transaction.
167-
168-
// In the current transaction, all the newly created table data (segments, blocks, etc.)
169-
// has timestamps that are greater than or equal to txn_begin_timestamp, but the
170-
// final snapshot which contains those data (and is yet to be committed) may have a timestamp
171-
// that is larger than txn_begin_timestamp.
172-
173-
// To maintain vacuum safety, we must ensure that if the latest snapshot's timestamp
174-
// (latest_snapshot_timestamp) is larger than txn_begin_timestamp, we abort the transaction
175-
// to prevent potential data loss during vacuum operations.
176-
177-
// Example:
178-
// session1: session2: session3:
179-
// begin;
180-
// -- newly created table data
181-
// -- timestamped as A
182-
// insert into t values (1);
183-
// -- new snapshot S's ts is B
184-
// insert into t values (2);
185-
// -- using S as gc root
186-
// -- if B > A, then newly created table data
187-
// -- in session1 will be purged
188-
// call fuse_vacuum2('db', 't');
189-
// -- while merging with S
190-
// -- if A < B, this txn should abort
191-
// commit;
192-
193-
if txn_begin_timestamp < latest_snapshot_timestamp {
194-
return Err(ErrorCode::UnresolvableConflict(format!(
163+
};
164+
165+
// By enforcing txn_begin_timestamp >= latest_snapshot_timestamp, we ensure that
166+
// vacuum operations won't remove table data (segment, blocks, etc.) that newly
167+
// created in the current active transaction.
168+
169+
// In the current transaction, all the newly created table data (segments, blocks, etc.)
170+
// has timestamps that are greater than or equal to txn_begin_timestamp, but the
171+
// final snapshot which contains those data (and is yet to be committed) may have a timestamp
172+
// that is larger than txn_begin_timestamp.
173+
174+
// To maintain vacuum safety, we must ensure that if the latest snapshot's timestamp
175+
// (latest_snapshot_timestamp) is larger than txn_begin_timestamp, we abort the transaction
176+
// to prevent potential data loss during vacuum operations.
177+
178+
// Example:
179+
// session1: session2: session3:
180+
// begin;
181+
// -- newly created table data
182+
// -- timestamped as A
183+
// insert into t values (1);
184+
// -- new snapshot S's ts is B
185+
// insert into t values (2);
186+
// -- using S as gc root
187+
// -- if B > A, then newly created table data
188+
// -- in session1 will be purged
189+
// call fuse_vacuum2('db', 't');
190+
// -- while merging with S
191+
// -- if A < B, this txn should abort
192+
// commit;
193+
194+
if txn_begin_timestamp < latest_snapshot_timestamp {
195+
return Err(ErrorCode::UnresolvableConflict(format!(
195196
"Unresolvable conflict detected for table {} while resolving conflicts: txn started with logical timestamp {}, which is less than the latest table timestamp {}. Transaction must be aborted.",
196197
tid, txn_begin_timestamp, latest_snapshot_timestamp
197198
)));
199+
}
198200
}
199201
}
200202
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python3
2+
3+
import mysql.connector
4+
5+
# https://github.com/databendlabs/databend/issues/18705
6+
if __name__ == "__main__":
7+
# Session 1:
8+
# Insert into empty table
9+
mdb = mysql.connector.connect(host="127.0.0.1", user="root", passwd="root", port="3307")
10+
mycursor = mdb.cursor()
11+
mycursor.execute("create or replace table t_18705(c int)")
12+
mycursor.fetchall()
13+
mycursor.execute("begin")
14+
mycursor.fetchall()
15+
mycursor.execute("insert into t_18705 values (1)")
16+
mycursor.fetchall()
17+
18+
# Session 2:
19+
# Alter table in another session, so that the new table after alter operation will still be empty
20+
mydb_alter_tbl = mysql.connector.connect(host="127.0.0.1", user="root", passwd="root", port="3307")
21+
mycursor_alter_tbl = mydb_alter_tbl.cursor()
22+
mycursor_alter_tbl.execute("alter table t_18705 SET OPTIONS (block_per_segment = 500)")
23+
mycursor_alter_tbl.fetchall()
24+
25+
# Session 1:
26+
# Try commit the txn in session one
27+
mycursor.execute("commit")
28+
mycursor.fetchall()
29+
30+
# Will not reach here, if `commit` failed
31+
print("Looks good")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Looks good

0 commit comments

Comments
 (0)