当前位置: 首页 > news >正文

Apache Doris 基础 -- 数据表设计(表索引)

1、索引概述

索引用于帮助快速过滤或搜索数据。目前,Doris支持两种类型的索引:内置智能索引用户创建的二级索引

内置智能索引

  • 排序键和前缀索引:Apache Doris基于排序键以有序的方式存储数据。它为每1024行数据创建一个前缀索引。索引中的键是当前1024行组的第一行中已排序列的值。如果查询涉及排序的列,系统将找到相关的1024行组的第一行,并从那里开始扫描。
  • ZoneMap索引:ZoneMap索引以基于列的存储格式自动维护每列的索引信息,包括Min/Max值等。这种类型的索引对用户是透明的。它们是段级索引和页级索引,记录页内每列的最大值和最小值,还记录段内每列的最大值和最小值。因此,在等价查询和范围查询中,可以使用Min/Max索引来缩小过滤范围。

用户创建二级索引

  • 布隆过滤器索引
  • N-Gram Bloom Filter Index
  • 位图索引
  • 倒排索引

2、排序键和前缀索引

Doris将数据存储在类似于SSTable (Sorted String Table)的数据结构中,这是一种有序的数据结构,可以按照指定的列对数据进行排序和存储。在这种数据结构中,基于排序的列执行查询是非常高效的。

AggregateUniqueDuplicate这三种数据模型中,底层数据存储根据各自表创建语句的AGGREGATE KEY, UNIQUE KEY, 和 DUPLICATE KEY中指定的列进行排序和存储。这些键被称为排序键。在Sort Keys的帮助下,Doris可以在查询时通过在排序的列上指定条件来快速找到要处理的数据,从而降低了搜索的复杂性,从而加快了查询的速度,而无需扫描整个表。

在排序键的基础上,引入了前缀索引。前缀索引是一种稀疏索引。在表中,根据相应的行数形成一个逻辑数据块。每个逻辑数据块在前缀索引表中存储一个索引项。索引表项的长度不超过36字节,其内容是由数据块中第一行数据的排序列组成的前缀。在搜索Prefix Index表时,它可以帮助确定行数据所在的逻辑数据块的起始行号。由于Prefix Index相对较小,因此可以将其完全缓存在内存中,从而实现快速的数据块本地化,并显著提高查询效率

数据块中一行数据的前36个字节作为该行的前缀索引。当遇到VARCHAR类型时,前缀索引将被直接截断。如果第一列是VARCHAR类型,即使长度没有达到36字节,也会发生截断。

2.1 例子

  • 如果表的排序键如下:5列,则前缀索引为:user_id(8字节),age(4字节),message(前缀20字节)。
    在这里插入图片描述
  • 如果表的排序键由5列组成,并且第一列是VARCHAR类型的user_name,那么前缀索引将是user_name(截断为20字节)。即使前缀索引的总大小没有达到36字节,也会发生截断,因为它遇到一个VARCHAR列,并且不包括其他列
    在这里插入图片描述
  • 当我们的查询条件匹配前缀索引时,可以大大加快查询速度。例如,在第一种情况下,执行以下查询:
SELECT * FROM table WHERE user_id=1829239 and age=20;

该查询的效率将远远高于以下查询:

SELECT * FROM table WHERE age=20;

因此,在创建表时,选择正确的列顺序可以大大提高查询效率。

2.2 多个前缀索引

由于在表创建过程中指定了列顺序,因此表通常只有一种类型的前缀索引。这可能不满足使用其他列作为条件的查询的效率要求,因为这些列没有命中前缀索引。在这种情况下,可以通过创建具有调整列顺序的单个表的对应的强一致性物化视图来间接实现多个前缀索引。有关详细信息,请参阅物化视图。

3、BloomFilter 索引

BloomFilter是Bloom于1970年提出的多哈希函数映射的快速搜索算法。通常用于一些需要快速确定某个元素是否属于一个集合,但不严格要求100%正确的场合,BloomFilter具有以下特点:

  • 一种高空间效率的概率数据结构,用于检查元素是否在集合中。
  • 对于检测元素是否存在的调用,BloomFilter将告诉调用者两种结果中的一种:它可能存在或者它一定不存在
  • 缺点是有一个误判,告诉你它可能存在,不一定是真的。

布隆过滤器实际上是由一个极长的二进制位数组和一系列哈希函数组成的。二进制位数组一开始都是0。当给定要查询的元素时,将通过一系列哈希函数计算该元素以映射出一系列值,并且所有值在位数组的偏移量中都被视为1。

下图显示了m=18, k=3的Bloom Filter示例(m是Bit数组的大小,k是Hash函数的数量)。集合中的x、y和z三个元素通过三个不同的散列函数散列到位数组中。查询元素w时,经过哈希函数计算,因为有一位是0,所以w不在集合中。

类似地,如果一个元素经过哈希函数计算以获得其所有偏移位置,并且如果所有这些位置都设置为1,则确定该元素可能在集合中。

在这里插入图片描述
布隆过滤器是一种数据结构,允许对存储在每个数据块中的数据进行反向测试。当请求特定的行时,Bloom Filter首先检查该行是否不在数据块中。布隆过滤器要么自信地声明该行不存在,要么给出一个不确定的答案。这种不确定性就是为什么它被称为反向测试。然而,布隆过滤器并非没有成本。存储这个额外的索引级别会消耗额外的空间,并且Bloom Filters会随着索引数据的增长而增长。

在Doris中,可以在表创建期间或通过ALTER表操作指定BloomFilter索引。从本质上讲,布隆过滤器是一个位图结构,用于快速确定给定值是否在集合中。这种测定可能导致小概率的假阳性。也就是说,如果返回false,则该值肯定不在集合中。但如果它返回true,则该值可能在集合中。

