跳到主要内容

第八章 插入数据

简介

通过更新类语句(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_offsetInRowGroupRowGroup中已有的数据量插入的第一个RowGroup的数据量初始化+执行每插入成功一批数据会更新。换新的RowGroup时,也会更新
ColumnAppendState_current要插入的ColumnSegment要插入的第一个ColumnSegment初始化+执行ColumnSegment装满时,要换新的ColumnSegment
ColumnAppendState_appendState指向要插入数据的blockColumnSegment.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.LocalAppendchunkLocalAppendState
LocalStorage.AppendchunkLocalAppendState
RowGroupCollection.AppendchunkTableAppendState
RowGroup.AppendchunkRowGroupAppendState
ColumnData.AppendvectorColumnAppendState
ColumnData.AppendDataUnifiedFormatColumnAppendState
ColumnSegment.AppendUnifiedFormatColumnAppendState
CompressAppendUnifiedFormatCompressAppendState
  • 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.FinalizeLocalAppendLocalAppendState
LocalStorage.FinalizeAppendLocalAppendState
RowGroupCollection.FinalizeAppendTableAppendState
  • RowGroupCollection.FinalizeAppend 遍历新数据所在的所有RowGroup(从_startRowGroup开始),给它们增加行版本信息。

小结

介绍了相关的状态、分流逻辑。内存的层次结构、分层的状态增加了理解插入过程的难度。