In the previous implementation of 'version_list', an unexpected exception could occur: "Value exists, but did not successfully read." This happens when a read operation returns no results, but previous write operations updated values that should have been part of the read results. The problem arises because these updated values haven't been committed yet, making them invisible in isolation levels above Read Committed.
This is especially concerning under snapshot isolation because it only shows updates from transactions that were committed before the snapshot was created.
Example:
Database Type: PostgreSQL (pg)
Test Name: IR0-PCW0
Isolation Level: Serializable
Preparation Steps:
Create new tables and insert the necessary data.
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (k INT PRIMARY KEY, v INT);
INSERT INTO t1 VALUES (0, 0);
INSERT INTO t1 VALUES (2, 2);
INSERT INTO t1 VALUES (4, 4);
Execution Steps:
Start multiple transactions with the isolation level set to serializable.
Run the following commands:
Q1-T1: BEGIN;
Q2-T1: INSERT INTO t1 VALUES (1, 1);
Q3-T2: BEGIN;
Q4-T2: SELECT * FROM t1 WHERE k=1; (This returns null)
Q5-T2: SELECT * FROM t1 WHERE k > 0 AND k < 2; (This also returns null)
Q6-T2: COMMIT;
Q7-T1: UPDATE t1 SET v=2 WHERE k=1;
Q8-T1: COMMIT;
Test Result:
The test finished without any rollback and was checked for consistency.
The exception occurred during Q4-T2, but it should have returned an empty result instead.
Solution:
I added visible timestamps and snapshot timestamps. Now, a snapshot can only include a version if its timestamp is more recent than the visible timestamp. For read uncommitted, the visible timestamp is the time the write operation finished. For higher isolation levels, it reflects when the transaction commits.
For snapshot reads, the snapshot time is different in different DBMS. For MySQL, the snapshot is taken before the first normal read operation; for postgreSQL, the snapshot is taken before the first non-control statement.
I also allow transactions to see their own uncommitted updates.
2. Handling Versions:
To see the latest version, I traversed the 'version_list' in reverse. If the first valid version is not the one that was read, it indicates that the read operation is using an older version. This approach helps prevent Intermediate Read anomalies.
3. Checking Loops for Weak Isolation Levels:
Different isolation levels handle loops in dependency graphs differently:
PL-1 (Read Uncommitted): Prohibits loops with only write dependencies(G0).
PL-2 (Read Committed): Prohibits loops with both write and read dependencies(G1c) and prevents aborted/intermediate reads(G1a and G1b) .
PL-2.99 (Repeatable Read): Prohibits G1 and loops with item-anti-dependencies.
PL-3 (Serializable): Prohibits all loops and aborted/intermediate reads.
For snapshot isolation, if a loop exists, it must have at least two consecutive anti-dependency edges.
Implementation Note: Adya’s definitions are the minimum requirements for isolation levels, but the actual implementation might be stricter. For instance, the serializability implemented by SSI does not allow two consecutive anti-dependency edges.
MySQL's repeatable read is lower than PL-2.99, which allows loops with item anti-dependencies. And MySQL's repeatable read is not snapshot isolation, because it allows concurrent updates, which can cause lost updates. I use snapshot read visibility plus PL-1 rules to detect MySQL's repeatable read isolation level.
Verification Process:
To check if the results meet the isolation level requirements, I go through all loops in the graph and ensure that all illegal loops are not allowed.
1. Changes to 'version_list':
In the previous implementation of 'version_list', an unexpected exception could occur: "Value exists, but did not successfully read." This happens when a read operation returns no results, but previous write operations updated values that should have been part of the read results. The problem arises because these updated values haven't been committed yet, making them invisible in isolation levels above Read Committed.
This is especially concerning under snapshot isolation because it only shows updates from transactions that were committed before the snapshot was created.
Example:
Preparation Steps:
Execution Steps:
Q1-T1: BEGIN;
Q2-T1: INSERT INTO t1 VALUES (1, 1);
Q3-T2: BEGIN;
Q4-T2: SELECT * FROM t1 WHERE k=1;
(This returns null)Q5-T2: SELECT * FROM t1 WHERE k > 0 AND k < 2;
(This also returns null)Q6-T2: COMMIT;
Q7-T1: UPDATE t1 SET v=2 WHERE k=1;
Q8-T1: COMMIT;
Test Result:
Q4-T2
, but it should have returned an empty result instead.Solution:
I added visible timestamps and snapshot timestamps. Now, a snapshot can only include a version if its timestamp is more recent than the visible timestamp. For read uncommitted, the visible timestamp is the time the write operation finished. For higher isolation levels, it reflects when the transaction commits.
For snapshot reads, the snapshot time is different in different DBMS. For MySQL, the snapshot is taken before the first normal read operation; for postgreSQL, the snapshot is taken before the first non-control statement.
I also allow transactions to see their own uncommitted updates.
2. Handling Versions:
To see the latest version, I traversed the 'version_list' in reverse. If the first valid version is not the one that was read, it indicates that the read operation is using an older version. This approach helps prevent Intermediate Read anomalies.
3. Checking Loops for Weak Isolation Levels:
Different isolation levels handle loops in dependency graphs differently:
For snapshot isolation, if a loop exists, it must have at least two consecutive anti-dependency edges.
Implementation Note: Adya’s definitions are the minimum requirements for isolation levels, but the actual implementation might be stricter. For instance, the serializability implemented by SSI does not allow two consecutive anti-dependency edges.
MySQL's repeatable read is lower than PL-2.99, which allows loops with item anti-dependencies. And MySQL's repeatable read is not snapshot isolation, because it allows concurrent updates, which can cause lost updates. I use snapshot read visibility plus PL-1 rules to detect MySQL's repeatable read isolation level.
Verification Process: