From b57eb60486ffd3827bf28d0fdfb090be45b0dfd6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 19 Jun 2025 12:37:39 +0530 Subject: [PATCH] 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 --- frappe/app.py | 6 +++--- frappe/database/database.py | 18 ++++++++++++------ frappe/utils/background_jobs.py | 8 ++++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index d8d259e066..04e06171ef 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -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): diff --git a/frappe/database/database.py b/frappe/database/database.py index 1501edef77..1f809c8d14 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -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: diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 5926c5c656..39fff78bde 100644 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -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: