访问列存数据

列存即列式存储,是一种将数据按列进行存储和处理的数据管理方式。Lindorm计算引擎支持将半结构化、结构化数据以列存方式进行存储,相较于行式存储,列式存储的查询响应时间更短,消耗IO更少。本文介绍如何通过计算引擎访问Lindorm列存数据。

背景信息

Lindorm列存是面向海量半结构化、结构化数据设计的列格式分布式存储服务,适用于车联网、物联网、订单、日志等大规模存储场景,核心能力包括:

  • 计算分析

    Lindorm计算引擎可以访问列存数据,完成海量数据的交互式分析和离线计算。列存提供丰富的索引能力和数据分布特征,可以有效加速计算过程中的数据定位与排布,通过SQL即可完成海量主键数据的增删改查。

  • 高吞吐

    列存引擎吞吐能力支持水平扩展,提供每分钟TB级数据的读写能力。适用于车联网数据快速导入、模型训练数据集存取和大规模报表分析生产等高吞吐数据场景。

  • 低成本

    通过列格式高压缩比算法、高密度低成本介质、冷热分离、多压缩编码和数据冷归档等技术,Lindorm列存相比自建系统存储成本显著降低,满足海量数据归档留存等低成本存储需求。

  • 高可用

    通过纠删码等技术,Lindorm列存保证了分布式数据集的高可用性,同时保证了数据访问无单点。

  • 开源兼容

    兼容Iceberg开源标准接口,与Spark、Flink等多种计算引擎互联互通,无缝对接主流数据生态。

  • 冷热分离

    您可以根据业务需求将冷热数据存储在不同的介质上,减少访问冷数据带来的性能消耗,同时有效降低存储成本。

前提条件

功能说明

DDL

命名空间

创建Namespace(Database)

USE lindorm_columnar;
CREATE NAMESPACE mydb;

删除Namespace(Database)

USE lindorm_columnar;
DROP NAMESPACE mydb;

创建表

USE lindorm_columnar;
CREATE TABLE mydb.mytable (
  id INT NOT NULL,
  city STRING NOT NULL,
  name STRING,
  score INT)
PARTITIONED BY (city, bucket(128,id))
TBLPROPERTIES(
  'primary-key' = 'id,city');

以下分别对主键、数据分区方式进行说明。

主键

创建表时,可以通过设置主键创建主键表或不设置主键创建非主键表。以下将分别介绍主键表和非主键表的创建方法,以及创建时需遵循的规则。

  • 创建主建表。创建表时,设置TBLPROPERTIESprimary-key参数的值,指定表的主键字段即可。主键表遵循以下规则:

    • 多个主键字段需用英文逗号分隔。支持字段类型:BOOLEAN、BYTE、SHORT、INT、LONG、FLOAT、DOUBLE、STRINGBINARY。

    • 列存表主键具备唯一性。

    • 当相同主键数据多次写入,新数据将覆盖旧数据。

    • 必须指定分区,表分区表达式字段必须来自主键字段,最后一级分区必须为bucket分区。

  • 创建非主键表。创建表时,不设置TBLPROPERTIES中的primary-key参数的值即可。非主键表无分区要求且数据无唯一性保证,允许存在重复数据。

数据分区方式

