In System Development, Is It Okay to Call APIs Inside a Transaction? When Database Connections Run Out, Everything Freezes
In project delivery, directly calling external APIs (HTTP, RPC, sending messages) inside a transaction is a high-frequency source of production incidents. According to common practice in 2026, inside a transaction you should only write local data and event records, and trigger external calls after the transaction commits. If you must synchronously get the result, put the call after the transaction commits and handle failure compensation properly. The judgment criterion is simple: any scenario where external call timeout and retry could affect database connections should not be placed inside a transaction.
Why calling APIs inside transactions tends to cause problems
The essence of a database transaction is holding locks for a long time, while external API latency is uncontrollable. If an API times out in 2 seconds, the transaction lock is held for 2 extra seconds, and other transactions in the connection pool have to queue. Once the connection pool is exhausted, all requests needing that database are blocked, which manifests as a "database freeze."
Additionally, calling APIs inside transactions brings consistency issues: the call succeeds before the transaction commits, but then the transaction rolls back, and the external system has already performed an action; or the transaction commits but the call fails, leaving the two sides out of sync.
For example, in the common deduction-then-SMS scenario, directly calling the SMS gateway inside the deduction transaction—if the SMS gateway is slow, a large number of locks pile up on the account table. This kind of problem often doesn't show in testing, only surfacing under high traffic or when the peer system jitters.
- Runaway lock time: every extra second of external call adds one more second of lock hold time; with high concurrency, the connection pool is quickly exhausted.
- Blurred transaction boundary: the database transaction cannot govern the external system, so atomicity across both sides cannot be guaranteed.
- Retries amplify the problem: when an API times out and is retried while still inside the transaction, it again occupies connections and locks, worsening congestion.
How to tell if the current design is a pitfall: the Three-Question Check
In projects, when I see code reviews where a transaction contains remote calls, I usually ask the team to do three checks. This method can be called the "Three-Question Check" and is used to quickly expose risk.
- Question 1: Does this call have to live and die with the database transaction? If the external action fails, must the database transaction roll back? If yes, the coupling is deep and needs to be redesigned.
- Question 2: In the worst case, how long can this call wait? Based on experience range, if the external API P99 exceeds 500ms, or if there is no configured timeout, it should not be inside a transaction; over 2 seconds is likely to cause problems.
- Question 3: If the call fails, can the business tolerate delayed compensation? If eventual consistency via async is acceptable, preferred approach is to move it out; only for strong consistency should you consider distributed transactions, and even then control the scope.
Among these three questions, the second is a hard criterion. In our deliveries, we usually require that non-SQL operations inside a transaction must not exceed 50ms, otherwise we recommend moving them out. Achieving this basically avoids connection pool exhaustion.
How to choose a solution: comparison of three common approaches
Based on 2026 project delivery habits, three common approaches are used for such scenarios, each with its applicable range. Below is a comparison from three dimensions: consistency, change effort, and operational cost.
Option A: Inside the transaction, only write to an event table; push asynchronously after commit
- Approach: Inside the transaction, only insert a local event record. After the transaction commits, a background task or message middleware pushes the event downstream.
- Advantages: The local transaction remains consistent, and external call failures do not affect the main flow; simple implementation, no additional distributed transaction components are introduced.
- Costs: Downstream actions are delayed, typically from hundreds of milliseconds to a few minutes; you need to handle event table accumulation and duplicate consumption.
Option B: Place the API call after the transaction commits (synchronous compensation)
- Approach: After the database transaction commits normally, call the external API immediately in memory; if it fails, handle it via retry or alerting with manual intervention.
- Advantages: Results are returned synchronously, providing good user experience; no additional storage is introduced.
- Costs: A process crash after commit can lose the call; retries require idempotent interfaces; during peak hours, application threads are still occupied.
Option C: Distributed transactions (e.g., TCC, Saga)
- Approach: Introduce a distributed transaction framework to coordinate commits or rollbacks across multiple systems.
- Advantages: Handles cross-system strong consistency; required for certain business scenarios (e.g., cross-system deductions).
- Costs: High development complexity, long debugging path; based on experience range, project duration increases by 30%-50%; not recommended for small teams to adopt for a single scenario.
In comparison, for scenarios with high consistency requirements like orders and payments, Option A is the first choice; only when you need real-time synchronous results and failures can be retried should you consider Option B; Option C should be evaluated last, as its operational cost is easily underestimated. In addition, regardless of the chosen option, interface idempotency and timeout circuit breaking are prerequisites; otherwise, moving the call outside the transaction doesn't help and incidents still occur.
Delivery scene: a post-mortem of a connection pool exhaustion incident
I encountered this pitfall in a mall project at Xiyue Company. Inside the order placement transaction, we directly called the inventory system. During a promotional peak, the inventory API P99 rose from 300ms to 3 seconds, and the database connection pool of 200 connections was fully occupied by transactions. Order creation suffered massive timeouts. Overnight, we moved the call to after the transaction commits and added a local message table inside the transaction, which would be pushed by a scheduled task to the inventory system. After the change, database connection usage dropped by 30%-50%, order success rate returned to normal, at the cost of a 1-3 second delay in inventory deduction. The key constraint here: API timeout is uncontrollable and traffic has peaks, so external calls must be moved off the lock-holding path.
How to prevent recurrence systematically
Manual review alone is not enough. The 2026 approach is to bake rules into the toolchain. In our deliveries, we set up three hard rules for the team: First, configure a code scanning rule that prohibits calling external APIs inside transaction methods; if hit, it directly raises an error. Second, alarm when connection pool usage exceeds 60%, to allow early scaling or investigation. Third, run a failure drill each iteration, deliberately making downstream APIs time out to observe whether transactions drag the database down.
Additionally, during design reviews of new features, I ask to clearly mark the transaction boundary, circle external calls with a red pen, and annotate the expected latency. If you can't specify it, assume the worst case of 2 seconds, which makes it easy to decide whether to split.
Applicable scenarios and boundaries
Suitable for "calling APIs inside a transaction": the call is to an in-process memory method, or the peer system has stable latency in the tens of milliseconds with circuit breakers and timeouts configured; also, the business allows retry or rollback compensation on failure. For example, internal user permission checks that tolerate brief blocking.
Not suitable or unnecessary for distributed transactions: most businesses actually accept eventual consistency, such as sending notifications, updating search indexes, or refreshing caches. In these cases, a local message table or ordinary message queue is sufficient; forcing distributed transactions adds maintenance burden. If the team is only two or three people, it is especially recommended to split transactions rather than introduce a framework.
- Signals that transaction-internal calls are acceptable: the call has a timeout (experience range suggests within 500ms), the peer has circuit breaking and degradation, failures can be handled by retry or manual compensation, and concurrency peaks are low.
- Signals that the call must be moved out: no timeout configured, P99 above 1 second, prior connection pool alarms, frequent jitter of the downstream interface, or the call modifies external state.
A simple boundary judgment: does the external call affect the submission of core data in the main path? If not, move it out; if it does, check whether strong consistency is required—if not, make it asynchronous; only when strong consistency is required should you consider distributed transactions.
Frequently asked questions
After the transaction commits, the API call fails. How do I compensate?
First ensure the interface is idempotent, then retry via a local message table or scheduled task. After retries exceed the experience range of 3-5 times, alert for manual handling.
Will the local message table cause duplicate consumption?
Yes, so downstream interfaces must be idempotent; you can use unique business IDs, state machine fields, and other methods for deduplication.
Can I put an API with a short timeout inside a transaction?
Still not recommended. Even with a short timeout, if the peer fails, the connection is still held up; you can do a pre-check outside the transaction and then execute quickly inside.
When are distributed transactions worth using?
Only when the business requires cross-system strong consistency and cannot be transformed into eventual consistency via business design. Based on experience range, introducing a distributed transaction for a single scenario increases project duration cost by over 30%, so careful evaluation is required.
Does sending a message to a message queue inside a transaction count as calling an interface?
Yes, message sending also involves network I/O and asynchronous callbacks. The common practice is to only write a message table inside the transaction and have a send component push after commit, to avoid a message gateway failure dragging down the transaction.
First, run a health check on existing transactions using the "Three-Question Check," treating any external call as a risk point. If you've already seen connection pool exhaustion or data inconsistency, prefer changing to writing an event inside the transaction and pushing asynchronously after commit. In 2026, major cloud providers all offer mature messaging services that you can directly integrate—no need to reinvent the wheel. The applicability boundary is clear: only consider distributed transactions for strong consistency scenarios; for eventual consistency scenarios, go asynchronous.
-
System Program Development: Store Time Fields as Timestamp or String? Time Zones Cause Repeated Rework
Date: Aug 28, 2026 Read: 8
-
Logs too sparse to trace issues, too verbose to afford — what to do when production troubleshooting always misses that one key detail?
Date: Aug 27, 2026 Read: 12
-
How Detailed Should API Return Codes Be to Avoid Back-and-Forth During Integration?
Date: Aug 27, 2026 Read: 17
-
System Development: Too Few Comments Make Code Hard to Understand, Too Many Are Ignored—How Many Is Enough?
Date: Aug 26, 2026 Read: 18
-
Config files or environment variables for multi-environment? After a wrong production DB, I switched.
Date: Aug 25, 2026 Read: 19