Doris中的BloomFilter索引是在块级别创建的。在每个Block中,指定列的值被视为一个集合,以生成BloomFilter索引条目。在查询过程中使用此条目来快速过滤掉不符合条件的数据。

现在让我们通过一个例子来看看Doris是如何创建BloomFilter索引的。

3.1 创建BloomFilter索引

Doris BloomFilter索引是通过在表构建语句的PROPERTIES中添加"bloom_filter_columns"="k1,k2,k3"来创建的,这个属性k1,k2,k3是您想要创建的BloomFilter索引的关键列名称,例如,我们为表中的saler_id和category_id创建BloomFilter索引。

CREATE TABLE IF NOT EXISTS sale_detail_bloom (sale_date date NOT NULL COMMENT "Sales time",customer_id int NOT NULL COMMENT "Customer ID",saler_id int NOT NULL COMMENT "Salesperson",sku_id int NOT NULL COMMENT "Product ID",category_id int NOT NULL COMMENT "Product Category",sale_count int NOT NULL COMMENT "Sales Quantity",sale_price DECIMAL(12,2) NOT NULL COMMENT "unit price",sale_amt DECIMAL(20,2) COMMENT "Total sales amount"
)
Duplicate KEY(sale_date, customer_id,saler_id,sku_id,category_id)
PARTITION BY RANGE(sale_date)
(
PARTITION P_202111 VALUES [('2021-11-01'), ('2021-12-01'))
)
DISTRIBUTED BY HASH(saler_id) BUCKETS 10
PROPERTIES (
"replication_num" = "3",
"bloom_filter_columns"="saler_id,category_id",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "MONTH",
"dynamic_partition.time_zone" = "Asia/Shanghai",
"dynamic_partition.start" = "-2147483648",
"dynamic_partition.end" = "2",
"dynamic_partition.prefix" = "P_",
"dynamic_partition.replication_num" = "3",
"dynamic_partition.buckets" = "3"
);

3.2 查看BloomFilter索引

检查我们在表上建立的BloomFilter索引是否要使用:

SHOW CREATE TABLE <table_name>;

3.3 删除BloomFilter索引

删除索引就是从bloom_filter_columns属性中删除索引列:

ALTER TABLE <db.table_name> SET ("bloom_filter_columns" = "");

3.4 修改BloomFilter索引

修改索引就是修改表的bloom_filter_columns属性:

ALTER TABLE <db.table_name> SET ("bloom_filter_columns" = "k1,k3");

3.5 BloomFilter使用场景

当满足以下条件时,用户可以为列创建BloomFilter索引:

  • BloomFilter适用于非前缀过滤。
  • 查询将根据列的高频率进行过滤,大多数查询条件都在in=中过滤。
  • BloomFilter适用于高基数列。例如UserID。因为如果它是在低基数列(比如gender列)上创建的,那么每个Block将几乎包含所有值,从而导致BloomFilter索引失去其意义。

3.6 BloomFilter使用注意事项

  • 它不支持为TinyintFloatDouble列创建BloomFilter索引。
  • BloomFilter索引只对in=过滤查询有加速作用。
  • 如果您想检查查询是否命中BloomFilter索引,您可以检查查询的配置文件信息。

4、N-Gram BloomFilter索引

为了提高like 查询的性能,实现了N-Gram BloomFilter索引。

从Doris 2.0版本开始支持N-Gram BloomFilter索引。

4.1 创建N-Gram BloomFilter索引

在创建表期间:

CREATE TABLE `table3` (`siteid` int(11) NULL DEFAULT "10" COMMENT "",`citycode` smallint(6) NULL COMMENT "",`username` varchar(100) NULL DEFAULT "" COMMENT "",INDEX idx_ngrambf (`username`) USING NGRAM_BF PROPERTIES("gram_size"="3", "bf_size"="256") COMMENT 'username ngram_bf index'
) ENGINE=OLAP
AGGREGATE KEY(`siteid`, `citycode`, `username`) COMMENT "OLAP"
DISTRIBUTED BY HASH(`siteid`) BUCKETS 10
PROPERTIES (
"replication_num" = "1"
);
  • PROPERTIES(“gram_size”=“3”, “bf_size”=“1024”),分别表示布隆过滤器的gram 数和字节数。
  • gram 数与实际查询场景有关,通常设置为大多数查询字符串的长度。Bloom Filter的字节大小可以通过测试确定,通常大小越大过滤效果越好。您可以从256字节开始进行验证测试,以查看结果。但是,较大的字节大小也会增加索引的存储和内存成本。
  • 如果数据基数相对较高,则不需要将字节大小设置得太大。如果基数不是很高,可以通过增加字节大小来改善过滤效果

4.2 显示N-Gram BloomFilter索引

show index from example_db.table3;

4.3 删除N-Gram BloomFilter索引

alter table example_db.table3 drop index idx_ngrambf;

4.4 修改N-Gram BloomFilter索引

为旧列添加N-Gram BloomFilter索引:

alter table example_db.table3 add index idx_ngrambf(username) using NGRAM_BF PROPERTIES("gram_size"="3", "bf_size"="512")comment 'username ngram_bf index' 

4.5 关于Doris NGram BloomFilter的一些注意

  • NGram BloomFilter只支持CHAR/VARCHAR/String
  • NGram BloomFilter索引和BloomFilter索引在同一列上应该是互斥的
  • BloomFilter的 gram 数和字节可以调整和优化。比如如果gram太小,你可以增加BloomFilter的字节数。
  • 要查找是否使用NGram BloomFilter索引的查询,可以检查查询配置文件。

5、位图索引

