Seata 分布式事务原理源码分析(一)UndoLog
SunRan

Seata 分布式事务原理源码分析(一)UndoLog

什么是UndoLog?

UndoLog 是 MySQL 中比较重要的事务日志之一,顾名思义是一种用于撤销回退的日志,在事务没提交之前,MySQL会先记录更新前的数据到 UndoLog 日志文件里面,当事务回滚时或者数据库崩溃时,可以利用 UndoLog 来进行回退。

主要作用有两个:

  1. 提供回滚操作,保证原子性

    MySQL事务的连个操作:commit 和 rollback;rollback

  2. 提供多版本并发控制,保证隔离型

    多个事务在操作时互不影响

UndoLog 存在 MySQL 的服务端中,面对微服务架构做不到共享。所以 Seata AT 模式通过 UndoLog 表实现分布式事务。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT transaction mode undo table';

UndoLogManager

UndoLog 统一由 UndoLogManager 接口管理,实现类有:MySQLUndoLogManagerOracleUndoLogManager ,对应了MySQL 和 Oracle 两大数据库。

1
2
3
4
5
6
7
8
9
private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME +
" (" + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + ClientTableColumnsName.UNDO_LOG_XID + ", "
+ ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", "
+ ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", "
+ ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" +
" VALUES (?, ?, ?, ?, ?, now(), now())";

private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME +
" WHERE log_created <= ? LIMIT ?";

以MySQL为例,存储了两条SQL语句,一条是插入一条是删除。分别用于开始全局事务后数据发生变动插入UndoLog,和事物提交或回滚后删除对应的UndoLog。

何时插入UndoLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void flushUndoLogs(ConnectionProxy cp) throws SQLException {
ConnectionContext connectionContext = cp.getContext();
String xid = connectionContext.getXid();
long branchID = connectionContext.getBranchId();

BranchUndoLog branchUndoLog = new BranchUndoLog();
branchUndoLog.setXid(xid);
branchUndoLog.setBranchId(branchID);
branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());

UndoLogParser parser = UndoLogParserFactory.getInstance();
byte[] undoLogContent = parser.encode(branchUndoLog);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Flushing UNDO LOG: {}", new String(undoLogContent, Constants.DEFAULT_CHARSET));
}

insertUndoLogWithNormal(xid, branchID, buildContext(parser.getName()), undoLogContent,
cp.getTargetConnection());
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void insertUndoLog(String xid, long branchID, String rollbackCtx,
byte[] undoLogContent, State state, Connection conn) throws SQLException {
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL);
pst.setLong(1, branchID);
pst.setString(2, xid);
pst.setString(3, rollbackCtx);
pst.setBlob(4, BlobUtils.bytes2Blob(undoLogContent));
pst.setInt(5, state.getValue());
pst.executeUpdate();
} catch (Exception e) {
if (!(e instanceof SQLException)) {
e = new SQLException(e);
}
throw (SQLException) e;
} finally {
if (pst != null) {
pst.close();
}
}
}

那么有个疑问,Seata是如何实现在增删改操作时自动插入UndoLog数据。

  • 本文标题:Seata 分布式事务原理源码分析(一)UndoLog
  • 本文作者:SunRan
  • 创建时间:2022-06-30 11:22:51
  • 本文链接:https://lksun.cn/2022/06/30/Seata 分布式事务原理源码分析(一)UndoLog/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论