#

在关系型数据库中一个表就是一个关系(Relation),一个关系数据库可以包含多个表。表是一种按行、列排列的具有相关信息的逻辑结构,是关系数据库中组织数据的基本单元。

# 表的元素

#

数据库中表包含若干个列(或字段),列表示关系模型的属性,每个列描述了它所包含的数据的意义。例如一个字节的数据0x61,当它是一个一字节整型列的数据时表示数值97,当它是一个字符型列的数据时表示字符a

每个列的定义通常包含列名、数据类型、列最大长度等。对于变长的数据类型,通常需要显式声明列的最大长度,定长数据类型,通常不需要显式指定最大长度。

创建表时声明的列,通常具备以下特征:

  • 数据库会存储该列的数据,占用一定的存储空间。
  • 查询表的数据时,会显示该列的数据。

但是YashanDB中还包存在以下三种特殊列不满足上述特征:

  • 虚拟列:其数据不占用存储空间,只在需要访问该列的数据时,通过该列的default表达式计算出该列的值。

    例如,通过以下语句为表创建函数索引:

    CREATE TABLE employee(id INT, name VARCHAR(32));
    
    CREATE INDEX idx_emp_name ON employee(UPPER(name));
    

    索引idx_emp_name并非基于employee表的列而创建,而是基于upper(name),在此情况下,数据库会为employee表添加一个虚拟列,该列的default值为upper(name)。

  • 隐藏列:其数据会占用存储空间,但用户无法直接查询隐藏列的数据。

    例如,用户创建的表中包含一个自定义的对象类型,对象类型可能包含若干个属性,每个属性会在表中以至少一个隐藏列存储,用户无法直接查询这些隐藏列,只能通过查询对象列,再通过访问对象类型的属性。

    CREATE OR replace TYPE person AS object (id CHAR(18), name VARCHAR(32), birthday DATE);
    /
    CREATE TABLE employee(emp_id INT, personal_info person);
    INSERT INTO employee VALUES(1, person('11011120000101011X', 'Zhang San', '2000-01-01'));
    
    -- 查询表的所有列(不含隐藏列)
    SELECT column_id, column_name, data_type FROM USER_TAB_COLUMNS WHERE table_name = 'EMPLOYEE' ORDER BY 1;
    
       COLUMN_ID COLUMN_NAME                                                      DATA_TYPE                                                        
    ------------ ---------------------------------------------------------------- ---------------------------------------------------------------- 
             0 EMP_ID                                                           INTEGER                                                         
             1 PERSONAL_INFO                                                    PERSON                                                      PERSON                                                        
    -- 查询表的所有列(含隐藏列)
    SELECT column_id, column_name, data_type FROM USER_TAB_COLS WHERE table_name = 'EMPLOYEE' ORDER BY 1;
    
       COLUMN_ID COLUMN_NAME                                                      DATA_TYPE                                                        
    ------------ ---------------------------------------------------------------- ---------------------------------------------------------------- 
             0 EMP_ID                                                           INTEGER                                                         
             1 PERSONAL_INFO                                                    PERSON                                                          
             2 SYS_NC00002$                                                     CHAR                                                            
             3 SYS_NC00003$                                                     VARCHAR                                                         
             4 SYS_NC00004$                                                     DATE   
    
    SELECT e.personal_info.name FROM employee e;
    
    PERSONAL_INFO.NAME                
    --------------------------------- 
    Zhang San                        
    
    

    在上述示例中,employee表包含一个personal_info列,该列为用户自定义的person数据类型,该类型包含id、name、birthday三个属性,这三个属性不会显示在USER_TAB_COLUMNS视图中,因此无法直接访问。

  • 已删除的列:无法被访问。

    当用户通过alter table语句删除掉表中某一列后,该列的定义并不会从系统表中删除,而是仅将该列标记为删除,其数据也不会从存储空间中删除。

#

表中的一行,即为关系模型中的一个元组(或记录)。它由一组表的列数据组成。表中的一行代表该表中存储的某一个特定实体(或对象)的所有属性信息。

# 数据类型

表中的每一列都必须指定一个数据类型及规格,当用户向表中插入数据时,待插入的数据将被强制按照列的数据类型和规格做转换。其中,规格包括列的最大长度、精度等信息(非必须项)。

YashanDB内置了常用的基础类型,包括字符型、数值型以及日期时间型。

# 字符型

VARCHAR类型

VARCHAR类型用于存储变长的字符串。

用户如果将表中的某一列数据类型指定为VARCHAR类型,需要同时指定该列的最大长度(字符或字节)。语法示例如下:

-- 声明最大长度为10字节的VARCHAR类型
VARCHAR(10)
-- 声明最大长度为10字节的VARCHAR类型
VARCHAR(10 byte)
-- 声明最大长度为10个字符的VARCHAR类型
VARCHAR(10 char)

当用户向表中插入的字符串时,SQL语句中字符串需要用单引号包裹,且根据字符串长度不同而有不同表现:

  • 字符串长度超过该列的最大长度时,会返回错误。

  • 字符串长度小于该列的最大长度时,按实际输入的字符串进行存储。

CHAR类型

CHAR类型用于存储定长的字符串,用户可以指定最大长度(字符或字节)。

当用户向表中插入的字符串时,SQL语句中字符串需要用单引号包裹,且根据字符串长度不同而有不同表现:

  • 字符串长度超过该列的最大长度时,会返回错误。

  • 字符串长度小于该列的最大长度时,将在字符串末尾填充若干空格,直到字符串长度等于最大长度。

由于CHAR类型存在空格补位机制(实际存储的数据会填充至最大长度),因此在数据更新时,长度始终保持一致,其性能在某些场景下会优于VARCHAR类型,但其消耗的存储空间会相对比VARCHAR类型更大。

两个CHAR类型的值在比较大小时,将忽略末尾补位的空格。

CHAR类型与VARCHAR类型比较大小时,CHAR类型末尾补位的空格仍会参与比较。

NCHAR与NVARCHAR类型

NCHAR用于指定支持UNICODE的固定长度字符串,NVARCHAR用于指定支持UNICODE的可变长度字符串,这两种字符型仅支持在配置UNICODE字符集的数据库中使用,可以对多语言数据进行存储。

当使用这两种类型时,类型的长度仅指字符长度。

# 数值型

NUMBER类型

YashanDB的NUMBER类型可以存储定点数和浮点数。使用NUMBER类型时,可以指定精度和范围,格式如下:

NUMBER(precision, scale)

precision和scale的含义如下:

  • precision表示数字的最大位数。用户输入的数值将按照precision做四舍五入,以确保结果的数字位数不超过precision。

  • scale表示小数点到最后一位有效数字之间的数字位数,如果scale为正数,则scale表示小数点后的数字位数;如果scale为负数,则表示整数部分最后一位有效数字后有多少个0,此时无小数部分。例如,123按NUMBER(5,2)存储为12300E-2,按NUMBER(5,-2)存储为1E2。

NUMBER类型存储格式的编码与硬件和操作系统无关,为YashanDB内部编码格式,可跨平台迁移。

NUMBER类型的有效数字最大可支持38位。

原生数值类型

原生数值类型指该类型的编码方式与CPU支持的数值类型一致,包括原生的整数类型和浮点数类型。

  • 原生整数类型:YashanDB支持四种整数类型,其存储格式均采用CPU架构支持的原生格式,因此运算性能较好,但存储格式与CPU的字节序相关,无法保持跨平台兼容。
类型名称 长度/字节 最小值 最大值
BINARY_TINYINT 1 -128 127
BINARY_SMALLINT 2 -32768 32767
BINARY_INTEGER 4 -2147483648 2147483647
BINARY_BIGINT 8 -9223372036854775808 9223372036854775807
  • 原生浮点数类型:YashanDB支持以下两种浮点数,BINARY_FLOAT与BINARY_DOUBLE分别对应ANSI/IEEE Std 754-1985标准定义的单精度浮点数和双精度浮点数。
类型名称 长度/字节 最小值 最大值 有效数字
BINARY_FLOAT 4 -3.40E+38 +3.40E+38 7
BINARY_DOUBLE 8 -1.79E+308 +1.79E+308 16

原生类型与NUMBER类型的差异

  • 相对于原生整数类型而言,NUMBER支持的数值范围和有效数字位数更大。

  • NUMBER类型采用变长方式存储有效数字,在数值远小于原生整数类型的边界时,通常NUMBER占用的存储空间更小。

  • 相对于原生浮点数类型而言,NUMBER类型最大支持38位有效数字,比原生浮点数类型结果更精确。

  • 原生类型的四则运算可以直接调用CPU指令,因此性能更好。