位图索引是由位图表示的索引,其中为列中的每个键值创建位图。与其他索引相比,它占用很少的存储空间,并且创建和使用非常快然而,它有一个缺点,即修改操作的锁粒度太大,不适合频繁更新

在这里插入图片描述

5.1 适用的场景

  • 重复值高的列,建议在100 ~ 100,000之间,如occupation、city等。如果重复次数过高,则与其他类型的指标相比没有明显的优势;如果重复过低,空间效率和性能将大大降低。
  • 特定类型的查询,如count, or, and只需要位操作的逻辑操作。例如:多个条件组合查询,select count(*) from table where city = 'Nanjing' and job = 'Doctor' and phonetype = 'iphone' and gender = 'Male'.如果在每个查询条件列上建立位图索引,则数据库可以执行高效的位操作,准确定位所需的数据,减少磁盘IO,并且过滤的结果集越小,位图索引的优势越明显。
  • 适用于特别查询、多维分析和其他分析场景。如果一个表有100列,用户使用其中的20列作为查询条件(任意使用这20列中的几列),那么在这些列上创建20个位图索引将允许所有查询使用这些索引。

不适用的场景

  • 低重复值的列,如身份证、电话号码等
  • 重复次数过高的列,如性别。虽然可以建立位图索引,但不建议将它们单独用作查询条件。建议与其他条件一起过滤。
  • 经常需要更新的列。

创建位图索引

在名为table_name的表的siteid列上创建位图索引index_name。

CREATE INDEX [IF NOT EXISTS] index_name ON table1 (siteid) USING BITMAP;

5.2 查看位图索引

显示指定table_name下的索引:

SHOW INDEX FROM table_name;

删除索引

删除指定table_name下名为index_name的索引。

DROP INDEX [IF EXISTS] index_name ON table_name;

说明

  • 位图索引只能在单列上创建。
  • 位图索引可以应用于Duplicate和Uniq数据模型中的所有列,以及Aggregate模型中的关键列。
  • 位图索引支持的数据类型如下:
    • TINYINT
    • SMALLINT
    • INT
    • BIGINT
    • CHAR
    • VARCHAR
    • DATE
    • DATETIME
    • LARGEINT
    • DECIMAL
    • BOOL

6、倒排索引

从2.0版本开始,Doris实现了倒排索引,以支持文本字段上的全文搜索,文本,数字,日期时间字段上的正常eq和范围过滤器。本文档介绍了反向索引的用法,包括创建、删除和查询。

6.1 术语

倒排索引是信息检索中常用的一种索引技术。它将文本拆分为词项,并构造一个词项以建立文档索引。这种索引称为倒排索引,可用于查找出现特定术语的文档。

6.2 基本原则

在Doris的倒排索引中,表中的一行对应CLucene中的一个doc,列对应doc中的一个字段。因此,通过使用倒排索引,doris可以获得满足SQL WHERE子句过滤条件的行,然后无需读取其他不相关的行即可快速获得这些行。

Doris使用单独的文件来存储倒排索引。它在逻辑上与段文件相关,但彼此隔离。优点是,创建和删除倒排索引不需要重写tablet 和段文件,这是非常繁重的工作。

6.3 特点

倒排索引的特点如下:

  • 添加全文搜索文本(string, varchar, char)字段
    • MATCH_ALL匹配所有关键字,MATCH_ANY匹配任何关键字
    • 支持短语查询MATCH_PHRASE
    • 支持短语+前缀MATCH_PHRASE_PREFIX
    • 支持正则表达式查询MATCH_REGEXP
    • 支持文本字段数组上的全文
    • 支持英文,中文和混合unicode单词解析器
  • 加速普通等值、范围查询,可代替 Bitmap 索引
    • 在文本、数字和日期时间类型上支持=, !=, >, >=, <, <=
    • 支持=, !=, >, >=, <, <=对文本,数字,日期时间类型的数组
  • 完全支持逻辑组合
    • 新增索引对 OR NOT 逻辑的下推
    • 支持AND、OR、NOT的组合
  • 灵活和快速的索引管理
    • 支持在创建表时定义倒排索引
    • 支持在已存在的表上添加倒排索引,无需重写数据
    • 支持在已存在的表上删除倒排索引,无需重写数据

6.4 语法

表创建时的倒排索引定义语法如下

  • USING INVERTED是必选的,它指定索引类型为倒排索引
  • PROPERTIES是可选的,它允许用户为索引指定额外的属性。目前,有三种类型的属性可用。
    • parser 用于设置tokenizer/parser的类型

      • 缺失表示没有解析器,整个字段被认为是一个术语
      • english 代表英语解析器
      • chinese 代表中文解析器
      • unicode代表多语言混合分词,适用于中英文混合的情况。可以对邮件的前缀后缀、IP地址、混合字符和数字进行分段,也可以对中文字符进行逐字分段。
    • parser_mode 用于设置中文分词的分词器/解析器(tokenizer/parser)类型

      • fine_grained(“细粒度”)模式下,系统倾向于生成简短的单词,例如:比如 ‘武汉市长江大桥’ 会分成 ‘武汉’, ‘武汉市’, ‘市长’, ‘长江’, ‘长江大桥’, ‘大桥’ 6 个词
      • coarse_grained( “粗粒度”)模式下,系统倾向于生成长单词,比如 ‘武汉市长江大桥’ 会分成 ‘武汉市’ ‘长江大桥’ 2 个词
      • 默认模式为coarse_grained(“粗粒度”)。
    • support_phrase用于指定索引是否需要支持短语模式查询MATCH_PHRASE

      • true表示需要支持,但需要更多存储空间用于索引。
      • false表示不需要支持,索引存储空间较小。MATCH_ALL可用于无顺序匹配多个单词。
      • 默认模式为false
    • char_filter:主要功能是在分词前对字符串进行预处理

      • char_filter_type:指定不同功能的char_filters(目前只支持char_replace)
        • char_replace:用替换中的字符替换模式中的每个字符
          • char_filter_pattern:要替换的字符数组,
          • char_filter_replacement:替换的字符数组,可以不设置,默认为空格字符
    • ignore_above:控制字符串是否被索引。

      • 大于ignore_above设置的字符串将不会被索引。对于字符串数组,ignore_above将分别应用于每个数组元素,并且长度大于ignore_above的字符串元素将不会被索引。
      • 缺省值是256字节
    • ower_case:是否将令牌转换为小写,从而实现不区分大小写的匹配。

      • true:转换为小写
      • false:不转换为小写
  • COMMENT 是可选的
