-
Notifications
You must be signed in to change notification settings - Fork 403
Description
Bug description
MVCC assumes that if a get statement in a transaction sees a gap due to some other statement in that transaction (own_change == true), then no gap-tracker is required. But that's not true for secondary indexes. The same applies to the case when get read something - MVCC will incorrectly assume that a read-tracker is not required. To understand the issue, see the comment below regarding the reproducer.
The check that decides not to create a tracker:
Line 3014 in e6ec4aa
| if (txn != NULL && !own_change) { |
Steps to reproduce
Run the following reproducer.lua script:
fiber = require('fiber')
box.cfg{memtx_use_mvcc_engine=true}
box.schema.space.create("test")
box.space.test:format{{'a', type='unsigned'}, {'b', type='unsigned'}}
box.space.test:create_index("pk", {parts={{'a'}}})
box.space.test:create_index("sk", {parts={{'b'}}, unique=true})
box.space.test:truncate()
box.space.test:replace{0, 2}
f1 = fiber.create(function()
box.begin()
box.space.test:replace{0, 1}
local res = box.space.test.index.sk:get{2}
print(res)
fiber.sleep(1)
box.commit()
end)
f1:set_joinable(true)
box.begin()
box.space.test:delete{0}
box.space.test:insert{2, 2}
box.commit()
local ok, err = f1:join()
print(ok, err)
print(require('yaml').encode(box.space.test:select{}))
box.space.test:drop()
os.exit()How to run:
$ tarantool -i reproducer.luaActual output:
nil
true nil
---
- [0, 1]
- [2, 2]
...
It is obvious that in the serialized sequence, the second transaction TXN2 (fiber f1) was the last one. This is because each of the transactions replaced 0 in the primary key (the first TXN1 performed replace{0, 2}, the second TXN2 performed replace{0, 1}, and the third TXN3 performed delete{0}). However, as seen in the output, the index ended up containing the tuple {0, 1}.
Thus, two schedules are possible: 1, 3, 2 and 3, 1, 2. 1, 3, 2 could not be, because then get(2) in transaction TXN2 should have returned {2, 2}, but it returned nil. But 3, 1, 2 is also impossible, because then replace{0, 2} in TXN1 should have failed with error:
Duplicate key exists in unique index "sk" in space "test" with old tuple
- [2, 2] and new tuple - [0, 2]
because TXN3 transaction inserted {2, 2} before TXN1 tried to replace{0, 2}.
Actual behavior
The second transaction was not rolled back, resulting in a non-serializable execution.
Expected behavior
The second transaction is rolled back.