
# 存储引擎
数据库存储引擎负责数据的存储、检索和管理。从整个数据库服务的组成架构来看，存储引擎向上对接SQL引擎，为SQL引擎提供或接收标准化的数据格式（元组或向量数组），向下对接存储介质，按照特定的数据组织方式，以页面、压缩单元（Compress Unit）或其他形式为单位，通过存储介质提供的特定接口完成读写操作。GaussDB通过静态编译使数据库专业人员可以为特定的应用程序需求选择专用的存储引擎。为了减少对执行引擎的干扰，提供行存访问接口层TableAM，用于屏蔽底层行存引擎带来的差异，使不同行存引擎可以分别独立演进。如下图所示。
![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/figure/zh-cn_image_0000002686630655.png "点击放大")
在此基础上，存储引擎通过日志系统提供数据的持久化和可靠性保障。通过并发控制（事务）系统保证并发读写操作之间的原子性、一致性和隔离性，通过索引系统提供对特定数据的加速寻址和查询能力，通过主备复制系统提供整个数据库服务的高可用能力。
行存引擎主要面向OLTP（OnLine Transaction Processing）类业务应用场景，适合高并发、小数据量的单点或小范围数据读写操作。行存引擎向上为SQL引擎提供元组形式的读写接口，向下以页面为单位通过可扩展的介质管理器对存储介质进行读写操作，并通过页面粒度的共享缓冲区来优化读写操作的效率。对于读写并发操作，采用多版本并发控制（MVCC，Multi-Version Concurrency Control）；对于写并发操作，采用基于两阶段锁协议（2PL，Two-Phase Locking）的悲观并发控制（PCC，Pessimistic Concurrency Control）。
当前，行存引擎默认的介质管理器采用磁盘文件系统接口，后续可扩展支持块设备等其他类型的存储介质。GaussDB行存引擎可以选择基于Append update的Astore或基于In-place update的Ustore。
#### 为什么需要存储引擎？
不同业务对数据写入和更新的诉求存在差异：有的以写入为主、更新较少，更关注写入吞吐与异常恢复的简单性；有的则存在大量更新，更关注高频更新下的空间膨胀控制与稳定的事务时延。同一种数据组织方式很难同时兼顾写入效率与更新时的空间膨胀控制，难以满足这些相互冲突的需求。
如果数据库只内置一种固定的存储方式，就只能在某一类场景下表现良好，难以适配多样化的业务。为此，GaussDB采用可插拔存储引擎架构：在统一的事务、日志、并发控制与缓存管理之上抽象出Table Access Method接口，将"如何存储数据"与"如何管理事务"解耦。用户可以按表选择Astore或Ustore存储引擎，从而在同一个数据库实例中，让不同业务特征的表各自使用最适合的存储引擎，兼顾性能、空间与可靠性。
#### 存储引擎的分类
在GaussDB中，存储引擎可分为Ustore与Astore。Ustore为原位更新引擎，是GaussDB Kernel的默认存储引擎，新旧版本分离存储、空间膨胀可控并支持完整闪回，更适合高并发更新场景；Astore为追加更新引擎，写入高效、恢复简单。
#### Ustore存储引擎
Ustore采用In-place Update更新模式，即原地更新模式，是存储内核新增的一种存储模式。区别于Append Update（追加更新）模式，Ustore存储引擎在频繁UPDATE（页面内更新）的场景下有较好的表现。
In-place Update存储模式提供"原地更新"能力，主要思路是将最新版本的"有效数据"和历史版本的"垃圾数据"分离存储。将最新版本的"有效数据"存储在数据页面上，而单独开辟一段Undo（回滚）空间，用于统一管理历史版本的"垃圾数据"，因此数据空间不会由于频繁更新而膨胀，垃圾回收效率更高。通过NUMA-aware的Undo子系统设计，使得Undo子系统在多核平台上高效扩展。同时通过对元组和数据页面结构的重新设计，减少存储空间的占用。采用多版本索引技术，解决索引膨胀问题，去除对Autovacuum（异步垃圾清理线程）机制的依赖，提升存储空间的回收复用效率。
#### Ustore存储引擎的优势是什么？
- **空间膨胀可控：**最新版本与历史版本分离存储，历史版本集中由Undo空间管理并可批量回收，频繁更新下数据页与索引的空间膨胀得到有效抑制。
- **原位更新、CTID相对稳定：**非索引列/索引列更新堆表均可原地完成，元组CTID保持相对稳定；大并发更新同一行时先到先得，更新时延相对稳定。
- **扫描范围更小、回表更少：**借助多版本B-tree索引（携带事务信息、可独立进行MVCC）提升IndexOnlyScan比例，大幅减少回表次数。
- **自治式空间回收：**不依赖VACUUM进行旧版本清理，索引与堆表解耦、可独立清理，I/O更平稳。
- **企业级闪回能力：**结合Undo空间可实现更高效、更全面的闪回查询、闪回表等，支持快速回退误操作。
 
#### Ustore存储引擎的使用场景
- **高频更新的OLTP** **场景**：如订单、账务等存在大量UPDATE/DELETE的业务，原位更新结合Undo分离存储，可有效抑制数据页与索引的空间膨胀。
- **大并发更新同一行**：原位更新机制保证元组CTID相对稳定、先到先得，更新时延相对稳定。
- **需要完整闪回与回收站能力**：依托Undo空间可实现更高效、更全面的闪回查询、闪回表等企业级恢复功能。
- **对空间可控性要求高**：通过DML过程中的动态页面清理去除对VACUUM的依赖，自治式空间管理避免异步清理带来的I/O抖动。
 
#### Ustore存储引擎的工作原理
主要功能模块如下表所示：
|    **模块**    |                                              **说明**                                              |
|---|---|
| Ustore表存取管理  | 向上对接SQL引擎，提供对Ustore表的行级查询、插入、删除、修改等操作接口，向下根据Ustore表页间、页内结构，以及Ustore表元组结构，完成对Ustore表文件的遍历和增删改查操作。 |
| Ustore索引存取管理 | 向上对接SQL引擎，提供对索引表的行级查询、插入、删除等操作接口，向下根据索引表页间、页内结构，以及索引表元组结构，完成对指定索引键的查找和增删操作。                      |
| Ustore表页面结构  | 包括Ustore表元组在页面内的具体组织形式，在页面内插入元组操作、页面整理操作、页面初始化操作等。                                               |
| Ustore表元组结构  | 包括Ustore表元组的结构、填充、解构、修改、字段查询、变形等操作。                                                              |
| Undo管理       | 包括Undo记录的结构、填充、编码、解码等操作。                                                                         |
| 多版本索引        | 包括Ustore专用多版本索引UB-tree的页面结构、查询、修改、可见性检查、垃圾回收等模块。                                                 |
   
**示例**
- 创建一个Ustore存储引擎表，插入部分数据，查看元组位置信息。
  ```
  -- 创建一个Ustore表
   gaussdb=# CREATE TABLE tb_t1(id int, name VARCHAR(20)) WITH (STORAGE_TYPE=Ustore); 
   CREATE TABLE 
    
  -- 插入数据
   gaussdb=# INSERT INTO tb_t1 VALUES (1,'Joe'),(2,'Jack'); 
   INSERT 0 2 
    
  -- 查看元组的ctid信息
  -- ctid（blkno, offset）为元组在数据页上的位置，blkno为页面编号，offset是元组在页面内的偏移量。
  gaussdb=# select *, ctid from tb_t1; 
   id | name | ctid   
  ----+------+------- 
    1 | Joe  | (0,1) 
    2 | Jack | (0,2) 
  (2 rows)
  ```
  
- 修改一条记录，查看元组位置信息，可以看出元组的ctid并未发生变化，即被原位更新了；更新前的旧数据会被存放至Undo空间。
  ```
  -- 修改id为2的元组
  gaussdb=# UPDATE tb_t1 SET name='Scott' WHERE id=2; 
  UPDATE 1 
     
  -- 再次查看ctid信息
  gaussdb=# select *, ctid from tb_t1; 
    id | name  | ctid   
  ----+-------+------- 
    1 | Joe   | (0,1) 
    2 | Scott | (0,2) 
  (2 rows)
  ```
  
 
#### 回滚段与MVCC
- **回滚段**
  旧版本数据会集中在回滚段的Undo目录中，为了减少读写冲突，旧版本数据（回滚记录）采用追加写的方式写入数据目录的Undo目录下。这样旧版本数据的读取和写入不会发生冲突，同一个事务的旧版本数据也会连续存放，便于进行回滚操作。为了减少并发写入时的竞争，Undo目录空间被划分成多个逻辑区域（UndoZone，回滚段逻辑区域）。线程会在自己的逻辑区域上进行分配，与其他线程完全隔离，从而写入旧数据分配空间时就不会有额外的锁开销。UndoZone还可以按照CPU的NUMA核进行划分，每个线程会从当前的NUMA核上的UndoZone进行分配，进一步提升分配效率。在分配Undo空间时会按照事务粒度进行记录，旧版本数据一旦确认没有事务进行访问，就会进行回收。
  
- **MVCC**
  Ustore的可见性检查和Astore类似，将快照CSN和元组删除和插入事务的CSN进行比较，判断元组是否可见。Ustore和Astore使用同一套事务管理机制和快照管理机制。
  Ustore和Astore最大的区别在于Astore会在页面上保留旧版本数据，而Ustore会将旧版本数据放到回滚段统一存放。在需要获取旧版本数据时，Astore可以直接从tuple的头部读取到元组的插入和删除的事务号(XID)，来判断元组的可见性。但是Ustore需要从回滚段里读取旧版本的事务信息，来判断旧版本是否可见。由于从回滚段中读取旧版本数据存在相对昂贵的开销，Ustore通过一系列的优化手段来避免从回滚段中读取旧版本数据。
  Ustore在获取元组时，会先检查对应的事务目录。事务目录分为有效和无效两种。当事务目录是有效的，Ustore直接就会得到元组上最新的事务。
  如果事务目录被冻结（FROZEN），意味着元组已经在所有的事务中都会可见。如果事务目录中的事务id小于oldestXidInUndo，意味着元组已经足够旧，在所有事务中都可见。同时会把事务目录置成冻结，来加速后续的查询。
  如果元组被标记有一个无效事务目录，意味着修改元组的事务已经提交，并且比当前的事务目录中的事务旧。此时Ustore会使用事务目录中的事务进行可见性判断。如果可见，意味着修改元组的事务已经可见，就不需要从Undo目录中再读取事务信息。
  
 
#### 空间管理和回收
不同于Astore的空间管理和回收机制，Ustore实现了自治式的空间管理机制。Ustore中的堆以及索引的空间分配和回收都在业务运行的过程中平稳地进行，不依赖VACUUM及Autovacuum清理机制。
- **自治式堆页面空间管理**
  Ustore中堆页面的自治式空间管理，建立在与Astore类似的轻量级堆页面清理机制的基础上。在执行DML及DQL操作的过程中，Ustore都会进行堆数据页面清理，以取代VACUUM清理机制。Ustore会清理已经提交的被删除元组。
  对于Astore而言，复用数据元组的行指针前必须保证对应的索引元组已经被清理。这是为了防止通过索引元组访问已经被复用的行指针，导致取到错误的数据。在Astore中需要通过VACUUM操作将这样的无效索引元组统一清除掉后才能复用行指针，这使得堆页面和索引页面的清理逻辑耦合在一起，也会导致间断性的大量I/O。在Ustore中能高效地单独进行数据和索引页面的清理，因为带有版本信息的UB-tree能够独立检测并过滤掉无效的索引元组，不会通过无效索引元组访问对应的数据表。
  堆页面的空间管理机制复用GaussDB中的FSM来管理UHeap中的可用空间。在成功对页面进行清理后，会将其空闲空间刷新到对应的FSM页面中。为了避免每次页面清理都需要更新整个树状结构的FSM，从而带来额外的开销，引入了一个更新整个FSM的概率计算。考虑当前清理后的可用空间占预留可用空间（Reserved Free Space）阈值的百分比，计算得出清理一个页面后更新FSM的概率。也就是说，页面清理获得的可用空间越大，更新整个FSM的概率也就越大。
  当数据元组被删除时，会在页面上记录对应的潜在空闲空间（Potential Free Space），该值用于估计页面上的空闲空间。在运行过程中，有多个场景会对页面尝试进行清理。DML语句执行过程中，INSERT、UPDATE以及DELETE操作都会拿到页面的写锁。如果发现空间不足，或者检测到潜在空闲空间到达某个阈值，会尝试对页面进行清理。DQL查询语句执行的过程中若检测到页面上潜在空闲空间到达阈值，也同样会尝试申请页面的写锁；如果拿到了页面的写锁，会尝试对页面进行清理。
  由于当前的清理机制是基于访问进行清理的，存在部分页面有可清理的元组，但一直不被访问的页面，导致不能通过这一机制正确地清理。为了解决这一问题，引入了基于概率的清理方案。在一些寻找新的可用空间页面的操作中，若通过FSM发现没有足够的可用空间，在对物理文件进行扩展前，会"随机"选取一些页面进行清理，该清理算法能找出这些潜在需要清理的页面。
  
- **自治式索引页面空间管理**
  索引页面的空间管理不依靠FSM数据结构，而是依靠特有的URQ（UB-tree Recycle Queue）结构，简称为回收队列。索引回收队列单独储存在UB-tree索引对应的.urq文件中，没有原有B-tree索引的.fsm文件。
  索引中的回收队列分为两部分，一部分是潜在空页队列（Potential Empty Page Queue），一部分是可用页面队列（Available Page Queue）。两个队列都是跨页面的循环队列，其中每个元素都会储存blkno以及XID。其中blkno表示该元素对应索引页面的block number；XID表示该页面在何时能够被回收或复用。这些元素在循环队列单个页内按照XID的顺序进行排序，以便于快速找到XID小（最可能被回收或复用）的页面。
  对于潜在空页队列而言，里面存放页内元组已经被全部删除但还没有全部无效的页面，其中的XID就标志页面中最后一个元组无效的可能时机。在系统整体的oldestXmin超过该XID后，该页面就有可能被从索引上删除，但也可能因为新插入元组或删除元组的事务中止而导致页面不能被删除。潜在空页队列中的页面在成功被删除后会被放入可用页面队列，并记录删除时最新事务的XID。
  对于可用页面队列而言，里面存放已经被删除，可以或即将可以被复用的页面。其中XID就表示该页面可以被复用的时机。这样的页面复用时延是来自UB-tree索引页面删除时可能的并发访问导致的。
  在业务运行的过程中，索引会不断尝试对潜在空页队列中的页面进行回收。在索引申请新的页面时，会查找当前可用的空闲页面。当可用页面队列中没有可用页面时，一般会通过扩展索引物理文件的方式来获得新的页面。但也存在物理文件批量扩展，或扩展后还未来得及使用就出错退出的情况。此时在回收队列的元信息页面中保存了已正确追踪的页面数量，若该数量少于整个索引表的页面数量，会尝试去使用这一部分未追踪的页面，并更新已追踪的页面数量。
  
 
#### Astore存储引擎
Astore（Append Store追加存储）是GaussDB存储引擎设计模型中的一种，数据在这种模型下是以追加的方式进行存储的。当数据更新时，会追加到现有数据的末尾，而不是覆盖旧的数据。
#### Astore存储引擎的优势是什么？
- **写入高效：**采用追加存储，数据直接追加到末尾、避免数据的移动与重建，写入性能高，适合频繁插入、少量更新的场景。
- **异常恢复简单：**没有回滚段，旧数据记录在原数据文件中，数据库异常crash后恢复无需像Ustore那样同步Redo和Undo，恢复流程更简单。
- **回滚快：**回滚并不删除数据，因此回滚可以很快完成。
- **WAL** **日志更简单：**仅需记录数据文件的变化，不需要记录回滚段的变化。
 