您可以在创建表时通过PARTITIONED BY([普通分区表达式],{bucket(bucketNum,bucketCol)})指定数据分区方式。

  • bucket分区表达式

    • bucketNum为分片数量,直接影响数据写入和扫描的并发度。

      说明

      不同的bucket分区有不同的分区号(bucket_index)。bucketNum决定了一个普通分区下的bucket分区数量。

      • bucket分区号的计算方式是基于分区字段求Hash值,然后对bucketNum取余得出。以示例表mydb.mytable中的数据为例,bucket_index=hash(id)%128

      • 对于每个不同的bucket_index,底层存储将进行物理划分。建议您在创建表前评估数据总量,并合理设置bucketNum,保证单bucket分区的数据量在50M~512M之间。

    • bucketCol为具体的bucket分区字段。

      重要

      设置bucket分区字段时为避免数据倾斜,需确保bucket分区字段具有足够的离散特征。

    示例

    创建表时仅指定bucket分区。

    • 示例一:

      USE lindorm_columnar;
      CREATE TABLE mydb.mytable (
        id INT NOT NULL,
        city STRING,
        name STRING,
        score DOUBLE)
      PARTITIONED BY (bucket(1024,id))
      TBLPROPERTIES(
        'primary-key' = 'id');
    • 示例二:

      USE lindorm_columnar;
      CREATE TABLE mydb.mytable (
        id INT NOT NULL,
        timestamp LONG NOT NULL,
        city STRING,
        name STRING,
        score DOUBLE)
      PARTITIONED BY (bucket(512,timestamp))
      TBLPROPERTIES(
        'primary-key' = 'id,timestamp');
  • 普通分区表达式

    对于普通分区表达式每个不同的值,底层存储将进行物理划分,保证数据扫描的裁剪能力。

    重要

    请您确保普通分区表达式的取值相对集中,常见普通分区字段包括:日期、城市、性别等等。如果您的普通分区表达式取值过于离散,例如时间戳,将导致列存元数据压力过大。

    示例

    创建表时同时指定普通分区和bucket分区。

    • 示例一:

      USE lindorm_columnar;
      CREATE TABLE mydb.mytable (
        id INT NOT NULL,
        year STRING NOT NULL,
        month STRING NOT NULL,
        day STRING NOT NULL,
        city STRING,
        name STRING,
        score DOUBLE)
      PARTITIONED BY (year, month, day, bucket(1024,id))
      TBLPROPERTIES(
        'primary-key' = 'id, year, month, day');
    • 示例二:

      USE lindorm_columnar;
      CREATE TABLE mydb.mytable (
        id INT NOT NULL,
        date STRING NOT NULL,
        city STRING NOT NULL,
        name STRING,
        score DOUBLE)
      PARTITIONED BY (date, city, bucket(1024,id))
      TBLPROPERTIES(
        'primary-key' = 'id,date,city');

查看当前Namespace下的表

USE lindorm_columnar;
USE mydb;
SHOW TABLES;

查看已存在的表

您可以执行以下SQL语句查看表结构。

USE lindorm_columnar;
SHOW CREATE TABLE mydb.mytable;
DESC mydb.mytable;

删除指定表

USE lindorm_columnar;
-- 删除表保留数据文件
DROP TABLE mydb.mytable;
-- 删除表删除数据文件
DROP TABLE mydb.mytable PURGE;

清空表中数据

USE lindorm_columnar;
TRUNCATE TABLE mydb.mytable;

分区

删除分区

您可以通过DELETE FROM语法指定WHERE条件匹配分区来删除分区,示例如下。

USE lindorm_columnar;
DELETE FROM mydb.mytable WHERE city = 'beijing';

DML

在表中插入数据

  • 示例一:

    USE lindorm_columnar;
    INSERT INTO mydb.mytable VALUES (0, 'beijing', 'zhang3', 99);
  • 示例二:

    USE lindorm_columnar;
    INSERT INTO mydb.mytable SELECT id, city, name, score FROM another_table;

查询表中的数据

  • 示例一:

    USE lindorm_columnar;
    SELECT * from mydb.mytable where id=0;
  • 示例二:

    USE lindorm_columnar;
    SELECT count(1), sum(score) from mydb.mytable where city = 'beijing';

分区整理

在列存分区写入数据,经过一段时间后,您可以执行rewrite_data_filesrewrite_manifest命令,整理分区数据,可以将零散的小文件合并为大文件,减少数据或元数据冗余,提升数据查询性能。详细说明,请参见rewrite_data_files语法rewrite_manifest语法

说明

执行rewrite_data_filesrewrite_manifest命令时,会占用数据库资源,建议在业务低峰期执行。

  • 示例一:

    USE lindorm_columnar;
    CALL lindorm_columnar.system.rewrite_data_files(table => 'mydb.mytable');
  • 示例二:

    USE lindorm_columnar;
    CALL lindorm_columnar.system.rewrite_data_files(table => 'mydb.mytable', where => 'city=\"beijing\"');
  • 示例三:

    USE lindorm_columnar;
    CALL lindorm_columnar.system.rewrite_manifest('mydb.mytable');

冷热介质

在数据管理时,为优化成本与性能,通常会选择将高频访问的数据存储在高性能介质上,而对于长时间未访问的历史数据,则希望将其迁移到低成本的存储介质中。Lindorm列存支持三级冷热分层解决方案(从热到冷分别为L1、L2L3),可以根据业务需求灵活制定列存数据的冷热转换策略。通过数据湖服务自动完成数据在冷热介质之间的自动转储,帮助您有效管理存储成本而不牺牲关键业务的响应速度。

重要

Lindorm列存数据自动冷热转换功能,需联系Lindorm技术支持(钉钉号:s0s3eg3)开通。

参数配置

您可以通过在创建列存表时定义CHS(Cold or Hot Storage)属性,以列存表的时间分区为粒度,定义数据冷热转储。配置CHS的参数说明如下:

参数

说明

CHS

配置该参数即表示开启冷热分离,可以配置为:

  • 一个长整数(单位为秒)。

    • 如果分区数据时间距离当前时间小于等于该阈值,则存入L1层。

    • 如果分区数据时间距离当前时间大于该阈值,自动转储到L2层。

    说明

    如:'CHS'='259200',表示冷热分离成两层。

    • 分区数据时间距离当前时间小于等于259200秒时,数据存入L1层。

    • 分区数据时间距离当前时间大于259200秒时,数据自动转储到L2层。

  • 两个长整数(单位为秒)。用逗号分隔,形如num0, num1,要求num0<num1

    • 如果分区数据时间距离当前时间小于等于第一个阈值,则存入L1层。

    • 如果分区数据时间距离当前时间大于第一阈值,且小于等于第二阈值,则自动转储到L2层。

    • 如果分区数据时间距离当前时间大于第二阈值,则自动转储到L3层。

    说明

    如:'CHS'='259200, 864000',表示冷热分离成三层。

    • 分区数据时间距离当前时间小于等于259200秒时,数据存入L1层。

    • 分区数据时间距离当前时间大于259200秒且小于等于864000秒时,数据自动转储到L2层。

    • 分区数据时间距离当前时间大于864000秒后,数据自动转储到L3层。

CHS_L1

配置L1层选用的存储介质,参数格式为:'CHS_L1'='storagetype=目标存储类型'

说明

如果在创建表时没有配置CHS_L1指定存储介质,则默认使用容量型云存储。

如果您是云存储用户,目标存储类型可选值如下:

  • CAPACITY_CLOUD_STORAGE:容量型云存储(默认值)。

  • STANDARD_CLOUD_STORAGE:标准型云存储。

  • PERFORMANCE_CLOUD_STORAGE:性能型云存储。

  • CLOUD_ARCHIVE_STORAGE:归档型存储。

    说明

    归档型存储类型目前在内测中,如需使用请联系Lindorm技术支持(钉钉号:s0s3eg3)开通。

如果您是本地盘用户,目标存储类型可选值如下:

  • CAPACITY_CLOUD_STORAGE:容量型云存储(默认值)。

  • LOCAL_SSD_STORAGE:本地SSD存储。

  • LOCAL_HDD_STORAGE:大数据型。

  • LOCAL_EBS_STORAGE:本地ESSD云盘。

CHS_L2

配置L2层选用的存储介质,参数格式与可选值同CHS_L1。

说明

CHS_L2参数必须配置。

CHS_L3

配置L3层选用的存储介质,参数格式与可选值同CHS_L1。

说明

如果您的CHS参数为两个长整数时,CHS_L3参数必须配置。

CHS_EXP

配置分区数据时间的提取方法,您需要将其定义为形如:toSec(${column0},${pattern0},${column1},${pattern1},....${columnN},${patternN})

其中:

  • columnN:表示时间的枚举分区字段,支持的数据类型为:INTEGER、LONG、STRINGDATE。

  • patternN:对应时间分区字段的格式,可选值如下:

    • yyyy:表示年。

    • MM:表示月。

    • dd:表示日。

    • HH:表示小时。

    • mm:表示分钟。

  • toSec系统函数能够计算出对应时间分区范围的最大数据时间。示例如下:

    • 如果时间分区的3个字段为year/month/day,对于year=2023, month=10,day=2这个分区,toSec(year,yyyy)返回数据时间为2023-12-31 23:59:59,toSec(year, yyyy, month, MM)返回数据时间为2023-10-31 23:59:59,toSec(year, yyyy, month,MM,day,'dd')返回数据时间为2023-10-02 23:59:59

    • 如果时间分区字段为date,对于date=2023-10-02这个分区,toSec(date, yyyy-MM-dd)返回数据时间为2023-10-02 23:59:59

Lindorm计算引擎会基于CHS_EXP获取时间分区的最大数据时间,结合CHS中定义的时间阈值范围,将分区数据转储到相应的分层介质。

示例

  • 示例一:

    创建表table0,按年,月,日三个字段分区,字段名称为year,month,day。定义冷热分层策略为:1个月(2592000秒)以前的数据自动转储到容量型云存储。建表语句如下:

    CREATE TABLE table0 (col0 INT,year STRING,month STRING,day STRING)
    PARTITIONED BY  (year,month)
    TBLPROPERTIES 
    'CHS'='2592000',
    'CHS_L2'='storagetype=CAPACITY_CLOUD_STORAGE',
    'CHS_EXP'='toSec(year,yyyy,month,MM,day,dd)'
    );
  • 示例二:

    修改table0冷热分层策略为:1个月(2592000秒)以前的数据自动转储到容量型云存储,超过3个月(5184000秒)后自动转储到归档型存储,其他规则不变。更新表语句如下:

    ALTER TABLE table0
    SET TBLPROPERTIES (
    'CHS'='2592000,5184000',
    'CHS_L2'='storagetype=CAPACITY_CLOUD_STORAGE',
    'CHS_L3'='storagetype=CLOUD_ARCHIVE_STORAGE',
    'CHS_EXP'='toSec(year,yyyy,month,MM,day,dd)'
    );
  • 示例三:

    创建table1,按天分区,字段名为dt,形如:2020/12/1。定义冷热分层策略为:1个月(2592000秒)以前的数据自动转储到容量型云存储,超过3个月(5184000秒)后自动转储到归档型存储。建表语句如下:

    CREATE TABLE table1 (col0 INT,dt STRING)
    PARTITIONED BY  (dt)
    TBLPROPERTIES (
    'CHS'='2592000,5184000',
    'CHS_L2'='storagetype=CAPACITY_CLOUD_STORAGE',
    'CHS_L3'='storagetype=CLOUD_ARCHIVE_STORAGE',
    'CHS_EXP'='toSec(dt,yyyy/MM/dd)'
    );
注意事项
  • 在创建表时,您可以指定CHS参数。如果后续需要更改冷热分层策略,可以通过执行ALTER TABLE ...SET TBLPROPERTIES...语句来调整配置。

  • 错误的CHS配置不会影响表创建与更新,但会导致无法自动触发冷热介质转换功能。

  • 数据的冷热转储过程是通过异步方式触发的。在整个转储过程中及完成后,数据访问均不受影响,但基于不同存储介质,访问性能可能会有所变化。

  • 仅支持基于列存表的时间分区来实现数据冷热分层策略,暂不支持其他方式。

最佳实践

您可以通过以下方案,加速数据查询或计算。

主键数据查询

如果表中存储了海量数据集,查询时可以指定通过主键过滤条件,实现加速效果。查询时,主键的数据范围设置得越小,加速效果越好。

假设表结构如下:

USE lindorm_columnar;
CREATE TABLE orders (
o_orderkey       INT NOT NULL,
o_custkey        INT,
o_orderstatus    STRING,
o_totalprice     DOUBLE,
o_orderdate      STRING,
o_orderpriority  STRING,
o_clerk          STRING,
o_shippriority   INT,
o_comment        STRING)
PARTITIONED BY (bucket(1024,o_orderkey))
TBLPROPERTIES(
'primary-key' = 'o_orderkey');           
  • 示例一:

    USE lindorm_columnar;
    SELECT * FROM orders WHERE o_orderkey=18394;
  • 示例二:

    USE lindorm_columnar;
    SELECT count(*) FROM orders WHERE o_orderkey>100000 AND o_orderkey<200000;
  • 示例三:

    USE lindorm_columnar;
    SELECT count(*) FROM orders WHERE o_orderkey>100000;

添加分区过滤

Lindorm列存引擎中不同分区之间彼此物理隔离,因此,通过添加分区过滤条件,可以加速数据查询。

假设表结构如下:

USE lindorm_columnar;
CREATE TABLE orders (
o_orderkey       INT NOT NULL,
o_custkey        INT,
o_orderstatus    STRING,
o_totalprice     DOUBLE,
o_orderdate      STRING NOT NULL,
o_orderpriority  STRING,
o_clerk          STRING,
o_shippriority   INT,
o_comment        STRING)
PARTITIONED BY (o_orderdate, bucket(1024,o_orderkey))
TBLPROPERTIES(
'primary-key' = 'o_orderdate,o_orderkey');
  • 示例一:

    USE lindorm_columnar;
    SELECT o_orderdate, count(*) FROM orders WHERE o_orderdate='2022-01-01' GROUP BY o_orderdate;
  • 示例二:

    USE lindorm_columnar;
    SELECT o_orderdate, count(*) FROM orders WHERE o_orderdate>='2022-01-01' AND o_orderdate<='2022-01-07' GROUP BY o_orderdate;

查询加速

对指定表或者表中的指定分区进行数据整理(Rewrite),可以增强数据的有序性或紧凑性,从而提升数据扫描性能。

假设表结构如下:

CREATE TABLE mydb.mytable (
  id INT NOT NULL, 
  city STRING NOT NULL, 
  name STRING, 
  score INT)
partitioned by (city, bucket(4, id))
tblproperties('primary-key' = 'id,city');
  • 示例一:对mydb.mytable全表进行数据整理。

    CALL lindorm_columnar.system.rewrite_data_files(table => 'mydb.mytable');
  • 示例二:对指定分区进行数据整理。

    CALL lindorm_columnar.system.rewrite_data_files(table => 'mydb.mytable', where => 'city=\"beijing\"');

完成数据整理后,如果想要进一步提升后续查询的效率,可以执行以下语句设置表的相关参数来加速后续查询:

ALTER TABLE mydb.mytable SET TBLPROPERTIES ('read.scan-major-rewritten-files-only' = true);

参数说明

read.scan-major-rewritten-files-only:指定数据查询范围。数据类型为BOOLEAN。取值如下:

  • true:只查询已完成数据整理的数据,忽略增量写入且未完成数据整理的数据。

  • false:默认值。查询所有数据。

非主键条件查询

针对分区整理过程,列存表默认按主键排序,可以在建表后按需配置排序键,从而加速非主键条件查询。

重要

自定义排序键加速查询效果需要在配置排序键后进行分区整理,且只扫描已进行分区整理的数据,不再扫描增量数据。

假设表结构如下:

USE lindorm_columnar;
CREATE TABLE orders (
o_orderkey       INT NOT NULL,
o_custkey        INT,
o_orderstatus    STRING,
o_totalprice     DOUBLE ,
o_orderdate      STRING ,
o_orderpriority  STRING,
o_clerk          STRING,
o_shippriority   INT,
o_comment        STRING)
PARTITIONED BY (bucket(1024,o_orderkey))
TBLPROPERTIES(
'primary-key' = 'o_orderkey',
'read.scan-major-rewritten-files-only' = 'true');

执行以下语句配置排序键:

ALTER TABLE orders WRITE ORDERED BY o_shippriority,o_totalprice;

执行以下语句整理分区:

CALL lindorm_columnar.system.rewrite_data_files(table => 'orders');

您可以使用以下SQL语句查询已完成分区整理的表中的数据。

  • 示例一:

    USE lindorm_columnar;
    SELECT count(*) FROM orders WHERE o_shippriority=0;
  • 示例二:

    USE lindorm_columnar;
    SELECT count(*) FROM orders WHERE o_shippriority=0 AND o_totalprice>999.9;