Seata 分布式事务原理源码分析(一)UndoLog
什么是UndoLog?
UndoLog 是 MySQL 中比较重要的事务日志之一,顾名思义是一种用于撤销回退的日志,在事务没提交之前,MySQL会先记录更新前的数据到 UndoLog 日志文件里面,当事务回滚时或者数据库崩溃时,可以利用 UndoLog 来进行回退。
主要作用有两个:
提供回滚操作,保证原子性
MySQL事务的连个操作:commit 和 rollback;rollback
提供多版本并发控制,保证隔离型
多个事务在操作时互不影响
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
接口管理,实现类有:MySQLUndoLogManager
和 OracleUndoLogManager
,对应了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数据。