perf: chain db transactions (#33004)

* perf: chain transactions

Frequently used rollback/commits can be modified to chain previous
transaction.

This reduces one query to DB in most requests.

* perf: chain transactions in requests
This commit is contained in:
Ankush Menat 2025-06-19 12:37:39 +05:30 committed by GitHub
parent c2dbae3ece
commit b57eb60486
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 13 deletions

View file

@ -128,7 +128,7 @@ def application(request: Request):
except Exception as e:
response = e.get_response(request.environ) if isinstance(e, HTTPException) else handle_exception(e)
if db := getattr(frappe.local, "db", None):
db.rollback()
db.rollback(chain=True)
else:
sync_database()
@ -398,9 +398,9 @@ def sync_database():
# if HTTP method would change server state, commit if necessary
if frappe.local.request.method in UNSAFE_HTTP_METHODS or frappe.local.flags.commit:
db.commit()
db.commit(chain=True)
else:
db.rollback()
db.rollback(chain=True)
# update session
if session := getattr(frappe.local, "session_obj", None):

View file

@ -1147,7 +1147,7 @@ class Database:
mode = "READ ONLY" if read_only else ""
self.sql(f"START TRANSACTION {mode}")
def commit(self):
def commit(self, *, chain=False):
"""Commit current transaction. Calls SQL `COMMIT`."""
if self._disable_transaction_control:
warnings.warn(message=TRANSACTION_DISABLED_MSG, stacklevel=2)
@ -1158,12 +1158,15 @@ class Database:
self.before_commit.run()
self.sql("commit")
self.begin() # explicitly start a new transaction
if chain:
self.sql("commit and chain")
else:
self.sql("commit")
self.begin()
self.after_commit.run()
def rollback(self, *, save_point=None):
def rollback(self, *, save_point=None, chain=False):
"""`ROLLBACK` current transaction. Optionally rollback to a known save_point."""
if save_point:
self.sql(f"rollback to savepoint {save_point}")
@ -1173,8 +1176,11 @@ class Database:
self.before_rollback.run()
self.sql("rollback")
self.begin()
if chain:
self.sql("rollback and chain")
else:
self.sql("rollback")
self.begin()
self.after_rollback.run()
else:

View file

@ -272,7 +272,7 @@ def execute_job(site, method, event, job_name, kwargs, user=None, is_async=True,
retval = method(**kwargs)
except (frappe.db.InternalError, frappe.RetryBackgroundJobError) as e:
frappe.db.rollback()
frappe.db.rollback(chain=True)
if retry < 5 and (
isinstance(e, frappe.RetryBackgroundJobError)
@ -293,15 +293,15 @@ def execute_job(site, method, event, job_name, kwargs, user=None, is_async=True,
raise
except Exception as e:
frappe.db.rollback()
frappe.db.rollback(chain=True)
frappe.log_error(title=method_name)
frappe.monitor.add_data_to_monitor(exception=e.__class__.__name__)
frappe.db.commit()
frappe.db.commit(chain=True)
print(frappe.get_traceback())
raise
else:
frappe.db.commit()
frappe.db.commit(chain=True)
return retval
finally: