文档

访问列存数据

更新时间:

列存即列式存储,是一种将数据按列进行存储和处理的数据管理方式。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、STRING和BINARY。

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

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

    • 必须指定分区,表分区表达式字段必须来自主键字段,最后一级分区必须为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语法

  • 示例一:

    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');

最佳实践

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

主键数据查询

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

假设表结构如下:

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;