开篇
这篇文章的目的是介绍Fescar当中回滚日志undoLog的数据结构,为后续RM执行回滚动作rollback打下基础,因为undoLog涉及的数据结构用源码表示起来比较简单通俗易懂,所以直接根据源码进行讲解。
undoLog源码介绍
public class TableRecords {
// 表元数据记录
private TableMeta tableMeta;
// 表名
private String tableName;
// 查询获得的行记录
private List<Row> rows = new ArrayList<Row>();
}
public class Row {
// 表的行记录其实表中列字段的集合,Field是column字段。
private List<Field> fields = new ArrayList<Field>();
}
public class Field {
// 列名
public String name;
private KeyType keyType = KeyType.NULL;
// 列的字段类型。
public int type;
// 列的值
public Object value;
}
说明::
- 介绍UndoLog对象之前必须先了解构建UndoLog的原始记录beforeImage和afterImage对象。
- TableRecords beforeImage = beforeImage()中TableRecords保存SQL执行前后数据镜像。
- TableRecords顾名思义就是表当中的数据记录,保存从数据表中查询得到的行记录。
- TableRecords中的List是行记录的集合。
- Row中的List是单行当中的列字段column。
- Field是描述单列的数据结构,包含列的字段名,字段类型,字段值等。
public abstract class BaseTransactionalExecutor<T, S extends Statement> implements Executor {
protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
if (beforeImage.getRows().size() == 0 && afterImage.getRows().size() == 0) {
return;
}
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
// 生成TC的全局锁对象lockKeys
TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
// 根据SQL类型区分生成锁字段的原始素材
String lockKeys = buildLockKey(lockKeyRecords);
// 将锁字段保存到connectionProxy的维度
connectionProxy.appendLockKey(lockKeys);
// 准备SQLUndoLog
SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
// 将回滚日志保存到connectionProxy的维度
connectionProxy.appendUndoLog(sqlUndoLog);
}
}
说明:
- 根据SQL类型区分生成锁字段的原始素材beforeImage或afterImage。
- 将锁字段保存到connectionProxy的维度。
- 准备SQLUndoLog,通过beforeImage或afterImage。
- 将回滚日志sqlUndoLog保存到connectionProxy的维度。
public abstract class BaseTransactionalExecutor<T, S extends Statement> implements Executor {
protected String buildLockKey(TableRecords rowsIncludingPK) {
if (rowsIncludingPK.size() == 0) {
return null;
}
StringBuilder sb = new StringBuilder();
sb.append(rowsIncludingPK.getTableMeta().getTableName());
sb.append(":");
boolean flag = false;
for (Field field : rowsIncludingPK.pkRows()) {
if (flag) {
sb.append(",");
} else {
flag = true;
}
sb.append(field.getValue());
}
return sb.toString();
}
}
说明:
- 锁字段的生成逻辑是表名:primaryKey的列值。
- 锁字段的生成逻辑是表名加pk的值。
- 关注外层的for循环,如果变更多条记录即多个row记录,就拼接所有row记录的pk的值。
public class SQLUndoLog {
// SQL的类型
private SQLType sqlType;
// 表名
private String tableName;
// 执行前镜像
private TableRecords beforeImage;
// 执行后镜像
private TableRecords afterImage;
}
public abstract class BaseTransactionalExecutor<T, S extends Statement> implements Executor {
protected SQLUndoLog buildUndoItem(TableRecords beforeImage, TableRecords afterImage) {
SQLType sqlType = sqlRecognizer.getSQLType();
String tableName = sqlRecognizer.getTableName();
SQLUndoLog sqlUndoLog = new SQLUndoLog();
sqlUndoLog.setSqlType(sqlType);
sqlUndoLog.setTableName(tableName);
sqlUndoLog.setBeforeImage(beforeImage);
sqlUndoLog.setAfterImage(afterImage);
return sqlUndoLog;
}
}
说明:
- SQLUndoLog的数据结构如上所示,包括SQL类型、表名、执行前后镜像。
- buildUndoItem的构建UndoItem的逻辑就是保存上述提到的基础数据。
重点
个人认为重点在于undoLog的lockKey的生成逻辑以及保存的容器在于connectionProxy对象。