Seata is an open source distributed transaction solution dedicated to providing high performance and easy to use distributed transaction services. Seata will provide users with AT, TCC, SAGA, and XA transaction models to create a one-stop distributed solution for users.
Evolution from the two phases commit protocol:
For example:
Two transactions tx1 and tx2 are trying to update field m of table a. The original value of m is 1000.
tx1 starts first, begins a local transaction, acquires the local lock, do the update operation: m = 1000 - 100 = 900. tx1 must acquire the global lock before committing the local transaction, after that, commit local transaction and release local lock.
next, tx2 begins local transaction, acquires local lock, do the update operation: m = 900 - 100 = 800. Before tx2 can commit local transaction, it must acquire the global lock, but the global lock may be hold by tx1, so tx2 will do retry. After tx1 does the global commit and releases the global lock, tx2 can acquire the global lock, then it can commit local transaction and release local lock.
See the figure above, tx1 does the global commit in phase 2 and release the global lock, tx2 acquires the global lock and commits local transaction.
See the figure above, if tx1 wants to do the global rollback, it must acquire local lock to revert the update operation of phase 1.
However, now the local lock is held by tx2 which hopes to acquire the global lock, so tx1 fails to rollback, but it would try it many times until it's timeout for tx2 to acquire the global lock, then tx2 rollbacks local transaction and releases local lock, after that, tx1 can acquire the local lock, and do the branch rollback successfully.
Because the global lock is held by tx1 during the whole process, there isn't no problem of dirty write.
The isolation level of local database is read committed or above, so the default isolation level of the global transaction is read uncommitted.
If it needs the isolation level of the global transaction is read committed, currently, Seata implements it via SELECT FOR UPDATE statement.
The global lock is be applied during the execution of SELECT FOR UPDATE statement, if the global lock is held by other transactions, the transaction will release local lock retry execute the SELECT FOR UPDATE statement. During the whole process, the query is blocked until the global lock is acquired, if the lock is acquired, it means the other global transaction has committed, so the isolation level of global transaction is read committed.
For the performance consideration, Seata only does proxy work for SELECT FOR UPDATE. For the general SELECT statement, do nothing.
Take an example to illustrate it.
A business table:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
The sql of branch transaction in AT mode:
update product set name = 'GTS' where name = 'TXC';
Process:
select id, name, since from product where name = 'TXC';
Got the "before image":
id | name | since |
---|---|---|
1 | TXC | 2014 |
select id, name, since from product where id = 1;
Got the after image:
id | name | since |
---|---|---|
1 | GTS | 2014 |
UNDO_LOG
.{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
update product set name = 'TXC' where id = 1;
UNDO_LOG Table:there is a little bit difference on the data type for different databases.
For MySQL example:
Field | Type |
---|---|
branch_id | bigint PK |
xid | varchar(100) |
context | varchar(128) |
rollback_info | longblob |
log_status | tinyint |
log_created | datetime |
log_modified | datetime |
-- Note that 0.7.0+ adds the field context
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Review the description in the overview: A distributed global transaction, the whole is a two-phase commit model. The global transaction is composed of several branch transactions. The branch transaction must meet the requirements of the two-phase commit model, that is, each branch transaction must have its own:
According to the two-phase behavior mode, we divide branch transactions into Automatic (Branch) Transaction Mode and TCC (Branch) Transaction Mode.
The AT mode (Reference Link TBD) is based on a relational database that supports local ACID transactions:
Correspondingly, the TCC mode does not rely on transaction support of the underlying data resources:
The so-called TCC mode refers to the support of customized's branch transactions into the management of global transactions.
The Saga model is a long transaction solution provided by SEATA. In the Saga model, each participant in the business process submits a local transaction. When a participant fails, the previous successful participant is compensated. One stage is positive serving and The two-stage compensation services are implemented by business development.
Theoretical basis: Hector & Kenneth Post a comment Sagas (1987)