# 日期时间型

日期时间类型

表示某个日期或时刻,具体包括日期(DATE)、时间(TIME)、时间戳(TIMESTAMP)三个数据类型。这三种类型在数据库中存储的是从某个特定时间开始的时间间隔数值,因此,日期、时间类型可以进行加、减运算。

用户也可以将日期、时间以某种格式转化成字符串显示。

时间间隔类型

表示两个日期或时刻之间的间隔长度,具体包括年到月间隔(INTERVAL YEAR TO MONTH)、天到秒(INTERVAL DAY TO SECOND)两个数据类型。

# 大对象型

某些数据的长度上限可能很大(超过一个数据块的大小),在数据库中会占用大量的存储空间。如果YashanDB将这种数据像其他字符或二进制数据类型一样存储,则每行数据可能占用大量的数据块,即使用户不查询该列数据,数据库在扫描该表时,仍然会访问大量的数据块,影响用户请求的响应速度。因此,YashanDB提供了一组大对象数据(LOB,Large Object)类型,包括CLOB(Character Large Object)、BLOB(Binary Large Object)和NCLOB(National Character Large Object)三种类型。

YashanDB只会将长度较小的LOB数据与行内其他列数据一起存储,长度较大的大对象类型数据只在行内存储一个LOB数据的位置信息,而实际数据则单独存储在其他数据块中,因此LOB数据在行内只占用极少的空间。

当用户扫描包含LOB类型的表时,并不会扫描LOB数据所在的数据块;当用户需要访问LOB数据时,需要从行内取到LOB数据的位置信息,然后再定位到LOB数据所在的数据块。因此,当一个表中包含大量较长的LOB数据时,如果用户的SQL语句中不涉及LOB列的访问,那么该SQL语句执行的性能不会因为LOB数据的存在而受到明显地影响,但是当用户需要访问LOB列的数据时,数据读取的性能会比其他数据类型数据的读取差。

# 其他数据类型

ROWID

当表中的数据按行组织时,系统根据每一行数据所在物理地址信息生成一个全局唯一记录,即ROWID。ROWID用于检索表数据时的寻址,也可以用作每一行数据的唯一标识。

RAW

变长的二进制类型。

BIT

BIT类型支持1-64位宽度(Size)的二进制位图,每BIT位只允许存放0/1值。

布尔型

布尔型数据的值只有1(TRUE)和0(FALSE),其字节长度为1,可用于指示某个二元特性的状态。

JSON

YashanDB中的JSON类型是一种可变长度数据类型,通过解析符合标准JSON格式的字符串获得的二进制数据。

# 表的组织方式

表的数据相当于一个二维矩阵,存储表的数据时,可以按行存储,即将一行的所有列数据集中存储,存完一行,再存储下一行;也可以按列存储,即先存一列的所有行数据,存完一列之后,再存下一列。

# 行存表

建表时将表的组织方式指定为HEAP,则表的数据将按行的方式组织。表的每一行数据将按表定义中列的顺序存储。行存表适用于数据按行获取的场景,因此通常用于事务型业务场景。

例如employees表数据如下表所示:

id name department
1 Zhang San marketing
2 Li Si sales
3 Wang Wu support

则数据的组织方式为:

row1: 1, Zhang San, marketing; row2: 2, Li Si, sales; row3: 3, Wang wu, support

# 列存表

列存表的特点是每列的数据集中存储,数据按列查询速度远大于按行读取,适用于分析型的场景。分析型场景中,有大量数据是不需要更新的,业务只会对数据做查询,这部分数据称为稳态数据,通常可以对这些数据做进一步处理(压缩、排序等),以提高查询性能。少数需要更新的数据,称为实时数据。

# LSC表

建表时,可将表的组织方式指定为LSC。LSC表适用于海量稳态数据分析场景,每个列的数据集中存储。LSC表将数据分为实时数据区和稳态数据区。数据写入数据库时,先写入实时数据区,实时数据区的数据使用segment存储,以适应业务写入的随机性。当数据逐步稳定后,则将数据转入稳态数据区,稳态数据采用大颗粒度、高压缩比的对象存储,通过数据排序、稀疏索引等方式,实现查询的加速。

# TAC表

建表时,可将表的组织方式指定为TAC。TAC是一种面向数据实时分析的列存表,TAC表的数据存储在实时数据区,每个列的数据集中存储。由于数据在实时数据区,因此数据的更新性能优于LSC表。

