-
-
Save Martin91/d2c121c25dece1aa028774c5cf3d8aaa to your computer and use it in GitHub Desktop.
https://elliot.land/implementing-your-own-transactions-with-mvcc, fixed a few runtime errors in the original gist and simulate a case that updating a deleting record
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
next_xid = 1 | |
active_xids = set() | |
records = [] | |
class TransactionConflictError(RuntimeError): | |
pass | |
def new_transaction(): | |
global next_xid | |
next_xid += 1 | |
active_xids.add(next_xid) | |
return Transaction(next_xid) | |
class Transaction(object): | |
def __init__(self, xid): | |
self.xid = xid | |
self.rollback_actions = [] | |
def add_record(self, record): | |
record['created_xid'] = self.xid | |
record['expired_xid'] = 0 | |
self.rollback_actions.append(["delete", len(records)]) | |
records.append(record) | |
def delete_record(self, id): | |
for i, record in enumerate(records): | |
if self.record_is_visible(record) and record['id'] == id: | |
if self.row_is_locked(record): | |
raise TransactionConflictError("Row locked by another transaction.") | |
else: | |
record['expired_xid'] = self.xid | |
self.rollback_actions.append(["add", i]) | |
def update_record(self, id, name): | |
self.delete_record(id) | |
self.add_record({"id": id, "name": name}) | |
def row_is_locked(self, record): | |
return record['expired_xid'] != 0 and \ | |
record['expired_xid'] in active_xids | |
def record_is_visible(self, record): | |
# The record was created in active transaction that is not our | |
# own. | |
if record['created_xid'] in active_xids and \ | |
record['created_xid'] != self.xid: | |
return False | |
# The record is expired or and no transaction holds it that is | |
# our own. | |
if record['expired_xid'] != 0 and \ | |
(record['expired_xid'] not in active_xids or \ | |
record['expired_xid'] == self.xid): | |
return False | |
return True | |
def fetch_all(self): | |
global records | |
visible_records = [] | |
for record in records: | |
if self.record_is_visible(record): | |
visible_records.append(record) | |
return visible_records | |
def commit(self): | |
active_xids.discard(self.xid) | |
def rollback(self): | |
for action in reversed(self.rollback_actions): | |
if action[0] == 'add': | |
records[action[1]]['expired_xid'] = 0 | |
elif action[0] == 'delete': | |
records[action[1]]['expired_xid'] = self.xid | |
active_xids.discard(self.xid) | |
client = new_transaction() | |
client.add_record({"id": 1, "name": "Bob"}) | |
client.add_record({"id": 2, "name": "John"}) | |
client.commit() | |
client1 = new_transaction() | |
client2 = new_transaction() | |
client1.delete_record(id=2) | |
print(client1.fetch_all()) | |
print(client2.fetch_all()) | |
client2.update_record(id=2, name="John2") | |
print(client2.fetch_all()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{'id': 1, 'name': 'Bob', 'created_xid': 2, 'expired_xid': 0}] | |
[{'id': 1, 'name': 'Bob', 'created_xid': 2, 'expired_xid': 0}, {'id': 2, 'name': 'John', 'created_xid': 2, 'expired_xid': 3}] | |
Traceback (most recent call last): | |
File "mvcc.py", line 93, in <module> | |
client2.update_record(id=2, name="John2") | |
File "mvcc.py", line 38, in update_record | |
self.delete_record(id) | |
File "mvcc.py", line 32, in delete_record | |
raise TransactionConflictError("Row locked by another transaction.") | |
__main__.TransactionConflictError: Row locked by another transaction. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment