
# 在业务开发前选择合适的表类型以提高开发效率和查询性能
数据库的存储模型直接影响数据处理的性能与效率，本文对当前DWS支持的表类型进行介绍，从存储格式进行分类，对各种类型的表的适用场景进行介绍，并提供了一些简单的使用示例，方便业务进行表选型和开发。
#### 表的存储格式
从数据的存储结构上看，DWS支持两种存储格式：**行存，列存**。
![](https://support.huaweicloud.com/bestpractice-dws/public_sys-resources/note_3.0-zh-cn.png)
需要注意的是，行列混存表本质上也是列存格式。
- **行存表** ：以数据行（Row）为最小物理存储单元，同一行的所有字段在物理上相邻连续存储（包括空值占位）。可以通过行标识符tid定位到一整行的数据。
  行存表的数据物理存储示例：
  ```
  | ID  | Name  | Age | Salary | Department | 
  |---|---|---|---|---|
  | 001 | 张三  | 30  | 15000  | 技术部     | ← 完整行数据连续存储
  | 002 | 李四  | 28  | 12000  | 市场部     |
  ```
  

- **列存表** ：以数据列（Column）为最小物理存储单元，每列的数据连续存储，同列数据连续排列，不同列独立存储为不同的存储单元。
  列存表的数据物理存储示例：
  ```
  Column: ID      → | 001 | 002 | 003 |...
  Column: Name    → | 张三 | 李四 | 王五 |...
  Column: Age     → | 30 | 28 | 35 |...
  ```
  
 
#### DWS支持的表类型
目前DWS支持的表类型有：**行存表，列存2.0表，Hstore_opt表，存算分离表，行列混存表**。
- DWS的系统表以及业务默认创建的表都是行存表。
- **列存2.0表，Hstore_opt表，存算分离表和行列混存表都属于列存表**，这几种列存表都是以数据列存储为基础演化出来的，分别适用于不同的业务场景。
 
#### 行存表介绍
行存表的一行数据可以通过一个位置指针（索引）一次找到，并且在读取时一次读取至少一整行数据。
基于以上特点，行存储的优势主要有两个方面：
- **点查性能好**：在点查场景下可以直接索引到某行数据的元组位置；
- **更新效率高**：行存储在实时并发入库，并发更新方面都比较有优势。
但是也有一些缺点：
- **存储时空间占用大**：当前DWS采用多版本保存更新前后的数据，造成脏数据也占用存储空间；
- **读放大**：读取时需要读取一整个页面和元组，如果是大宽表，但是又只读取一两列数据的情况下，读放大的问题比较严重。
对于传统的OLTP业务，数据需要进行频繁的增删改查的场景，比较适合使用行存表。
举例：
```
-- 默认类型，指定 ORIENTATION = ROW 或者默认即可。
CREATE TABLE table_row
(
    c1 INT,
    c2 TEXT
) WITH (ORIENTATION = ROW);
```
#### 列存表介绍
行形式存储的表如果涉及到分析查询场景，特别是在数据量大且复杂的查询时，就会遇到性能瓶颈，这时可以选择列存储。
列存储的优势主要有两方面：
- **批量查询性能好**：当分析查询只涉及某列或者某几列，不需要访问无关列，特别是在表的宽度比较大时（如一千列），优势更加明显。
- **空间占用小**：列存储支持压缩，而且由于单列数据类型相同，压缩性能更高。
在OLAP场景下，优选列存表，以下按不同使用场景分别介绍列存2.0表、Hstore_opt表、存算分离表(3.0列存表)、行列混存表。
#### 列存2.0表
列存2.0表是最初的列存表，各列数据按一定数量组成一个存取单元（CU），压缩后写入一个物理文件中，通过辅助表控制以CU为单位的事务可见性。
列存2.0表目前在已上线的各种业务中广泛使用。
举例：
```
-- 指定 ORIENTATION = COLUMN 选项
CREATE TABLE table_col
(
    c1 INT,
    c2 TEXT                                     
) WITH (ORIENTATION = COLUMN);
```
#### Hstore_opt表
列存2.0表虽然在海量复杂查询方面相比行存表有优势，但是也存在比较明显的短板：由于事务可见性基于CU，所以基本无法支持并发更新入库。
随着业务复杂程度的提升和实时入库+查询诉求越来越多，Hstore_opt表应运而生。Hstore_opt表融合行存和列存的优势，在列存2.0的基础上业务入库并发进行了优化：使用一个行存表存储实时的增删改操作信息，然后通过后台线程将行存表数据merge到CU中，保证了数据的压缩能力和分析能力。
由于近些年实时业务的需求越来越多，Hstore_opt表已逐步取代列存2.0表，成为首选的列存表类型。
举例：
```
-- 在列存的基础上指定 ENABLE_HSTORE_OPT = TRUE 选项
CREATE TABLE table_hstore
(
    c1 INT,
    c2 TEXT                                     
) WITH (ORIENTATION = COLUMN, ENABLE_HSTORE_OPT = TRUE);
```
#### 存算分离表
Hstore_opt表解决了实时业务的问题，但是由于列存表的AppendOnly属性，实时业务中大量的并发插入或更新会导致CU文件快速膨胀，而EVS存储价格不低，所以用户成本很高。
存算分离表在Hstore_opt表的基础上将CU文件存储到OBS（华为分布式对象存储，一种高可靠、低成本的海量持久化存储产品）中，搭配本地EVS盘的diskcache缓存能力，既可以尽可能地保证读取和写入的性能，又能有效降低用户成本。同时依托OBS的分布式存储能力，可以做到存储无限扩展，计算能力快速扩缩。
对于比较重视存储成本以及弹性能力的业务，可以选择存算分离表。
举例：
```
-- 需要选择存算分离集群
-- 在hstore表的基础上增加 COLVERSION = 3.0 选项
CREATE TABLE table_v3
(
    c1 INT,
    c2 TEXT                                     
) WITH (ORIENTATION = COLUMN, ENABLE_HSTORE_OPT = TRUE, COLVERSION = 3.0);
```
#### 行列混存表
列存2.0、Hstore_opt表、存算分离表虽然在批量查询、实时入库、空间管理方面都做了各种优化，但还是无法做到行存表的点查性能。行列混存表则用空间换时间，将行列格式混合存储，通过占用更多的空间来换取点查性能的提升。
行列混存表在一张表中同时存储行格式数据和列格式数据，两种格式各自独立维护、同步更新，由查询优化器根据实际查询路径选择最优访问方式，通过列存固有的过滤优化能力实现更智能的I/O路径控制，自动选择访问行/列格式数据。确保提供列存储高压缩、高吞吐优势的同时，保留行存储对局部数据的灵活访问能力。
行列混存表适用于混合查询场景下的高效数据调度。行列混存表可以显著降低整体I/O开销，同时提升数据命中率与查询性能。
举例：
```
-- 在hstore表的基础上增加 STORAGE_MODE = MIX 选项
CREATE TABLE table_mix
(
    c1 INT,
    c2 TEXT                                     
) WITH (ORIENTATION = COLUMN, ENABLE_HSTORE_OPT = TRUE, STORAGE_MODE = MIX);
```
#### 表类型选择建议
行存储和列存储各有优势，没有绝对的优劣之分。行存储在事务处理方面表现出色，而列存储在大数据分析领域优势明显。随着技术的发展，二者的界限正逐渐模糊，现代数据库系统越来越多地采用混合策略来兼顾不同场景的需求。数据库选型时，应根据具体的应用场景、查询模式和性能要求来选择合适的存储模型。
表1不同表类型差异 
| 对比维度   | 行存表                                            | 列存2.0                                                  | Hstore_opt表                                                       | 存算分离表                         | 行列混存表                                                           |
|:---|:---|:---|:---|:---|:---|
| 数据存储方式 | 以元组为单位，将每一条数据的所有属性值存储到临近的空间里。                  | 以CU（Compress Unit）为单位，将单个属性的所有值存储到临近的空间里。              | 数据主要以CU形式存储在列存主表上，对于被更新的列、小批量插入的数据将被序列化后存储到新设计的Delta表上。           | 类似hstore表，CU文件写入到OBS中。        | 存储行格式数据和列格式数据。                                                  |
| 数据写入   | 没有压缩能力，数据按原始状态存储，磁盘空间占用较大。                     | 支持压缩，同一列属性值类型相同，压缩效果好。数据写入时能极大节省IO资源与磁盘空间占用。           | 批量插入的数据直接写入CU，具有与列存一致的压缩优势。被更新的列、小批量插入的数据会序列化后压缩。同时定期merge到主表CU。  | 类似hstore表，CU数据异步写入OBS。        | 类似hstore，数据按行/列格式同时存入CU，占用空间变大，但得益于压缩能力，空间占用比行存表小。              |
| 数据更新   | 数据按行更新，没有CU锁问题，并发更新（update/upsert/delete等）性能好。 | 即使更新单条数据，也要获取整个CU的锁，基本无法支持并发更新（update/upsert/delete等）。 | 彻底解决列存更新的CU锁问题，并发更新（update/upsert/delete等）的性能达到行存的60%以上。          | OBS相较本地盘读写速度较慢，通过异步写方式加快写入速度。 | 并发更新能力与hstore表相似。                                               |
| 数据读取   | 按行读取，即使只需访问某一列的数据，也需要将一整行的数据取出。非点查性能较差。        | 按列读取时只需访问该列的CU，再加上CU的压缩优势导致需要占用的IO资源更少，单列读取性能很好。       | 对于列存主表的数据按列读取，对于被更新的列、小批量插入的数据会反序列化后取出，数据merge到主表后具有与列存一致的数据读取优势。 | 通过本地盘diskcache能力加速读取。         | 由查询优化器根据实际查询路径选择最优访问方式，通过列存固有的过滤优化能力实现更智能的I/O路径控制，自动选择访问行/列格式数据 |
| 优点     | 并发更新性能好，点查能力强。                                 | 查询性能好，磁盘占用空间少。                                         | 并发更新性能好，数据MERGE后具有与列存一致的查询性能优势与压缩优势。                              | 扩缩能力强，存储成本低。                  | 兼顾点查性能和空间压缩能力，比行存表磁盘占用小。                                        |
| 缺点     | 占用磁盘空间多，查询性能差。                                 | 基本无法支持并发更新。                                            | 需要后台常驻线程对HStore表进行merge清理操作。先merge到CU主表再进行清理，与SQL语法中的Merge无关。     | OBS相较本地盘读写速度较慢。               | 相比列存表和hstore表，磁盘占用较高。                                           |
| 适用场景   | 更新删除操作频繁的TP事务场景。点查询（基于索引的、返回数据量小的简单查询）。        | 查询分析为主的AP场景。数据量大，存入后的更新删除操作少。                          | 实时并发入库场景。需要支持高并发的更新入库操作以及高性能的查询效率。                                | 对实时扩展性要求较高或者存储成本敏感的场景。        | 既要求实时入库和大数据查询分析，又要兼顾点查性能的业务。                                    |
   
#### 更多文档
- [实时数仓及HStore表使用最佳实践](https://support.huaweicloud.com/bestpractice-dws/dws_05_0029.html)
- [使用DWS行列共存表](https://support.huaweicloud.com/bestpractice-dws/dws_05_0030.html)
 