#### Astore存储引擎的使用场景
- **写入操作频率较多，更新较少的业务**：如日志、流水、归档等以INSERT为主、UPDATE/DELETE较少的场景，追加写可获得较高的写入吞吐。
- **历史数据留存**：旧版本数据不会被立即覆盖，适合需要保留历史记录、便于数据恢复与重建的场景。
- **对恢复简单性要求高**：希望异常恢复与回滚流程尽量简单、快速的场景。需要注意的是，Astore在频繁更新时易出现空间膨胀，需配合VACUUM定期清理。
 
#### Astore存储引擎的工作原理
**整体框架**
作为行存储子格式之一，Astore有自己的堆表元组结构、堆表页面结构、元组多版本机制，以及空闲空间管理和回收机制。
Astore以堆表形式存储数据，表中数据没有特定顺序，元组更新基于在同页面追加写实现，因此Astore堆表页面上新旧元组可以共存。
主要功能模块如下表所示：
|      **模块**      |                                                                  **说明**                                                                   |
|---|---|
| Astore访存管理       | 提供Astore行存储格式表的具体访存操作实现，包括：对Astore堆表的行级查询、插入、删除、修改等操作接口；Astore堆表行级多版本机制和元组可见性判断；根据Astore堆表页间、页内结构，以及Astore堆表元组结构，完成对Astore堆表文件的遍历和增删改查操作。 |
| Astore索引访存管理     | 向上对接SQL引擎，提供对索引表的行级查询、插入、删除等操作接口，向下根据索引表页间、页内结构，以及索引表元组结构，完成对指定索引键的查找和增删操作。                                                               |
| Astore堆表/索引表页面结构 | 包括Astore堆表/索引表元组在页面内的具体组织形式，在页面内插入元组操作、页面整理操作、页面初始化、页面加解密、页面CRC（Cyclic Redundancy Check，循环冗余码校验）校验操作等。                                    |
| Astore堆表元组结构     | 包括Astore堆表元组的结构、填充、解构、修改、字段查询、变形、压缩、解压等操作。                                                                                                |
| 空间管控             | 结合FSM（Free Space Map），支持轻量级在线空间管控以及异步清理（Autovacuum）等空间管控机制。                                                                               |
   
**示例**
- 创建一个Astore存储引擎表，插入部分数据，查看元组位置信息。
  ```
  -- 创建一个Astore表
  gaussdb=# CREATE TABLE t1(id int, name VARCHAR(20)) WITH (STORAGE_TYPE=Astore);
  CREATE TABLE
  -- 插入数据
  gaussdb=# INSERT INTO t1 VALUES (1,'Joe'),(2,'Jack');
  INSERT 0 2
  -- 查看元组的ctid信息
  -- ctid（blkno, offset）为元组在数据页上的位置，blkno为页面编号，offset是元组在页面内的偏移量。
  gaussdb=# select *, ctid from t1;
   id | name | ctid
  ----+------+-------
    1 | Joe  | (0,1)
    2 | Jack | (0,2)
  (2 rows)
  ```
  
- 修改一条记录，查看元组位置信息。
  ```
  -- 修改id为2的元组
  gaussdb=# UPDATE t1 SET name='Scott' WHERE id=2;
  UPDATE 1
  -- 再次查看ctid信息
  gaussdb=# select *, ctid from t1;
   id | name  | ctid
  ----+-------+-------
    1 | Joe   | (0,1)
    2 | Scott | (0,3)
  (2 rows)
  ```
  