CREATE TABLE table_name
(columns_difinition,INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|chinese|unicode")] [COMMENT 'your comment']INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|chinese|unicode")] [COMMENT 'your comment']INDEX idx_name3(column_name3) USING INVERTED [PROPERTIES("parser" = "chinese", "parser_mode" = "fine_grained|coarse_grained")] [COMMENT 'your comment']INDEX idx_name4(column_name4) USING INVERTED [PROPERTIES("parser" = "english|chinese|unicode", "support_phrase" = "true|false")] [COMMENT 'your comment']INDEX idx_name5(column_name4) USING INVERTED [PROPERTIES("char_filter_type" = "char_replace", "char_filter_pattern" = "._"), "char_filter_replacement" = " "] [COMMENT 'your comment']INDEX idx_name5(column_name4) USING INVERTED [PROPERTIES("char_filter_type" = "char_replace", "char_filter_pattern" = "._")] [COMMENT 'your comment']
)
table_properties;

在不同的数据模型中,倒排索引有不同的限制:

  • 聚合模型:只能为Key列创建倒排索引
    唯一模型:需要启用写时合并特性。启用后,可以为任何列创建倒排索引。
  • 复制模型:可以为任何列创建倒排索引

向已存在的表添加倒排索引

0.0版本之前:

-- syntax 1
CREATE INDEX idx_name ON table_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese|unicode")] [COMMENT 'your comment'];
-- syntax 2
ALTER TABLE table_name ADD INDEX idx_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese|unicode")] [COMMENT 'your comment'];
  • 2.0.0版本之后(包括2.0.0):
  • 上面的“创建/添加索引”操作只会为增量数据生成倒排索引。增加BUILD INDEX的语法,为存量数据添加倒排索引:
-- syntax 1, add inverted index to the stock data of the whole table by default
BUILD INDEX index_name ON table_name;
-- syntax 2, partition can be specified, and one or more can be specified
BUILD INDEX index_name ON table_name PARTITIONS(partition_name1, partition_name2);

(上面的创建/添加索引操作需要在执行BUILD index之前执行)

要查看BUILD INDEX的进度,可以运行以下语句

SHOW BUILD INDEX [FROM db_name];
-- Example 1: Viewing the progress of all BUILD INDEX tasks
SHOW BUILD INDEX;
-- Example 2: Viewing the progress of the BUILD INDEX task for a specified table
SHOW BUILD INDEX where TableName = "table1";

要取消BUILD INDEX,可以运行以下语句

CANCEL BUILD INDEX ON table_name;
CANCEL BUILD INDEX ON table_name (job_id1,jobid_2,...);

删除倒排索引

-- syntax 1
DROP INDEX idx_name ON table_name;
-- syntax 2
ALTER TABLE table_name DROP INDEX idx_name;

使用倒排索引加速查询

-- 1. fulltext search using MATCH_ANY OR MATCH_ALL
SELECT * FROM table_name WHERE column_name MATCH_ANY | MATCH_ALL 'keyword1 ...';-- 1.1 find rows that logmsg contains keyword1
SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword1';-- 1.2 find rows that logmsg contains keyword1 or keyword2 or more keywords
SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword1 keyword2';-- 1.3 find rows that logmsg contains both keyword1 and keyword2 and more keywords
SELECT * FROM table_name WHERE logmsg MATCH_ALL 'keyword1 keyword2';-- 1.4 When not specifying a slop for a phrase query, the default slop is 0, meaning "keyword1 keyword2" must be adjacent. You can specify the slop for a phrase query using the tilde (~) symbol.
SELECT * FROM table_name WHERE logmsg MATCH_PHRASE 'keyword1 keyword2';
SELECT * FROM table_name WHERE logmsg MATCH_PHRASE 'keyword1 keyword2 ~3';-- 1.5 Under the premise of maintaining word order, perform prefix matching on the last word "keyword2", and default to finding 50 prefix words (controlled by the session variable "inverted_index_max_expansions").
SELECT * FROM table_name WHERE logmsg MATCH_PHRASE_PREFIX 'keyword1 keyword2';-- 1.6 If only one word is provided, it will revert to a prefix query, and default to finding 50 prefix words (controlled by the session variable "inverted_index_max_expansions").
SELECT * FROM table_name WHERE logmsg MATCH_PHRASE_PREFIX 'keyword1';-- 1.7 Perform regular expression matching on the segmented words, and default to matching 50 words (controlled by the session variable "inverted_index_max_expansions").
SELECT * FROM table_name WHERE logmsg MATCH_REGEXP 'key*';-- 2. normal equal, range query
SELECT * FROM table_name WHERE id = 123;
SELECT * FROM table_name WHERE ts > '2023-01-01 00:00:00';
SELECT * FROM table_name WHERE op_type IN ('add', 'delete');

Tokenization Function

要评估分词的实际效果或文本块分词,可以使用tokenize 函数。

mysql> SELECT TOKENIZE('武汉长江大桥','"parser"="chinese","parser_mode"="fine_grained");
+-----------------------------------------------------------------------------------+
| tokenize('武汉长江大桥', '"parser"="chinese","parser_mode"="fine_grained"')       |
+-----------------------------------------------------------------------------------+
| ["武汉", "武汉长江大桥", "长江", "长江大桥", "大桥"]                              |
+-----------------------------------------------------------------------------------+
1 row in set (0.02 sec)mysql> SELECT TOKENIZE('武汉市长江大桥','"parser"="chinese","parser_mode"="fine_grained");
+--------------------------------------------------------------------------------------+
| tokenize('武汉市长江大桥', '"parser"="chinese","parser_mode"="fine_grained"')        |
+--------------------------------------------------------------------------------------+
| ["武汉", "武汉市", "市长", "长江", "长江大桥", "大桥"]                               |
+--------------------------------------------------------------------------------------+
1 row in set (0.02 sec)mysql> SELECT TOKENIZE('武汉市长江大桥','"parser"="chinese","parser_mode"="coarse_grained");
+----------------------------------------------------------------------------------------+
| tokenize('武汉市长江大桥', '"parser"="chinese","parser_mode"="coarse_grained"')        |
+----------------------------------------------------------------------------------------+
| ["武汉市", "长江大桥"]                                                                 |
+----------------------------------------------------------------------------------------+
1 row in set (0.02 sec)mysql> SELECT TOKENIZE('I love CHINA','"parser"="english");
+------------------------------------------------+
| tokenize('I love CHINA', '"parser"="english"') |
+------------------------------------------------+
| ["i", "love", "china"]                         |
+------------------------------------------------+
1 row in set (0.02 sec)mysql> SELECT TOKENIZE('I love CHINA 我爱我的祖国','"parser"="unicode");
+-------------------------------------------------------------------+
| tokenize('I love CHINA 我爱我的祖国', '"parser"="unicode"')       |
+-------------------------------------------------------------------+
| ["i", "love", "china", "我", "爱", "我", "的", "祖", "国"]        |
+-------------------------------------------------------------------+
1 row in set (0.02 sec)

6.5 Examples

这个示例将演示使用一个有100万行的hackernews数据集创建倒排索引、全文查询和普通查询。还将显示使用和不使用倒排索引之间的性能比较。

创建表


CREATE DATABASE test_inverted_index;USE test_inverted_index;-- define inverted index idx_comment for comment column on table creation
--   USING INVERTED specify using inverted index
--   PROPERTIES("parser" = "english") specify english word parser
CREATE TABLE hackernews_1m
(`id` BIGINT,`deleted` TINYINT,`type` String,`author` String,`timestamp` DateTimeV2,`comment` String,`dead` TINYINT,`parent` BIGINT,`poll` BIGINT,`children` Array<BIGINT>,`url` String,`score` INT,`title` String,`parts` Array<INT>,`descendants` INT,INDEX idx_comment (`comment`) USING INVERTED PROPERTIES("parser" = "english") COMMENT 'inverted index for comment'
)
DUPLICATE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES ("replication_num" = "1");

导入数据

通过流加载加载数


wget https://doris-build-1308700295.cos.ap-beijing.myqcloud.com/regression/index/hacknernews_1m.csv.gzcurl --location-trusted -u root: -H "compress_type:gz" -T hacknernews_1m.csv.gz  http://127.0.0.1:8030/api/test_inverted_index/hackernews_1m/_stream_load
{"TxnId": 2,"Label": "a8a3e802-2329-49e8-912b-04c800a461a6","TwoPhaseCommit": "false","Status": "Success","Message": "OK","NumberTotalRows": 1000000,"NumberLoadedRows": 1000000,"NumberFilteredRows": 0,"NumberUnselectedRows": 0,"LoadBytes": 130618406,"LoadTimeMs": 8988,"BeginTxnTimeMs": 23,"StreamLoadPutTimeMs": 113,"ReadDataTimeMs": 4788,"WriteDataTimeMs": 8811,"CommitAndPublishTimeMs": 38
}

通过SQL count()检查加载的数据

mysql> SELECT count() FROM hackernews_1m;
+---------+
| count() |
+---------+
| 1000000 |
+---------+
1 row in set (0.02 sec)

查询

  • 全文检索查询
    使用LIKE计算评论包含’OLAP’的行数,花费0.18秒
mysql> SELECT count() FROM hackernews_1m WHERE comment LIKE '%OLAP%';
+---------+
| count() |
+---------+
|      34 |
+---------+
1 row in set (0.18 sec)
  • 使用基于倒排索引的MATCH_ANY全文搜索计算评论包含’OLAP’的行,成本为0.02秒,加速速度为9倍,在更大的数据集上加速速度会更大
    • 这里结果条数的差异,是因为倒排索引对 comment 分词后,还会对词进行进行统一成小写等归一化处理,因此 MATCH_ANYLIKE 的结果多一些。
mysql> SELECT count() FROM hackernews_1m WHERE comment MATCH_ANY 'OLAP';
+---------+
| count() |
+---------+
|      35 |
+---------+
1 row in set (0.02 sec)

同样,count on“OLTP”显示0.07s vs 0.01s。由于Doris中的缓存,LIKE和MATCH_ANY都更快,但仍然有7倍的加速。

mysql> SELECT count() FROM hackernews_1m WHERE comment LIKE '%OLTP%';
+---------+
| count() |
+---------+
|      48 |
+---------+
1 row in set (0.07 sec)mysql> SELECT count() FROM hackernews_1m WHERE comment MATCH_ANY 'OLTP';
+---------+
| count() |
+---------+
|      51 |
+---------+
1 row in set (0.01 sec)
  • 同样,count on“OLTP”显示0.07s vs 0.01s。由于Doris中的缓存,LIKE和MATCH_ANY都更快,但仍然有7倍的加速。
mysql> SELECT count() FROM hackernews_1m WHERE comment LIKE '%OLTP%';
+---------+
| count() |
+---------+
|      48 |
+---------+
1 row in set (0.07 sec)mysql> SELECT count() FROM hackernews_1m WHERE comment MATCH_ANY 'OLTP';
+---------+
| count() |
+---------+
|      51 |
+---------+
1 row in set (0.01 sec)
  • 同时搜索“OLAP”和“OLTP”,0.13秒vs 0.01秒,加速13倍
    • 如果需要显示所有关键字,则使用MATCH_ALL
mysql> SELECT count() FROM hackernews_1m WHERE comment LIKE '%OLAP%' AND comment LIKE '%OLTP%';
+---------+
| count() |
+---------+
|      14 |
+---------+
1 row in set (0.13 sec)mysql> SELECT count() FROM hackernews_1m WHERE comment MATCH_ALL 'OLAP OLTP';
+---------+
| count() |
+---------+
|      15 |
+---------+
1 row in set (0.01 sec)
  • 搜索“OLAP”或“OLTP”中的至少一个,0.12秒vs 0.01秒,加速12倍
    • 如果只需要出现至少一个关键字,则使用MATCH_ANY
mysql> SELECT count() FROM hackernews_1m WHERE comment LIKE '%OLAP%' OR comment LIKE '%OLTP%';
+---------+
| count() |
+---------+
|      68 |
+---------+
1 row in set (0.12 sec)mysql> SELECT count() FROM hackernews_1m WHERE comment MATCH_ANY 'OLAP OLTP';
+---------+
| count() |
+---------+
|      71 |
+---------+
1 row in set (0.01 sec)
  • 普通相等,范围查询
    查询DateTime
mysql> SELECT count() FROM hackernews_1m WHERE timestamp > '2007-08-23 04:17:00';
+---------+
| count() |
+---------+
|  999081 |
+---------+
1 row in set (0.03 sec)

为时间戳列添加倒排索引

-- for timestamp column, there is no need for word parser, so just USING INVERTED without PROPERTIES
-- this is the first syntax for CREATE INDEX
mysql> CREATE INDEX idx_timestamp ON hackernews_1m(timestamp) USING INVERTED;
Query OK, 0 rows affected (0.03 sec)

在2.0.0版本(包括2.0.0)之后,你需要执行BUILD INDEX来为存量 数据添加倒排索引:

mysql> BUILD INDEX idx_timestamp ON hackernews_1m;
Query OK, 0 rows affected (0.01 sec)

查看索引创建进度,通过 FinishTime 和 CreateTime 的差值,可以看到 100 万条数据对 timestamp 列建倒排索引只用了 1s

mysql> SHOW ALTER TABLE COLUMN;
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| JobId | TableName     | CreateTime              | FinishTime              | IndexName     | IndexId | OriginIndexId | SchemaVersion | TransactionId | State    | Msg  | Progress | Timeout |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| 10030 | hackernews_1m | 2023-02-10 19:44:12.929 | 2023-02-10 19:44:13.938 | hackernews_1m | 10031   | 10008         | 1:1994690496  | 3             | FINISHED |      | NULL     | 2592000 |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
1 row in set (0.00 sec)

在2.0.0版本之后(包括2.0.0),您可以通过 SHOW BUILD INDEX查看存量数据创建索引的进度:

-- If the table has no partitions, the PartitionName defaults to TableName
mysql> SHOW BUILD INDEX;
+-------+---------------+---------------+----------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| JobId | TableName     | PartitionName | AlterInvertedIndexes                                     | CreateTime              | FinishTime              | TransactionId | State    | Msg  | Progress |
+-------+---------------+---------------+----------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| 10191 | hackernews_1m | hackernews_1m | [ADD INDEX idx_timestamp (`timestamp`) USING INVERTED],  | 2023-06-26 15:32:33.894 | 2023-06-26 15:32:34.847 | 3             | FINISHED |      | NULL     |
+-------+---------------+---------------+----------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
1 row in set (0.04 sec)

在建立索引后,Doris会自动使用索引进行范围查询,但性能几乎是一样的,因为它在小数据集上已经很快了

mysql> SELECT count() FROM hackernews_1m WHERE timestamp > '2007-08-23 04:17:00';
+---------+
| count() |
+---------+
|  999081 |
+---------+
1 row in set (0.01 sec)

对具有数值类型的parent 列进行类似的测试,使用相等的查询

mysql> SELECT count() FROM hackernews_1m WHERE parent = 11189;
+---------+
| count() |
+---------+
|       2 |
+---------+
1 row in set (0.01 sec)-- do not use word parser for numeric type USING INVERTED
-- use the second syntax ALTER TABLE
mysql> ALTER TABLE hackernews_1m ADD INDEX idx_parent(parent) USING INVERTED;
Query OK, 0 rows affected (0.01 sec)-- After version 2.0.0 (including 2.0.0), you need to execute `BUILD INDEX` to add inverted index to the stock data:
mysql> BUILD INDEX idx_parent ON hackernews_1m;
Query OK, 0 rows affected (0.01 sec)mysql> SHOW ALTER TABLE COLUMN;
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| JobId | TableName     | CreateTime              | FinishTime              | IndexName     | IndexId | OriginIndexId | SchemaVersion | TransactionId | State    | Msg  | Progress | Timeout |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| 10030 | hackernews_1m | 2023-02-10 19:44:12.929 | 2023-02-10 19:44:13.938 | hackernews_1m | 10031   | 10008         | 1:1994690496  | 3             | FINISHED |      | NULL     | 2592000 |
| 10053 | hackernews_1m | 2023-02-10 19:49:32.893 | 2023-02-10 19:49:33.982 | hackernews_1m | 10054   | 10008         | 1:378856428   | 4             | FINISHED |      | NULL     | 2592000 |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+mysql> SHOW BUILD INDEX;
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| JobId | TableName     | PartitionName | AlterInvertedIndexes                               | CreateTime              | FinishTime              | TransactionId | State    | Msg  | Progress |
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| 11005 | hackernews_1m | hackernews_1m | [ADD INDEX idx_parent (`parent`) USING INVERTED],  | 2023-06-26 16:25:10.167 | 2023-06-26 16:25:10.838 | 1002          | FINISHED |      | NULL     |
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
1 row in set (0.01 sec)mysql> SELECT count() FROM hackernews_1m WHERE parent = 11189;
+---------+
| count() |
+---------+
|       2 |
+---------+
1 row in set (0.01 sec)
  • 对字符串类型的 author 建立不分词的倒排索引,等值查询也可以利用索引加速
mysql> SELECT count() FROM hackernews_1m WHERE author = 'faster';
+---------+
| count() |
+---------+
|      20 |
+---------+
1 row in set (0.03 sec)-- do not use any word parser for author to treat it as a whole
mysql> ALTER TABLE hackernews_1m ADD INDEX idx_author(author) USING INVERTED;
Query OK, 0 rows affected (0.01 sec)-- After version 2.0.0 (including 2.0.0), you need to execute `BUILD INDEX` to add inverted index to the stock data:
mysql> BUILD INDEX idx_author ON hackernews_1m;
Query OK, 0 rows affected (0.01 sec)-- costs 1.5s to BUILD INDEX for author column with 1 million rows.
mysql> SHOW ALTER TABLE COLUMN;
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| JobId | TableName     | CreateTime              | FinishTime              | IndexName     | IndexId | OriginIndexId | SchemaVersion | TransactionId | State    | Msg  | Progress | Timeout |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+
| 10030 | hackernews_1m | 2023-02-10 19:44:12.929 | 2023-02-10 19:44:13.938 | hackernews_1m | 10031   | 10008         | 1:1994690496  | 3             | FINISHED |      | NULL     | 2592000 |
| 10053 | hackernews_1m | 2023-02-10 19:49:32.893 | 2023-02-10 19:49:33.982 | hackernews_1m | 10054   | 10008         | 1:378856428   | 4             | FINISHED |      | NULL     | 2592000 |
| 10076 | hackernews_1m | 2023-02-10 19:54:20.046 | 2023-02-10 19:54:21.521 | hackernews_1m | 10077   | 10008         | 1:1335127701  | 5             | FINISHED |      | NULL     | 2592000 |
+-------+---------------+-------------------------+-------------------------+---------------+---------+---------------+---------------+---------------+----------+------+----------+---------+mysql> SHOW BUILD INDEX order by CreateTime desc limit 1;
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| JobId | TableName     | PartitionName | AlterInvertedIndexes                               | CreateTime              | FinishTime              | TransactionId | State    | Msg  | Progress |
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| 13006 | hackernews_1m | hackernews_1m | [ADD INDEX idx_author (`author`) USING INVERTED],  | 2023-06-26 17:23:02.610 | 2023-06-26 17:23:03.755 | 3004          | FINISHED |      | NULL     |
+-------+---------------+---------------+----------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
1 row in set (0.01 sec)-- equal qury on text field autor get 3x speedup
mysql> SELECT count() FROM hackernews_1m WHERE author = 'faster';
+---------+
| count() |
+---------+
|      20 |
+---------+
1 row in set (0.01 sec)

相关文章:

Apache Doris 基础 -- 数据表设计(表索引)

1、索引概述 索引用于帮助快速过滤或搜索数据。目前&#xff0c;Doris支持两种类型的索引:内置智能索引和用户创建的二级索引。 内置智能索引 排序键和前缀索引:Apache Doris基于排序键以有序的方式存储数据。它为每1024行数据创建一个前缀索引。索引中的键是当前1024行组的…...

资源描述框架的用途及实际应用解析

什么是RDF&#xff1f; RDF代表 资源描述框架 RDF是用于描述网络资源的框架 RDF旨在被计算机阅读和理解 RDF并非设计用于供人阅读 RDF以 XML 编写 示例 描述购物商品的属性&#xff0c;如价格和可用性描述网络活动的时间表描述网页的信息&#xff08;内容&#xff0c;作者&a…...

工业级物联网边缘网关解决方案-天拓四方

随着工业4.0时代的到来&#xff0c;越来越多的企业开始寻求智能化升级&#xff0c;以提高生产效率、降低运营成本并增强市场竞争力。然而&#xff0c;在实际的转型升级过程中&#xff0c;许多企业面临着数据孤岛、设备兼容性差、网络安全风险高等问题&#xff0c;这些问题严重制…...

认识微服务,认识Spring Cloud

1. 介绍 本博客探讨的内容如下所示 什么是微服务&#xff1f;什么是springcloud&#xff1f;微服务和springcloud有什么关系&#xff1f; 首先&#xff0c;没有在接触springcloud之前&#xff0c;我写的项目都是单体结构&#xff0c; 但随着网站的用户量越来越大&#xff0c;…...

电脑设置密码怎么设置?让你的电脑更安全!

在如今信息化的社会中&#xff0c;保护个人电脑的安全至关重要。设置密码是最基本的电脑安全措施之一&#xff0c;它可以有效防止未经授权的访问和保护个人隐私&#xff0c;可是电脑设置密码怎么设置&#xff1f;本文将介绍三种设置电脑密码的方法&#xff0c;帮助您加强电脑的…...

搜维尔科技:SenseGlove Nova2使用主动接触反馈来模拟手掌的感觉,结合力反馈和振动触觉反馈,使其成为市场上第一款具有手掌反馈的无线触觉手套

SenseGlove Nova2使用主动接触反馈来模拟手掌的感觉&#xff0c;结合力反馈和振动触觉反馈&#xff0c;使其成为市场上第一款具有手掌反馈的无线触觉手套。 搜维尔科技&#xff1a;SenseGlove Nova2使用主动接触反馈来模拟手掌的感觉&#xff0c;结合力反馈和振动触觉反馈&…...

基于Python的实验室管理系统的设计与实现(论文+源码)_kaic

摘 要 随着实验室设备越来越多&#xff0c;实验室及其设备管理工作变得越来越繁重&#xff0c;还存在些管理模式仍旧处于手工管理模式和一些抢占实验室的不文明现象&#xff0c;传统的手工模式已经满足不了日益增长的管理需求&#xff0c;而本系统摒弃传统模式&#xff0c;开启…...

Windows系统WDS+MDT网络启动自动化安装

Windows系统WDS+MDT网络启动自动化安装 适用于在Windows系统上WDS+MDT网络启动自动化安装 1. 安装准备 1.下载windows server 2019、windows 10 pro的ISO文件,并安装好windows server 2019 2.下载windows 10 2004版ADK及镜像包 1.1 安装平台 Windows 111.2. 软件信息 软件…...

Apple开发者证书创建完整过程

1.创建CSR文件: 打开钥匙串访问程序 选择从证书颁发机构请求 创建证书 保存CSR文件到桌面 成功如下: 开始创建证书: 选择...

for深入学习

目录 练习&#xff1a; 例1&#xff1a; 求解0-100中整除3的数有哪些 例2&#xff1a; 求0-100中含数字9个个数 作业&#xff1a; 练习&#xff1a; 例1&#xff1a; 求解0-100中整除3的数有哪些 代码&#xff1a; #include<stdio.h> int main() {printf("整…...

引用(C++)和内联函数

前言&#xff1a;本文主要讲解C语法中引用如何使用和使用时的一些技巧 基本语法 引用就是取别名 #include <iostream> using namespace std; int main() {int a 10;int& b a;//给a取别名为bcout << a << endl;cout << b << endl;return 0…...

【stm32/CubeMX、HAL库】swjtu嵌入式实验七 ADC 实验

相关电路与IO引脚 注意&#xff1a;串口打印重定向后使用printf打印需要在keil里勾选 Use MicroLIB &#xff0c;否则会卡住。 参看&#xff1a;https://zhuanlan.zhihu.com/p/565613666 串口重定向&#xff1a; /* USER CODE BEGIN Includes */#include <stdio.h>//…...

springboot 解耦、隔离、异步的原则以及实战

在Spring Boot中实现解耦、隔离和异步的原则,能够提升应用程序的可维护性、可扩展性和性能。下面我会先介绍这三个原则的基本概念和意义,然后通过实战示例展示如何在Spring Boot应用中应用这些原则。 解耦 解耦是减少或消除应用程序组件之间依赖关系的过程,以提高模块的独…...

设计模式详解(八):外观模式——Facade

目录导航 什么是外观模式现实生活类比实战示例门面模式的好处门面模式源码举例 什么是外观模式 外观模式的英文名是Facade&#xff0c;意思是the front of a building&#xff0c;即建筑物的正面&#xff08;门面&#xff09;&#xff0c;我个人更喜欢翻译成门面模式。门面模式…...

R语言绘图 | 双Y轴截断图

教程原文&#xff1a;双Y轴截断图绘制教程 本期教程 本期教程&#xff0c;我们提供的原文的译文&#xff0c;若有需求请回复关键词&#xff1a;20240529 小杜的生信笔记&#xff0c;自2021年11月开始做的知识分享&#xff0c;主要内容是R语言绘图教程、转录组上游分析、转录组…...

使用PNP管控制MCU是否需要复位

这两台用到一款芯片带电池&#xff0c;希望电池还有电芯片在工作的时候插入电源不要给芯片复位&#xff0c;当电池没电&#xff0c;芯片不在工作的时候&#xff0c;插入电源给芯片复位所以使用一个PNP三极管&#xff0c;通过芯片IO控制是否打开复位&#xff0c;当芯片正常工作的…...

二重,三重积分和曲面,曲线积分的关系和区别

这是我在学习完曲面曲线积分概念后容易和二重三重积分混淆而大概总结和区分了一下&#xff0c;如果有错误请大佬指出&#xff0c;多谢&#xff01;&#xff01;&#xff01;...

处理STM32 DMA方式下的HAL_UART_ERROR_ORE错误

1. 检查并调整DMA和UART配置 确保初始化顺序&#xff1a;需要确保USART的CR寄存器UE位开关留到最后打开&#xff0c;即完成USART和DMA的所有配置初始化后再使能USART。这样可以避免初始化顺序不当导致的通信问题。配置合适的DMA缓冲区&#xff1a;确保DMA缓冲区足够大&#xf…...

初学者如何对大模型进行微调?

粗略地说&#xff0c;大模型训练有四个主要阶段&#xff1a;预训练、有监督微调、奖励建模、强化学习。 预训练消耗的时间占据了整个训练pipeline的99%&#xff0c;其他三个阶段是微调阶段&#xff0c;更多地遵循少量 GPU 和数小时或数天的路线。预训练对于算力和数据的要求非…...

【Qt知识】disconnect

在Qt框架中&#xff0c;disconnect函数用于断开信号与槽之间的连接。当不再需要某个信号触发特定槽函数时&#xff0c;或者为了防止内存泄漏和重复执行问题&#xff0c;你可以使用disconnect来取消这种关联。disconnect函数的基本用法可以根据不同的需求采用多种形式&#xff0…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...