Optimizing Large Transactions in Backend Development
The article explains why large database transactions degrade backend API performance, outlines the problems they cause such as concurrency inconsistency and lock blocking, and provides practical optimization techniques including programmatic transactions, batch processing, splitting into smaller transactions, and asynchronous parallel execution with code examples.
What Is a Large Transaction
In a recent backend API I built, the whole business logic—querying, remote or local calls, updates, inserts, calculations—was placed in a single method, creating a "large transaction" that takes a long time to execute and is inefficient.
Problems Caused by Large Transactions
Concurrent Data Inconsistency
Without locking, a second request may modify data before the first request finishes, causing the first request to write stale results.
Locking Leads to Blocking
Using locks prevents inconsistency but long‑running transactions can cause lock timeouts and block other operations, severely hurting performance.
Undo Log Performance Issues
Large transactions generate massive undo logs, slowing log queries and rollback efficiency.
High Database Pressure
When concurrency grows, the database experiences heavy read/write pressure and many threads wait.
How to Optimize Large Transactions
Avoid Remote RPC Calls Inside Transactions
Remote calls inside a transaction without a distributed‑transaction framework can cause inconsistency and rollback problems; they should be made asynchronous.
Use Programmatic Transactions for Flexibility
Declarative @Transactional applies to the whole method, which is often too broad. Programmatic transactions let you wrap only the update/insert operations, keeping queries outside the transaction.
public Boolean transactionCommit(String userName) {
// query user
SysUser sysUser = userMapper.selectUserByUserName(userName, null);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
if (null != sysUser) {
// update user status to 1
userMapper.updateStatus(userName);
}
} catch (Exception e) {
// rollback
transactionStatus.setRollbackOnly();
}
}
});
// query again
SysUser sysUser1 = userMapper.selectUserByUserName(userName, "1");
log.info("User with status 1: " + JSON.toJSONString(sysUser1));
return true;
}Programmatic transactions give fine‑grained control, keeping read operations outside the MySQL transaction table.
Batch Data Processing
When the frontend sends bulk data, split it into pages (e.g., 50 records per batch) to reduce transaction size and improve efficiency.
Frontend: paginate requests to avoid sending too many records at once.
Backend: process data in batches, e.g., using MyBatis batch updates.
List<List<ReceivableFeeSaveDTO>> partition = Lists.partition(receivableFeeSaveDTOList, 50);Split Large Transaction into Small Ones
Break a complex API into multiple smaller transaction APIs, each handling a single responsibility such as amount write‑back, remote call, or result update.
Asynchronous Parallel Processing
If remote calls cannot be avoided, execute them asynchronously. CompletableFuture can orchestrate parallel tasks and combine results.
CompletableFuture<Object> task1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Order check thread" + Thread.currentThread().getId());
// validate order, throw exception on failure
return "Bill entity";
}, executor);
CompletableFuture<Object> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("Bill generation thread" + Thread.currentThread().getId());
try {
// generate bill
return "Bill number";
Thread.sleep(3000);
System.out.println("Task2 finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}, executor);
// After task1 and task2 finish, execute task3
CompletableFuture<Boolean> future = task1.thenCombineAsync(task2, (t1, t2) -> {
System.out.println("Amount write‑back thread" + Thread.currentThread().getId());
// combine results and write back
return true;
}, executor);Conclusion
Large transactions are a major cause of API inefficiency; recognizing and refactoring them is an opportunity to improve one’s engineering skills.
When you notice that your code feels overly heavy, it’s a sign you’re ready to optimize and move to a higher level of performance.
Final Note (Support the Author)
If this article helped you, please like, view, share, or bookmark it. Your support motivates me to keep writing.
Additionally, I run a knowledge community where members can access advanced projects and tutorials for a fee of 199 CNY. Details are linked in the article.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