可以看出被更新元组的ctid由(0,2)变为(0,3)，新元组追加到现有数据的末尾，而不是覆盖旧的数据，为非原位更新。旧版本数据依然存放在当前页面。
**MVCC**
Astore支持单独的多版本元组并发控制机制，即为同一条记录保留多个历史版本的物理元组以解决对同一条记录的读、写并发冲突（读事务和写事务工作在不同版本的物理元组上）。
Astore存储格式为追加写优化设计，其元组历史版本与当前版本都存储在页面中。当一个更新操作将v0版本元组更新为v1版本元组之后，如果v0版本元组所在页面仍然有空闲空间，则直接在该页面内插入更新后的v1版本元组，并将v0版本的元组指针指向v1版本的元组指针。在这个过程中，新版本元组以追加写的方式和被更新的旧版本元组混合存放，这样可以减少更新操作的I/O开销。然而，需要指出的是，由于新、老版本元组是混合存放的，因此在清理旧版本元组时需要的清理开销会比较大。因此，Astore存储格式比较适合频繁插入、少量更新的业务场景。
Astore的元组中会存储事务信息（xmin，xmax等），xmin表示元组被插入时的事务信息，xmax表示元组被删除或更新时的事务信息，在默认隔离级别下（读已提交），只需结合CSN（Commit Sequence Number，事务提交序列号）判断元组上的事务信息是否可见即可判断元组是否可见，例如，将元组从v0版本更新到v1版本，并发事务中有快照查询到该元组，元组v0可见的条件是：xmin字段对应的CSN值小于等于查询快照的CSN，且xmax对应的CSN值大于读查询快照的CSN；元组v1可见的条件是：xmin字段对应的CSN值小于等于读查询快照的CSN（v1元组的xmax字段为0，无需判断）。当v0元组不可见时，可以通过v0元组上记录的ctid找到v1元组，继续判断其可见性。
**空间管理和回收**
Astore中采用最大堆二叉树结构来记录和管理堆表页面的空闲空间，该最大堆二叉树结构按照页面粒度进行与存储介质的读写操作，并单独储存于专门的空闲空间位图文件中（Free Space Map，简称FSM）。
所有页面分为叶子节点页面和内部节点页面两种。两种页面的页面内部结构完全相同，区别在于：对于叶子节点页面，其页面中记录的二叉树的叶子节点对应堆/索引表页面的空闲空间程度；对于内部节点页面，其页面中记录的二叉树的叶子节点对应下层FSM页面的最大空闲空间程度。
使用FSM页面中的1个字节（即256档）来记录一个堆/索引页面的空闲空间程度。在FSM页面中不会记录任何堆/索引页面的页号信息，也不会记录任何根、子FSM节点页面的页号信息，这些信息主要通过以下的规则来计算得到：
1.在一个FSM页面内部，二叉树节点按照从上到下、从左到右逐层排布，即：第一个字节为根节点的空闲程度，第二个字节为第一层内部节点最左边节点的空闲程度，依次类推。
2.所有FSM页面在物理存储上采用深度优先顺序，即某个FSM页面之前所有的物理页面包括：该FSM页面所在子树的所有上层节点，加上该FSM页面所有左侧子树。
3.所有FSM叶子节点页面中的二叉树的叶子节点，对应堆/索引表页面的空闲空间程度，且根据从左到右的顺序，分别对应第1个、第2个、...、第n个堆/索引表物理页面。
4.除了[3]中这些FSM节点之外，其他FSM父节点保存子节点（子树）中空闲空间的最大值。根据上述算法，可以高效地查询出具有足够空闲空间的堆/索引页面的页面号，并将待插入的数据插入其中。
空闲空间的管理难点在于空闲空间的回收。在GaussDB中，对于Astore存储格式，有3种回收空闲空间的方式。
- **轻量级堆页面清理**
当查询扫描到某个Astore堆表页面时，会借机尝试清理该页面上已经被删除的旧元组。由于只是顺带清理该页面内容，因此只能删除元组内容本身，元组指针还需要保留，以免在索引侧造成空引用或空指针。一个比较特殊的情况是HOT场景。HOT场景是指对于该表上所有的索引更新前后的索引键值均没有发生变化，因此对于更新后的元组只需要插入堆表元组而不需要新插入索引元组。对于同一个页面内一条HOT链上的多个元组，如果它们都足够旧了，那么在清理时可以额外删除所有中间的元组指针，只保留第一个版本的元组指针，并将其重定向到第一个不用被清理的元组版本的元组指针。
- **中量级堆页面和索引页面清理**
GaussDB提供VACUUM语句来让用户主动执行对某个Astore表（或某个库中所有的Astore表）及其上的索引进行中量级清理。中量级清理过程中，不阻塞相关表的查询和DML操作。由于在Astore表中，新、旧版本元组是混合存储的，因此，与顺带执行的轻量级清理相比，Astore表的中量级清理需要进行全表顺序（或索引）扫描，才能识别出所有待清理的旧版本元组。对于扫描出来的确认要清理的元组，会首先清理索引中的元组，然后再清理堆表中的元组，从而可以避免出现索引空指针的问题。
- **重量级堆页面和索引页面清理**
无论是轻量级清理，或是中量级清理，都只能局部清理Astore页面中的死亡元组，无法真正实现对这些空闲空间的释放（被清理出的空间，仍然只能被该表使用）。因此，GaussDB还提供了VACUUM FULL语句来让用户主动执行对某个Astore表（或某个库中所有Astore表）及其上的索引进行重量级清理。重量级清理将一个表中所有仍未死亡（但是可能已经被删除）的元组重新紧密插入到新的堆表文件中并在此基础上重新创建所有索引，从而实现对空闲空间的彻底回收。在重量级清理的主体流程中只允许用户执行只读查询操作，在重量级清理的提交流程中只读查询操作也会被阻塞。为了尽可能提高重新创建的索引性能，如果用户堆表上有索引，那么上述全表扫描会采用索引扫描。
#### Astore与Ustore的区别
Ustore与Astore都是GaussDB支持的存储引擎，二者的共同点在于：都基于统一的事务、日志与并发控制机制，都用于承载行式组织的业务数据，并都支持回收站（闪回DROP/TRUNCATE）功能。
二者的核心差异在于多版本的实现方式：Ustore采用原位更新，最新版本与历史版本分离存储，并依赖Undo回滚段管理历史版本；Astore采用追加存储，最新版本与历史版本不分离、无回滚段。这一差异主要源于设计目标不同------Ustore追求更新场景下的空间可控与功能丰富，Astore追求简单可靠的恢复与高效写入。
因此，Ustore更适合高频更新的OLTP场景，空间膨胀可控并支持完整闪回，但更新与单条扫描会带来略高的开销；Astore异常恢复流程更简单、回滚更快，但频繁更新时易出现空间膨胀。
| **对比维度** | **Ustore** **（原位更新）**    | **Astore** **（追加更新）** |
|:---|:---|:---|
| 共同点      | 均为行存引擎，基于统一事务/日志/并发控制，均支持回收站闪回DROP/TRUNCATE。    ||
| 多版本实现    | 原位更新，新旧版本分离存储。           | 追加存储，新旧版本不分离。         |
| 回滚段      | 有Undo回滚段。                | 无回滚段。                 |
| 空间管理     | 旧版本可批量回收，膨胀可控。           | 频繁更新易空间膨胀。            |
| 异常恢复     | 需Redo+Undo，恢复较复杂。        | 流程简单，回滚快。             |
| 闪回能力     | 支持闪回查询/表/DROP/TRUNCATE等。 | 仅支持闪回DROP/TRUNCATE。   |
   
#### 与存储引擎相关的特性和操作
在了解存储引擎的核心概念与分类后，如需了解更多细节以及在实际业务中配置使用，可参考以下对应章节：
- 设置存储引擎：建表时通过\`WITH (STORAGE_TYPE)\`指定引擎类型，并可通过GUC参数\`enable_default_ustore_table\`控制行存表默认引擎，详情请参考[设置存储引擎](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-0514.html)章节。
- 查询表的存储方式：查看现有表所使用的存储引擎，详情请参考[如何查询表的存储方式？](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-1152.html)章节。
- Ustore使用与最佳实践：了解Ustore的特性规格、测试方法与最佳实践，详情请参考[Ustore存储引擎](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-0521.html)章节。
 
