Seata Distributed Transactions: AT/TCC/SAGA Selection, Internals & Production Pitfalls
This comprehensive guide covers Seata's four transaction modes (AT, TCC, SAGA, XA) with deep internals, production configuration, code examples, nine critical pitfalls with solutions, a real-world failure postmortem, and an architectural decision framework for microservice data consistency.
Why Microservices Need Distributed Transactions
In a monolith, a single database and local ACID transactions guarantee all-or-nothing commits. Microservices split domains into separate services and databases (e.g., Order, Inventory, Account, Marketing). A synchronous call chain like create order → deduct inventory → deduct balance can leave the system in a half-committed state if the last step fails, causing permanent dirty data.
Seata's Four Transaction Modes: Comparison & Selection
Seata provides XA, AT, TCC, and SAGA. The article presents a production-grade selection matrix:
AT
Core Principle: Two-phase commit + undo_log snapshots for auto rollback
Code Intrusion: Very low (annotation only)
Performance Overhead: 5%-10%
Consistency Level: Eventual (weak)
Production Scenarios: 90% ordinary business, short synchronous chains
TCC
Core Principle: Manual Try/Confirm/Cancel compensation
Code Intrusion: Very high (full manual coding)
Performance Overhead: 15%-25%
Consistency Level: Strong
Production Scenarios: Funds, accounts, inventory, trading core paths
SAGA
Core Principle: Forward execution + reverse compensation, long transactions split into segments
Code Intrusion: Medium
Performance Overhead: Lower
Consistency Level: Eventual
Production Scenarios: Long-running flows, minute-level transactions, tickets, approvals
XA
Core Principle: Database-native two-phase commit
Code Intrusion: Low
Performance Overhead: Very high
Consistency Level: Strong
Production Scenarios: Basically abandoned in production
Architect Selection Rules
Ordinary business, short sync chain, high dev efficiency → AT
Trading, funds, balance, inventory — zero dirty data tolerance → TCC
Long processes, approvals, cross-service ultra-long chains → SAGA
XA → discard (lock contention, extremely low throughput)
AT Mode Deep Dive
Core Idea
Phase 1: execute business SQL, capture before/after snapshots, store in local undo_log table, commit local transaction. Phase 2: if all branches succeed, TC sends global commit → delete undo_log; if any branch fails/times out, TC sends global rollback → read before-image from undo_log and reverse-apply to restore data.
Phase 1 Steps
Intercept SQL, query DB before update → record Before snapshot
Execute business Insert/Update/Delete
Query latest data → record After snapshot
Write both snapshots to local undo_log table
Commit local DB transaction
Key point: Phase 1 already persists business data; it is not a prepare-only phase.
Why AT Is Fast & Non-Intrusive
SQL interception auto-generates snapshots — zero business code changes
Phase 1 commits local transaction immediately — no long lock holding
Phase 2 only deletes logs or restores data — extremely fast
Production-Grade AT Setup (Seata 1.7.0)
1. Create undo_log Table (every participating DB)
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL COMMENT 'branch transaction ID',
`xid` varchar(128) NOT NULL COMMENT 'global transaction ID',
`context` varchar(128) NOT NULL COMMENT 'context',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int NOT NULL COMMENT 'log status 0 normal 1 deleted',
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Seata transaction rollback log table';2. Add Dependency
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>3. Production YAML (Nacos Registry)
seata:
application:
name: seata-server
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: public
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
transaction:
global-timeout: 60000
default-mode: AT
client:
undo:
log-serialization: jackson
clean-period: 864000004. Core Business Code
@Service
public class OrderServiceImpl implements OrderService {
@Resource private OrderMapper orderMapper;
@Resource private StockFeignClient stockFeignClient;
@Resource private AccountFeignClient accountFeignClient;
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(OrderCreateDTO dto) {
// 1. Local order creation
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(dto.getUserId());
order.setAmount(dto.getAmount());
order.setStatus(1);
orderMapper.insert(order);
// 2. Remote inventory deduction
stockFeignClient.deductStock(dto.getSkuId(), dto.getNum());
// 3. Remote balance deduction (simulate exception to trigger rollback)
accountFeignClient.deductBalance(dto.getUserId(), dto.getAmount());
}
}Adding @GlobalTransactional automatically enlists the entire cross-service chain into a distributed transaction.
Nine Production Pitfalls & Fixes
Complex SQL / batch updates / multi-table joins unsupported — AT parses SQL for snapshots; complex statements break parsing. Fix: split into simple single-table statements.
Spring @Transactional nested inside @GlobalTransactional — overwrites Seata context, disabling distributed transaction. Fix: only @GlobalTransactional on outer method; forbid @Transactional inside.
Async threads lose XID context — thread pools don't inherit parent XID, branches fall outside global transaction. Fix: manually propagate XID to child threads.
Global timeout causes hanging transactions — long chains or network jitter trigger TC rollback while local business continues, reversing data. Fix: increase timeout per chain length; add timeout monitoring alerts.
Retries without idempotency cause duplicate orders/payments — Seata retries on timeout; non-idempotent endpoints double-charge. Fix: enforce idempotency on all distributed transaction endpoints via unique business keys.
undo_log table grows unbounded — millions of rows slow snapshot queries and stress DB. Fix: enable scheduled cleanup (e.g., delete completed logs older than 7 days).
Read replicas cause snapshot inconsistency — stale reads from replicas produce mismatched before/after images, rollback fails. Fix: force reads to primary within transaction.
Non-transactional methods intercepted / empty rollback — methods without DB ops generate empty snapshots, corrupting state. Fix: exclude non-DB methods from interception.
XID mismatch across versions/environments — different Seata versions or deployments generate incompatible XIDs, branches can't join global transaction. Fix: unify Seata version and XID generation rules across all environments.
TCC Mode for Strong Consistency
AT provides eventual consistency with a brief inconsistency window — unacceptable for funds, inventory, core trading. TCC enforces strong consistency via three manual phases:
Try : validate params, lock resources, freeze balance/inventory
Confirm : actually deduct, confirm transaction
Cancel : release locks, unfreeze, restore inventory, refund balance
Advantages: full manual control eliminates inconsistency window; no snapshot dependency avoids AT rollback failures; financial-grade zero-risk. Drawback: high code intrusion, large dev effort, must handle idempotency, anti-hanging, empty rollback manually — only for core paths.
SAGA Mode for Long-Running Flows
Minute-level processes, multi-node chains, approvals, async flows — AT/TCC suffer from timeouts and resource locking. SAGA splits a long transaction into a sequence of local transactions:
Each step executes forward; on success, proceed to next.
On any failure, invoke corresponding compensating operations in reverse order.
Benefits: no long locks, no blocking, fits ultra-long business chains.
Real-World Failure Postmortem
Symptom
Intermittent (3-5 orders/week): order created, inventory deducted, balance deduction failed → data inconsistency.
Root Cause Analysis
Account service timeout triggered global rollback.
Order and Inventory services had already committed local transactions in Phase 1.
Order rolled back correctly; Inventory rollback failed because complex batch SQL prevented snapshot parsing .
Resolution
Refactor inventory batch updates into single-row updates.
Increase interface timeouts and global transaction timeout.
Enforce idempotency on all transaction endpoints; add reconciliation batch job as safety net.
Result: distributed transaction inconsistencies reduced to zero.
Microservice Transaction Architecture Closure
Async / MQ decoupled scenarios → Local Message Table (lightweight, stable, zero ops cost).
Ordinary synchronous short chains → Seata AT (high dev efficiency, good performance).
Funds, inventory, core trading → Seata TCC (strong consistency guarantee).
Long processes, approvals, tickets → Seata SAGA (fits long transactions).
XA → completely abandoned in production.
All distributed transaction endpoints: idempotency is baseline, monitoring is assurance, reconciliation is last line of defense .
There is no silver bullet for distributed transactions; scenario matching, rational selection, and pitfall avoidance are the core of architectural stability.
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.
Internet Backend Architecture Stack
Over ten years of hands-on Java backend architecture experience, focusing on sharing mainstream internet technologies such as Java backend, microservices, distributed systems, high concurrency, performance optimization, and source code analysis. Building a platform for Java technologists to exchange and grow, collaborating with peers to advance together.
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.