# 特殊表类型

特殊表类型是指除普通表以外的临时表和外部表。

# 临时表

临时表用于存储仅在事务或会话期间存在的会话私有数据,不同会话间该类数据不共享。

# 临时表的分类

临时表按照表的定义是否在不同会话间共享分为全局临时表(Global temporary table)和私有临时表(Private temporary table)。

全局临时表按照事务结束时是否保留数据分为事务级全局临时表(on commit delete rows)和会话级全局临时表(on commit preserve rows)。

私有临时表按照事务结束时是否保留表定义及数据分为事务级私有临时表(on commit drop definition)和会话级私有临时表(on commit preserve definition)。

下表展示不同类型的临时表的行为差异:

特征 全局临时表 私有临时表
表的定义是否全局可 是,所有会话均可见 否,仅创建临时表的会话可见
表的定义是否持久化 是,数据库重启后临时表仍然存在 否,数据库重启后,临时表定义不存在
事务提交对临时表的影响 指定On commit delete rows时,事务提交后,表定义保留但数据清空
指定On commit preserved row时,事务提交后,表定义及数据均保留
指定On commit drop definition时,事务提交后,表定义及数据均清除
指定On commit preserve definition时,事务提交后,表定义及数据均保留
会话断连对临时表的影响 表定义保留,数据清空 表定义及数据均清除
表的命名规则 与普通表的表名规则一致 YAS$PTT_ORA$PTT_开头

# 临时表数据的存储

当一个会话向临时表中插入数据时,数据库系统会在临时表空间为该临时表创建segment,并分配数据块,该会话再次查询该临时表时,数据库将从segment上获取数据,但是该segment对于其他会话是不可见的。事务级全局/私有临时表在事务结束时会将segment所占有空间释放,会话级全局/私有临时表在会话结束时会将segment所占的空间释放。

全局临时表的定义是持久化到系统表中的,除非用户执行删除表的操作将该表定义删除,否则,不会因事务结束、会话结束、进程重启等导致全局临时表的定义丢失。私有临时表的定义是存储在内存中的,事务级私有临时表会在事务结束时从内存中清除该表的定义,会话级私有临时表则会在会话结束时清除该表的定义。

# 外部表

外部表指数据存储在数据库外部的数据表,其存储空间不受数据库进程管理,数据库仅存储表的定义等元数据信息,以便数据库可以像访问普通表一样访问外部表。

# 外部表的意义

外部表在导入数据库时,只需向数据库写入外部表的元数据,实际数据不会占用数据库的存储空间,因此在数据仓库等场景下,可大大减少数据入库的时间和存储空间消耗。

外部表的数据实际存储的位置可以灵活多样,例如文件系统、云存储、分布式文件系统等。

# 外部表数据访问

数据库访问外部表数据是通过数据库对应的外部表驱动实现的,外部表数据访问驱动可以解析外部表文件中的数据,从而实现像访问普通表一样访问外部表文件中的数据。

YashanDB目前仅支持CSV格式的外部表数据访问驱动。

# 外部表的创建

创建外部表的语法如下:

CREATE TABLE ...
ORGANIZATION (HEAP | TAC | LSC | EXTERNAL [external_table_clause])

external_table_clause=
([TYPE access_driver_type]                       -- 指定外部表驱动
 [external_table_data_props])                    -- 指定外部表数据属性
 [REJECT LIMIT (integer | UNLIMITED)]            -- 指定外部表数据错误容忍度

external_table_data_props=
[DEFAULT DIRECTORY directory]                    -- 指定外部表默认目录
[ACCESS PARAMETERS ( opaque_format_spec )]       -- 指定外部表访问参数
[LOCATION [directory :] ('location_specifier')]  -- 指定外部表文件位置

创建表时,通过指定ORGANIZATION EXTERNAL来声明该表是外部表。同时需要指定access_driver_type来指定外部表的数据访问驱动,以及指定LOCATION来声明外部表文件的位置。

数据库在创建外部表时,仅将必要的元数据写入系统表,并不会存储外部表文件中的数据、数据组织方式等。另外,数据库会存储访问外部表文件的必要信息(例如数据访问驱动的类型、外部表对应的外部表文件位置等),以及将外部表文件中的数据以表的方式获取的方法(例如CSV文件中的列与外部表的列之间的映射关系)。