第八章 插入数据
简介
通过更新类语句(insert,copy等)向表插入数据。表需要提供插入数据的接口。本文介绍这些接口以及数据插入表的过程。
插入数据涉及的组件:
- 表对象。数据要存入的表。
- 事务系统。
- 内存结构:RowGroup集合、RowGroup、ColumnData和ColumnSegment等。
插入数据的特征:
- 只追加。逻辑上是在表的尾部插入。数据在表中不是有序的。
- 分成两个阶段:事务未提交时,新数据在事务local storage中。提交时,会与表对象中的已有数据合并。
- 用状态记录追加操作的进展。
以从抽象到具体的方式来理解。RowGroup在逻辑上表示120K行数据。120K行数据不是全在一起的。内部又按列做了一层划分。列内部又按物理block再做一层划分。数据本身是记录在的block内存上的。ColumnSegment是物理block的逻辑抽象。
状态
列的数据类型可能不同、ColumnSegment的block上的空间可能不同。决定了Append状态不可能是单一状态,而是层次结构。多个状态共同构成了Append状态。
状态的层次结构:
- 数据先进入事务local storage。事务生命周期内,可能会向多个表写入数据。写每个表时都需要单独的状态。最顶层是LocalAppendState。
- 内存层次结构。
- 表级别:TableAppendState
- row group级别:RowGroupAppendState
- 列数据级别:ColumnAppendState。
- 块级别:CompressAppendState。内存block。
状态层次关系:
LocalAppendState{
TableAppendState{
RowGroupAppendState{
[]ColumnAppendState{
CompressAppendState{
}
}
}
}
}
每个状态会有一些字段,在不同阶段需要不同的字段。先理解每字段的含义,在后序讲插入的过程时,会再讲这些字段。
| 状态 | 字段 | 含义 | 初始值 | 作用阶段 | 变更 | 备注 |
|---|---|---|---|---|---|---|
| TableAppendState | _rowStart | 已有数据的总行数 或者新数据的开始行号。 | 已有数据的总行数 | 初始化阶段 | 初始化一次,后续不变 | |
| TableAppendState | _currentRow | 新数据的开始行号。_currentRow - _rowStart 表示已经插入数据的总行数。 | _rowStart | 执行阶段 | 每插入成功一批数据会更新。 | |
| TableAppendState | _totalAppendCount | 成功插入的总行数 | 0 | 执行阶段 | 每插入成功一批数据会更新。 | |
| TableAppendState | _startRowGroup | 要插入的第一个RowGroup | 插入的第一个RowGroup | 初始化+结束阶段 | 初始化后不再变更 | |
| TableAppendState | _remaining | 剩余的数据量 | 向事务local storage 插入数据阶段,值为0;同表中数据合并阶段,为要插入数据的总行数。 | 初始化;执行阶段 | 每插入成功一批数据会减掉一批的值。 | |
| RowGroupAppendState | _rowGroup | 要插入数据的RowGroup | 插入的第一个RowGroup | 初始化+执行 | 每次要更新RowGroup时,换成新RowGroup | |
| RowGroupAppendState | _offsetInRowGroup | RowGroup中已有的数据量 | 插入的第一个RowGroup的数据量 | 初始化+执行 | 每插入成功一批数据会更新。换新的RowGroup时,也会更新 | |
| ColumnAppendState | _current | 要插入的ColumnSegment | 要插入的第一个ColumnSegment | 初始化+执行 | ColumnSegment装满时,要换新的ColumnSegment | |
| ColumnAppendState | _appendState | 指向要插入数据的block | ColumnSegment.block | 初始化+执行 | 一个ColumnSegment满时,要换新的ColumnSegment。 |
插入数据过程
分为几步:
- 初始化阶段。给状态赋初值。
- 插入阶段。数据逐层进入并最终到达ColumnSegment的block上的过程。
- 结束阶段。数据插入完成时,更新版本信息。
初始化
1,分配局部存储LocalTableStorage。
- 表对象。要写入的表对象。
- RowGroup集合。数据先存入此处。
- 删除的行数。
- 索引信息。
2,初始化TableAppendState
_rowStart。用RowGroup集合的数据行数做初始值。在向局部存储插入时,值应该是0._currentRow。用_rowStart初始化。新数据的行号从此值开始。_totalAppendCount。总插入数据量。_startRowGroup。新数据要插入的第一个RowGroup。事务向表第一次插入数据时,需要先创建新的RowGroup,再给_startRowGroup赋值。_remaining。事务向表第一次插入数据时,值为0.
3,初始化RowGroupAppendState
_rowGroup。当前正要插入的RowGroup。_offsetInRowGroup。新数据在RowGroup中的偏移。RowGroup有120K行的总限制。 4,初始化ColumnAppendState_current。要插入的ColumnSegment。事务向表第一次插入数据时,需要先创建新的ColumnSegment。_appendState。block的内存。
插入
数据从最外层的接口到最终ColumnSegment的block内存上,经过多个内存层次结构。在每层,状态确定数据的流向。在每层接口,基于状态的分流逻辑,分流完成后,再更新状态。
从最外层接口到最内层接口,数据经过的接口:
| 接口 | 数据形式 | 状态 |
|---|---|---|
| DataTable.LocalAppend | chunk | LocalAppendState |
| LocalStorage.Append | chunk | LocalAppendState |
| RowGroupCollection.Append | chunk | TableAppendState |
| RowGroup.Append | chunk | RowGroupAppendState |
| ColumnData.Append | vector | ColumnAppendState |
| ColumnData.AppendData | UnifiedFormat | ColumnAppendState |
| ColumnSegment.Append | UnifiedFormat | ColumnAppendState |
| CompressAppend | UnifiedFormat | CompressAppendState |
- DataTable.LocalAppend。验证数据符合表的约束:主键,唯一健,not null(目前只支持这些)。
- LocalStorage.Append。数据先插入索引。要为每行数据计算rowid。先计算第一行的rowid,之后每行累加。
- RowGroupCollection.Append。
- 计算当前RowGroup还能插入多少行。120K行上限 减去 当前RowGroup中已经插入的数据行数。
- 为剩余数据创建新的RowGroup。变更RowGroupAppendState指向新RowGroup。
- RowGroup.Append。数据按列分别插入到每个列上。
- ColumnData.Append。vector转UnifiedFormat。
- ColumnData.AppendData。
- 尽可能将数据插入到当前ColumnSegment。
- block内存空间不够时,分配新ColumnSegment。并指向新ColumnSegment的block内存。
- ColumnSegment.Append。数据存入block内存的指定位置。
- CompressAppend。数据在block内存上写入的具体实现方法。根据数据类型有不同实现。这里不赘述。
结束
结束阶段是发生在一批数据插入结束。反应插入成功的结果信息。结束阶段与事务提交阶段是不同概念。结束阶段一定发生在事务提交前。不代表事务要提交。
- 完结状态。
- 更新版本信息。
从外层到内层,接口相对简单。
| 接口 | 状态 |
|---|---|
| DataTable.FinalizeLocalAppend | LocalAppendState |
| LocalStorage.FinalizeAppend | LocalAppendState |
| RowGroupCollection.FinalizeAppend | TableAppendState |
- RowGroupCollection.FinalizeAppend 遍历新数据所在的所有RowGroup(从_startRowGroup开始),给它们增加行版本信息。
小结
介绍了相关的状态、分流逻辑。内存的层次结构、分层的状态增加了理解插入过程的难度。