分析型数据库__ClickHouse_ 深度学习

文章目录

  • 分析型数据库__ClickHouse_ 深度学习
    • 一、ClickHouse概述
      • 1.1、什么是ClickHouse
      • 1.2、什么是列式存储
      • 1.3、DBMS 的功能
      • 1.4、多样化引擎
      • 1.5、高吞吐写入能力
      • 1.6、数据分区与线程级并行
    • 二、安装
    • 三、基本数据类型
      • 3.1、整形
      • 3.2、浮点型
      • 3.3、布尔型
      • 3.4、字符串
      • 3.5、Decimal型
      • 3.6、时间类型
      • 3.7、复杂类型
      • 3.8、特殊类型
    • 四、表引擎
      • 4.1、表引擎的使用
      • 4.2、MergeTree家族
          • MergeTree
          • CollapsingMergeTree 【折叠树】
          • CollapsingMergeTree概述
        • ReplacingMergeTree 【去重树】
          • ReplacingMergeTree概述
        • SummingMergeTree 【聚合树】
      • 4.2、公司业务分析
    • 五、物化视图
    • `待更新`

一、ClickHouse概述

1.1、什么是ClickHouse

​ ClickHouse是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库 (DBMS),使用C语言编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告。

#补充
OLAP:联机分析处理   ClickHouse、StarRocks等。 主要业务数据聚合、查询分析等。
OLTP:联机事务处理  一些关系型数据库:Mysql、Oracle等。主要业务运行在于增删改。
从对数据库操作来看,OLTP主要是对数据的增删改,OLAP是对数据的查询。

1.2、什么是列式存储

例如:

id name age
1 张三 18
2 李四 22
3 王五 34

采用行式存储时,数据在磁盘上的组织结构为:

好处是想查某个人所有的属性时,可以通过一次磁盘查找加顺序读取就可以。但是当想查所有人的年龄时,需要不停的查找,或者全表扫描才行,遍历的很多数据都是不需要的。

而采用列式存储时,数据在磁盘上的组织结构为:


1.3、DBMS 的功能

​ 几乎覆盖了标准 SQL 的大部分语法,包括 DDL 和 DML,以及配套的各种函数,用户管理及权限管理,数据的备份与恢复。

1.4、多样化引擎

​ ClickHouse 和 MySQL 类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。目前包括合并树、日志、接口和其他四大类 20 多种引擎。

1.5、高吞吐写入能力

​ ClickHouse 采用类 LSM Tree 的结构,数据写入后定期在后台 Compaction 。通过类 LSM tree的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台compaction 时也是多个段 merge sort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在 HDD 上也有着优异的写入性能。官方公开 benchmark 测试显示能够达到 50MB-200MB/s的写入吞吐能力,按照每行100Byte 估算,大约相当于 50W-200W 条 /s 的写入速度。

1.6、数据分区与线程级并行

​ ClickHouse 将数据划分为多个 partition ,每个 partition再进一步划分为多个 index granularity(索引粒度 ) ,然后通过多个 CPU 核心分别处理其中的一部分来实现并行数据处理。在这种设计下, 单条 Query 就能利用整机所有 CPU 。 极致的并行处理能力,极大的降低了查询延时。 所以ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多cpu ,就不利于同时并发多条查询。所以对于高 qps 的查询业务,ClickHouse 并不是强项。

二、安装

三、基本数据类型

3.1、整形

固定长度的整型,包括有符号整型或无符号整型。Clickhouse 对大小写敏感。

整型范围(-2^(n-1) ~ 2^(n-1) - 1):

  • Int8 [-128 : 127] 【相当于Byte】
  • Int16 [-32768 : 32767] 【相当于short】
  • Int32 [-2147483648 : 2147483647] 【相当于int】
  • Int64 [-9223372036854775808 : 9223372036854775807] 【相当于long】

无符号整型范围(0 ~ 2^n -1)

  • UInt8 [0 : 255]
  • UInt16 [0 : 65535]
  • UInt32 [0 : 4294967295]
  • UInt64 [0 : 18446744073709551615]

3.2、浮点型

建议尽可能以整型形式存储数据。例如,将固定精度的数字转换为整数值,如果间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差。

  • Float32 - 【float】

  • Float64 - 【double】

    【命令行中测试】

​ 【DBeaver测试】 这里就不显示精度误差了、应该是工具进行处理了。

3.3、布尔型

​ 因为没有单独类型存储、所以使用UInt8 类型,取值限制为 0 或 1。

3.4、字符串

  • String

字符串可以任意长度的。它可以包含任意的字节集,包含空字节。

  • FixedString(N)

固定长度N 的字符串,N 必须是严格的正自然数。当服务端读取长度小于N的字符串时候,通过在字符串末尾添加空字节来达到N 字节长度。当服务端读取长度大于 N 的字符串时候,将返回错误消息。

与String 相比,极少会使用FixedString,因为使用起来不是很方便。

  • UUID

UUID是一种数据库常见的主键类型、在ClickHouse中直接把它作为一种数据类型。UUID共有32位、它的格式为8-4-4-4-12. 如果一个UUID类型的字段在写入数据时没有被赋值,则会依照格式使用0填充。

CREATE TABLE test_uuid
(`uid` UUID,`name` String
)
ENGINE = Memory ;
DESCRIBE TABLE test_uuid┌─name─┬─type───┬
│ uid  │ UUID   │
│ name │ String │
└──────┴────────┴
insert into test_uuid select generateUUIDv4() , 'zss' ;
select * from test_uuid ;
┌──────────────────────────────────uid─┬─name─┐
│ 47e39e22-d2d6-46fd-8014-7cd3321f4c7b │ zss  │
└──────────────────────────────────────┴──────┘-------------------------UUID类型的字段默认补位0-----------------------------
insert into test_uuid (name) values('hangge') ;
┌──────────────────────────────────uid─┬─name─┐
│ 47e39e22-d2d6-46fd-8014-7cd3321f4c7b │ zss  │
└──────────────────────────────────────┴──────┘
┌──────────────────────────────────uid─┬─name───┐
│ 00000000-0000-0000-0000-000000000000 │ hangge │
└──────────────────────────────────────┴────────┘

3.5、Decimal型

有符号的浮点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。
有三种声明:
➢ Decimal32(s) s),相当于 Decimal(9-x, s),有效位数为 1~9
➢ Decimal64(s) s),相当于 Decimal(18-x, s),有效位数为 1~18
➢ Decimal128(s) s),相当于 Decimal(38-x, s),有效位数为 1~38

3.6、时间类型

  • Date

Date类型不包含具体的时间信息,只精确到天,支持字符串形式写入

CREATE TABLE test_date
(`id` int,`ct` Date
)
ENGINE = Memory ;
DESCRIBE TABLE test_date  ;
┌─name─┬─type──┬
│ id   │ Int32 │
│ ct   │ Date  │
└──────┴───────┴
insert into test_date vlaues(1,'2021-09-11'),(2,now()) ;
select id , ct from test_date ;┌─id─┬─────────ct─┐
│  1 │ 2021-09-11 │
│  2 │ 2021-05-17 │
└────┴────────────┘
  • DateTime

DateTime类型包含时、分、秒信息,精确到秒,支持字符串形式写入:

create table testDataTime(ctime DateTime) engine=Memory ;
insert into testDataTime values('2021-12-27 01:11:12'),(now()) ;
select * from testDataTime ;

  • DateTime64

DateTime64可以记录亚秒,它在DateTime之上增加了精度的设置

3.7、复杂类型

  • Array(T)

​ CK支持数组这种复合数据类型 , 并且数据在操作在今后的数据分析中起到非常便利的效果!数组的定义方式有两种 : array(T) [e1,e2…] , 我们在这里要求数组中的数据类型是一致的!

数组的定义
[1,2,3,4,5]
array('a' , 'b' , 'c')
[1,2,3,'hello']   -- 错误
create table test_array(
id Int8 ,
hobby Array(String)
)engine=Memory ;
insert into test_array values(1,['eat','drink','la']),(2,array('sleep','palyg','sql'));
┌─id─┬─hobby───────────────────┐
│  1 │ ['eat','drink','la']    │
│  2 │ ['sleep','palyg','sql'] │
└────┴─────────────────────────┘
select id , hobby  , toTypeName(hobby) from test_array ;
┌─id─┬─hobby───────────────────┬─toTypeName(hobby)─┐
│  1 │ ['eat','drink','la']    │ Array(String)     │
│  2 │ ['sleep','palyg','sql'] │ Array(String)     │
└────┴─────────────────────────┴───────────────────┘select id , hobby[2]  , toTypeName(hobby) from test_array ; -- 数组的取值 [index]  1-based
  • Enum

ClickHouse支持枚举类型,这是一种在定义常量时经常会使用的数据类型。ClickHouse提供了Enum8和Enum16两种枚举类型,它们除了取值范围不同之外,别无二致。枚举固定使用(String:Int)Key/Value键值对的形式定义数据,所以Enum8和Enum16分别会对应(String:Int8)和(String:Int16)!

create table test_enum(id Int8 , color Enum('red'=1 , 'green'=2 , 'blue'=3)) engine=Memory ;
insert into  test_enum values(1,'red'),(1,'red'),(2,'green');
也可以使用这种方式进行插入数据:
insert into test_enum values(3,3) ;
  • Tuple

​ 元组类型由1~n个元素组成,每个元素之间允许设置不同的数据类型,且彼此之间不要求兼容。元组同样支持类型推断,其推断依据仍然以最小存储代价为原则。与数组类似,元组也可以使用两种方式定义,常规方式tuple(T):元组中可以存储多种数据类型,但是要注意数据类型的顺序

select tuple(1,'asb',12.23) as x , toTypeName(x) ;┌─x───────────────┬─toTypeName(tuple(1, 'asb', 12.23))─┐
│ (1,'asb',12.23) │ Tuple(UInt8, String, Float64)      │
└─────────────────┴────────────────────────────────────┘
---简写形式
SELECT (1, 'asb', 12.23) AS x,toTypeName(x)┌─x───────────────┬─toTypeName(tuple(1, 'asb', 12.23))─┐
│ (1,'asb',12.23) │ Tuple(UInt8, String, Float64)      │
└─────────────────┴────────────────────────────────────┘
注意:建表的时候使用元组的需要制定元组的数据类型
CREATE TABLE test_tuple (
c1 Tuple(UInt8, String, Float64)
) ENGINE = Memory; 
  • Nested

Nested是一种嵌套表结构。一张数据表,可以定义任意多个嵌套类型字段,但每个字段的嵌套层级只支持一级,即嵌套表内不能继续使用嵌套类型。对于简单场景的层级关系或关联关系,使用嵌套类型也是一种不错的选择。

create table test_nested(uid Int8 ,name String ,props Nested(pid Int8,pnames String ,pvalues String)
)engine = Memory ;
desc test_nested ;
┌─name──────────┬─type──────────┬
│ uid           │ Int8          │
│ name          │ String        │
│ props.pid     │ Array(Int8)   │
│ props.pnames  │ Array(String) │
│ props.pvalues │ Array(String) │
└───────────────┴───────────────┴

3.8、特殊类型

  • Nullable

准确来说、Nullable并不能算是一种独立的数据类型、它更像是一种辅助的修饰符。要与基础数据类型一起搭配使用。Nullable类型与Java8的Optional对象有些类似、它表示某个基础数据类型可以是Null值。

CREATE  table  test_nullable(id Int8,Name Nullable(UInt8)
) ENGINE = TinyLog ;INSERT into  test_nullable values (1,null);SELECT id,Name,toTypeName(Name)  FROM  test_nullable 

  • Domain

域名类型分为IPv4和IPv6两类、本质上他们是对整形和字符串的进一步封装、Ipv4类型是基于UInt32封装。

create table test_domain(
id Int8 ,
ip IPv4
)engine=Memory ;
insert  into test_domain values(1,'192.168.133.2') ;
insert  into test_domain values(1,'192.168.133') ; 在插入数据的会进行数据的检查所以这行数据会报错
-- Exception on client:
-- Code: 441. DB::Exception: Invalid IPv4 value.
-- Connecting to database doit1 at localhost:9000 as user default.
-- Connected to ClickHouse server version 20.8.3 revision 54438.为什么不直接采用String类型呢、则有一下两点考量。  (1) 出于便捷性的考量,例如IPv4类型支持格式检查,格式错误的IP数据是无法被写入的,例如:INSERT INTO IP4_TEST VALUES (‘www.51doit.com’,‘192.0.0’)Code: 441. DB::Exception: Invalid IPv4 value.(2) 出于性能的考量,同样以IPv4为例,IPv4使用UInt32存储,相比String更加紧凑,占用的空间更小,查询性能更快。IPv6类型是基于FixedString(16)封装的,它的使用方法与IPv4别无二致, 在使用Domain类型的时候还有一点需要注意,虽然它从表象上看起来与String一样,但Domain类型并不是字符串,所以它不支持隐式的自动类型转换。如果需要返回IP的字符串形式,则需要显式调用 IPv4NumToString或IPv6NumToString函数进行转换。

四、表引擎

4.1、表引擎的使用

​ 表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。包括:

➢ 数据的存储方式和位置,写到哪里以及从哪里读取数据 。
➢ 支持哪些查询以及如何支持。
➢ 并发数据访问。
➢ 索引的使用(如果存在)。
➢ 是否可以执行多线程请求。
➢ 数据复制参数。
表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参数。
特别注意:引擎的名称大小写敏感

【这里主要介绍MergeTree引擎】

若有需要可访问官网查询:ClickHouse概述

4.2、MergeTree家族

  • MergeTree
    • MergeTree概述

    Clickhouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。

    MergeTree 系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合并。相比在插入时不断修改(重写)已存储的数据,这种策略会高效很多。

    • 主要特点

      • 存储的数据按主键排序。

        这使得您能够创建一个小型的稀疏索引来加快数据检索。

      • 如果指定了 分区键 的话,可以使用分区。

        在相同数据集和相同结果集的情况下 ClickHouse 中某些带分区的操作会比普通操作更快。查询中指定了分区键时 ClickHouse 会自动截取分区数据。这也有效增加了查询性能。

      • 支持数据副本。

        ReplicatedMergeTree 系列的表提供了数据副本功能。更多信息,请参阅 数据副本 一节。

      • 支持数据采样。

        需要的话,您可以给表设置一个采样方法。

    • 建表语句

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],...INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
    ) ENGINE = MergeTree()
    ORDER BY expr
    [PARTITION BY expr]
    [PRIMARY KEY expr]
    [SAMPLE BY expr]
    [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
    [SETTINGS name=value, ...]
    
    • 子句分析

      • ENGINE - 引擎名和参数。 ENGINE = MergeTree(). MergeTree 引擎没有参数。

      • ORDER BY — 排序键。

        可以是一组列的元组或任意的表达式。 例如: ORDER BY (CounterID, EventDate)

        如果没有使用 PRIMARY KEY 显式指定的主键,ClickHouse 会使用排序键作为主键。

        如果不需要排序,可以使用 ORDER BY tuple(). 参考 选择主键

      • PARTITION BY — 分区键 ,可选项。

        要按月分区,可以使用表达式 toYYYYMM(date_column) ,这里的 date_column 是一个 Date 类型的列。分区名的格式会是 "YYYYMM"

      • PRIMARY KEY - 如果要 选择与排序键不同的主键,在这里指定,可选项。

        默认情况下主键跟排序键(由 ORDER BY 子句指定)相同。
        因此,大部分情况下不需要再专门指定一个 PRIMARY KEY 子句。

      • SAMPLE BY - 用于抽样的表达式,可选项。

        如果要用抽样表达式,主键中必须包含这个表达式。例如:
        SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))

      • TTL - 指定行存储的持续时间并定义数据片段在硬盘和卷上的移动逻辑的规则列表,可选项。

        表达式中必须存在至少一个 DateDateTime 类型的列,比如:

        TTL date + INTERVAl 1 DAY

        规则的类型 DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'指定了当满足条件(到达指定时间)时所要执行的动作:移除过期的行,还是将数据片段(如果数据片段中的所有行都满足表达式的话)移动到指定的磁盘(TO DISK 'xxx') 或 卷(TO VOLUME 'xxx')。默认的规则是移除(DELETE)。可以在列表中指定多个规则,但最多只能有一个DELETE的规则。

        更多细节,请查看 表和列的 TTL

      • SETTINGS — 控制 MergeTree 行为的额外参数,可选项:

      • index_granularity — 索引粒度。索引中相邻的『标记』间的数据行数。默认值8192 。参考数据存储。

      • index_granularity_bytes — 索引粒度,以字节为单位,默认值: 10Mb。如果想要仅按数据行数限制索引粒度, 请设置为0(不建议)。

      • min_index_granularity_bytes - 允许的最小数据粒度,默认值:1024b。该选项用于防止误操作,添加了一个非常低索引粒度的表。参考数据存储

      • enable_mixed_granularity_parts — 是否启用通过 index_granularity_bytes 控制索引粒度的大小。在19.11版本之前, 只有 index_granularity 配置能够用于限制索引粒度的大小。当从具有很大的行(几十上百兆字节)的表中查询数据时候,index_granularity_bytes 配置能够提升ClickHouse的性能。如果您的表里有很大的行,可以开启这项配置来提升SELECT 查询的性能。

      • use_minimalistic_part_header_in_zookeeper — ZooKeeper中数据片段存储方式 。如果use_minimalistic_part_header_in_zookeeper=1 ,ZooKeeper 会存储更少的数据。更多信息参考[服务配置参数](https://clickhouse.com/docs/zh/engines/table-engines/mergetree-family/mergetree/Server Settings | ClickHouse Documentation)这章中的 设置描述 。

      • min_merge_bytes_to_use_direct_io — 使用直接 I/O 来操作磁盘的合并操作时要求的最小数据量。合并数据片段时,ClickHouse 会计算要被合并的所有数据的总存储空间。如果大小超过了 min_merge_bytes_to_use_direct_io 设置的字节数,则 ClickHouse 将使用直接 I/O 接口(O_DIRECT 选项)对磁盘读写。如果设置 min_merge_bytes_to_use_direct_io = 0 ,则会禁用直接 I/O。默认值:10 * 1024 * 1024 * 1024 字节。

      • merge_with_ttl_timeout — TTL合并频率的最小间隔时间,单位:秒。默认值: 86400 (1 天)。

      • write_final_mark — 是否启用在数据片段尾部写入最终索引标记。默认值: 1(不要关闭)。

      • merge_max_block_size — 在块中进行合并操作时的最大行数限制。默认值:8192

      • storage_policy — 存储策略。 参见 使用具有多个块的设备进行数据存储.

      • min_bytes_for_wide_part,min_rows_for_wide_part 在数据片段中可以使用Wide格式进行存储的最小字节数/行数。您可以不设置、只设置一个,或全都设置。参考:数据存储

      • max_parts_in_total - 所有分区中最大块的数量(意义不明)

      • max_compress_block_size - 在数据压缩写入表前,未压缩数据块的最大大小。您可以在全局设置中设置该值(参见max_compress_block_size)。建表时指定该值会覆盖全局设置。

      • min_compress_block_size - 在数据压缩写入表前,未压缩数据块的最小大小。您可以在全局设置中设置该值(参见min_compress_block_size)。建表时指定该值会覆盖全局设置。

      • max_partitions_to_read - 一次查询中可访问的分区最大数。您可以在全局设置中设置该值(参见max_partitions_to_read)。

  • AggregatingmergeTree 【聚合树】

    • AggregatingmergeTree 概述

    该表引擎继承自MergeTree,可以使用 AggregatingMergeTree 表来做增量数据统计聚合。如果要按一组规则来合并减少行数,则使用 AggregatingMergeTree 是合适的。AggregatingMergeTree是通过预先定义的聚合函数计算数据并通过二进制的格式存入表内。

    与SummingMergeTree的区别在于:SummingMergeTree对非主键列进行sum聚合,而AggregatingMergeTree则可以指定各种聚合函数。

    • 建表语句
    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...
    ) ENGINE = AggregatingMergeTree()
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [TTL expr]
    [SETTINGS name=value, ...]
    • 示例
    CREATE TABLE emp_aggregatingmergeTree
    (emp_id     UInt16 COMMENT '员工id',name       String COMMENT '员工姓名',work_place String COMMENT '工作地点',age        UInt8 COMMENT '员工年龄',depart     String COMMENT '部门',salary     AggregateFunction(sum, Decimal32(2)) COMMENT '工资'
    ) ENGINE = AggregatingMergeTree() ORDER BY (emp_id, name) PRIMARY KEY emp_id PARTITION BY work_place;ORDER BY (emp_id,name) -- 注意排序key是两个字段
    PRIMARY KEY emp_id     -- 主键是一个字段#对于AggregateFunction类型的列字段,在进行数据的写入和查询时与其他的表引擎有很大区别,在写入数据时,需要调用-State函数;而在查询数据时,则需要调用相应的-Merge函数。对于上面的建表语句而言,需要使用sumState**函数进行数据插入-- 插入数据,
    -- 注意:需要使用INSERT…SELECT语句进行数据插入
    INSERT INTO TABLE emp_aggregatingmergeTree SELECT 1,'tom','上海',25,'信息部',sumState(toDecimal32(10000,2));
    INSERT INTO TABLE emp_aggregatingmergeTree SELECT 1,'tom','上海',25,'信息部',sumState(toDecimal32(20000,2));
    -- 查询数据
    SELECT emp_id,name,sumMerge(salary) FROM emp_aggregatingmergeTree GROUP BY emp_id,name;
    -- 结果输出
    ┌─emp_id─┬─name─┬─sumMerge(salary)─┐
    │      1 │ tom  │         30000.00 │
    └────────┴──────┴──────────────────┘

    但更多是AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用。

    -- 创建一个MereTree引擎的明细表
    -- 用于存储全量的明细数据
    -- 对外提供实时查询
    CREATE TABLE emp_mergetree_base
    (emp_id     UInt16 COMMENT '员工id',name       String COMMENT '员工姓名',work_place String COMMENT '工作地点',age        UInt8 COMMENT '员工年龄',depart     String COMMENT '部门',salary     Decimal32(2) COMMENT '工资'
    ) ENGINE = MergeTree() ORDER BY (emp_id, name) PARTITION BY work_place;-- 创建一张物化视图
    -- 使用AggregatingMergeTree表引擎
    CREATE MATERIALIZED VIEW view_emp_agg ENGINE = AggregatingMergeTree() PARTITION BY emp_id ORDER BY (emp_id, name) AS
    SELECT emp_id, name, sumState(salary) AS salary
    FROM emp_mergetree_base
    GROUP BY emp_id, name;-- 向基础明细表emp_mergetree_base插入数据
    INSERT INTO emp_mergetree_base VALUES (1,'tom','上海',25,'技术部',20000),(1,'tom','上海',26,'人事部',10000);-- 查询物化视图
    SELECT emp_id,name,sumMerge(salary) FROM view_emp_agg GROUP BY emp_id,name;
    -- 结果
    ┌─emp_id─┬─name─┬─sumMerge(salary)─┐
    │      1 │ tom  │         50000.00 │
    └────────┴──────┴──────────────────┘
  • CollapsingMergeTree 【折叠树】
    • CollapsingMergeTree概述

    ​ CollapsingMergeTree就是一种通过以增代删的思路,支持行级数据修改和删除的表引擎。它通过定义一个sign标记位字段,记录数据行的状态。如果sign标记为1,则表示这是一行有效的数据;如果sign标记为-1,则表示这行数据需要被删除。当CollapsingMergeTree分区合并时,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。

    ​ 每次需要新增数据时,写入一行sign标记为1的数据;需要删除数据时,则写入一行sign标记为-1的数据。

    • 数据操作情况

      第一条数据状态 第二条数据状态 情况
      分区内 排序相同 1 -1 删除
      分区内 排序相同 1 1 保留最后一条数据【更新】
      分区内 排序相同 -1 1 无法折叠【一二两条数据共存】
      分区内 排序相同 -1 -1 保留第一个数据【去重】
    • 建表语句

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...
    ) ENGINE = CollapsingMergeTree(sign)
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [SETTINGS name=value, ...]
    • 举例

      上面的建表语句使用CollapsingMergeTree(sign),其中字段sign是一个Int8类型的字

      CREATE TABLE emp_collapsingmergetree
      (emp_id     UInt16 COMMENT '员工id',name       String COMMENT '员工姓名',work_place String COMMENT '工作地点',age        UInt8 COMMENT '员工年龄',depart     String COMMENT '部门',salary     Decimal32(2) COMMENT '工资',sign       Int8
      ) ENGINE = CollapsingMergeTree(sign) ORDER BY (emp_id, name) PARTITION BY work_place;

      CollapsingMergeTree同样是以ORDER BY排序键作为判断数据唯一性的依据。

      -- 插入新增数据,sign=1表示正常数据
      INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技术部',20000,1);-- 更新上述的数据
      -- 首先插入一条与原来相同的数据(ORDER BY字段一致),并将sign置为-1
      INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技术部',20000,-1);-- 再插入更新之后的数据
      INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技术部',30000,1);-- 查看一下结果
      select * from emp_collapsingmergetree ;
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │   -1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │    1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 30000.00 │    1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘-- 执行分区合并操作
      optimize table emp_collapsingmergetree FINAL;
      -- 再次查询,sign=1与sign=-1的数据相互抵消了,即被删除
      select * from emp_collapsingmergetree ;┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 30000.00 │    1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘

      CollapsingMergeTree对于写入数据的顺序有着严格要求,否则导致无法正常折叠。

      -- 建表
      CREATE TABLE emp_collapsingmergetree_order
      (emp_id     UInt16 COMMENT '员工id',name       String COMMENT '员工姓名',work_place String COMMENT '工作地点',age        UInt8 COMMENT '员工年龄',depart     String COMMENT '部门',salary     Decimal32(2) COMMENT '工资',sign       Int8
      ) ENGINE = CollapsingMergeTree(sign) ORDER BY (emp_id, name) PARTITION BY work_place;-- 先插入需要被删除的数据,即sign=-1的数据
      INSERT INTO emp_collapsingmergetree_order VALUES (1,'tom','上海',25,'技术部',20000,-1);
      -- 再插入sign=1的数据
      INSERT INTO emp_collapsingmergetree_order VALUES (1,'tom','上海',25,'技术部',20000,1);
      -- 查询表
      SELECT * FROM emp_collapsingmergetree_order;
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │   -1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │    1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘-- 执行合并操作
      optimize table emp_collapsingmergetree_order FINAL;
      -- 再次查询表
      -- 旧数据依然存在
      SELECT * FROM emp_collapsingmergetree_order;
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │   -1 │
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │    1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘

      存在问题:如果数据的写入程序是单线程执行的,则能够较好地控制写入顺序;如果需要处理的数据量很大,数据的写入程序通常是多线程执行的,那么此时就不能保障数据的写入顺序了。在这种情况下,CollapsingMergeTree的工作机制就会出现问题。但是可以通过VersionedCollapsingMergeTree的表引擎得到解决。

      引入:VersionedCollapsingMergeTree表引擎

      #上面提到CollapsingMergeTree表引擎对于数据写入乱序的情况下,不能够实现数据折叠的效果。VersionedCollapsingMergeTree表引擎的作用与CollapsingMergeTree完全相同,它们的不同之处在于,VersionedCollapsingMergeTree对数据的写入顺序没有要求,在同一个分区内,任意顺序的数据都能够完成折叠操作。#VersionedCollapsingMergeTree使用version列来实现乱序情况下的数据折叠。#建表语法
      CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
      (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...
      ) ENGINE = VersionedCollapsingMergeTree(sign, version)
      [PARTITION BY expr]
      [ORDER BY expr]
      [SAMPLE BY expr]
      [SETTINGS name=value, ...]#可以看出:该引擎除了需要指定一个sign标识之外,还需要指定一个UInt8类型的version版本号。
      

      示例:

      CREATE TABLE emp_versioned
      (emp_id     UInt16 COMMENT '员工id',name       String COMMENT '员工姓名',work_place String COMMENT '工作地点',age        UInt8 COMMENT '员工年龄',depart     String COMMENT '部门',salary     Decimal32(2) COMMENT '工资',sign       Int8,version    Int8
      ) ENGINE = VersionedCollapsingMergeTree(sign, version) ORDER BY (emp_id, name) PARTITION BY work_place;-- 先插入需要被删除的数据,即sign=-1的数据
      INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技术部',20000,-1,1);
      -- 再插入sign=1的数据
      INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技术部',20000,1,1);
      -- 在插入一个新版本数据
      INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技术部',30000,1,2);-- 先不执行合并,查看表数据
      select * from emp_versioned;
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │    1 │       1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 20000.00 │   -1 │       1 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 30000.00 │    1 │       2 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘-- 获取正确查询结果
      SELECT emp_id,name,sum(salary * sign) FROM emp_versioned GROUP BY emp_id,name HAVING sum(sign) > 0;
      ┌─emp_id─┬─name─┬─sum(multiply(salary, sign))─┐
      │      1 │ tom  │                    30000.00 │
      └────────┴──────┴─────────────────────────────┘-- 手动合并
      optimize table emp_versioned FINAL;-- 再次查询
      select * from emp_versioned;
      ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐
      │      1 │ tom  │ 上海       │  25 │ 技术部 │ 30000.00 │    1 │       2 │
      └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘

      ​ 可见上面虽然在插入数据乱序的情况下,依然能够实现折叠的效果。之所以能够达到这种效果,是因为在定义version字段之后,VersionedCollapsingMergeTree会自动将version作为排序条件并增加到ORDER BY的末端,就上述的例子而言,最终的排序字段为ORDER BY emp_id,name,version desc

  • ReplacingMergeTree 【去重树】
    • ReplacingMergeTree概述

    ​ ClickHouse中最常用也是最基础的表引擎为MergeTree,在它的功能基础上添加特定功能就构成了MergeTree系列引擎。MergeTree支持主键,但主键主要用来缩小查询范围,且不具备唯一性约束,可以正常写入相同主键的数据。但在一些情况下,可能需要表中没有主键重复的数据。ReplacingMergeTree就是在MergeTree的基础上加入了去重的功能,但它仅会在合并分区时,去删除重复的数据,写入相同数据时并不会引发异常。去重只会在分区内容进行去重,不能执行跨分区去重。所以不能保证数据重复性、但可以保证数据的最终一致性

    • 功能

    创建一张ReplacingMergeTree的表和创建MergeTree类似,修改引擎即可。ReplacingMergeTree引擎创建规范为:ENGINE = ReplacingMergeTree([ver]),其中ver为选填参数,它需要指定一个UInt8/UInt16、Date或DateTime类型的字段,它决定了数据去重时所用的算法,如果没有设置该参数,合并时保留分组内的最后一条数据;如果指定了该参数,则保留ver字段取值最大的那一行。

    1、不指定ver参数

    -- 创建未指定ver参数ReplacintMergeTree引擎的表
    CREATE TABLE replac_merge_test
    (`id` String, `code` String, `create_time` DateTime
    )
    ENGINE = ReplacingMergeTree()
    PARTITION BY toYYYYMM(create_time)
    PRIMARY KEY id
    ORDER BY (id, code)
    

    2、ReplacingMergeTree会根据ORDER BY所声明的表达式去重

-- 在上述表中插入数据
insert into replac_merge_test values ('A000', 'code1', now()),('A000', 'code1', '2020-07-28 21:30:00'), ('A001', 'code1', now()), ('A001', 'code2', '2020-07-28 21:30:00'), ('A0002', 'code2', now());
-- 查询当前数据
select * from replac_merge_test;
┌─id────┬─code──┬─────────create_time─┐
│ A000  │ code1 │ 2020-07-28 21:23:48 │
│ A000  │ code1 │ 2020-07-28 21:30:00 │
│ A0002 │ code2 │ 2020-07-28 21:23:48 │
│ A001  │ code1 │ 2020-07-28 21:23:48 │
│ A001  │ code2 │ 2020-07-28 21:30:00 │
└───────┴───────┴─────────────────────┘-- 强制进行分区合并
optimize table replac_merge_test FINAL;
-- 再次查询数据
select * from replac_merge_test;
┌─id────┬─code──┬─────────create_time─┐
│ A000  │ code1 │ 2020-07-28 21:30:00 │
│ A0002 │ code2 │ 2020-07-28 21:23:48 │
│ A001  │ code1 │ 2020-07-28 21:23:48 │
│ A001  │ code2 │ 2020-07-28 21:30:00 │
└───────┴───────┴─────────────────────┘#通过上面示例可以看到,id、code相同的字段’A000’,'code1’被去重剩余一条数据,由于创建表时没有设置ver参数,故保留分组内的最后一条数据(create_time字段)-- 再次使用insert插入一条数据
insert into replac_merge_test values ('A001', 'code1', '2020-07-28 21:30:00');-- 查询表中数据
select * from replac_merge_test;
┌─id────┬─code──┬─────────create_time─┐
│ A000  │ code1 │ 2020-07-28 21:30:00 │
│ A0002 │ code2 │ 2020-07-28 21:23:48 │
│ A001  │ code1 │ 2020-07-28 21:23:48 │
│ A001  │ code2 │ 2020-07-28 21:30:00 │
└───────┴───────┴─────────────────────┘
┌─id───┬─code──┬─────────create_time─┐
│ A001 │ code1 │ 2020-07-28 21:30:00 │
└──────┴───────┴─────────────────────┘#可以看到,再次插入重复数据时,查询仍然会存在重复。在ClickHouse中,默认一条insert插入的数据为同一个数据分区,不同insert插入的数据为不同的分区,所以ReplacingMergeTree是以分区为单位进行去重的,也就是说只有在相同的数据分区内,重复数据才可以被删除掉。只有数据合并完成后,才可以使用引擎特性进行去重。

3、指定ver参数

-- 创建指定ver参数ReplacingMergeTree引擎的表
CREATE TABLE replac_merge_ver_test
(`id` String, `code` String, `create_time` DateTime
)
ENGINE = ReplacingMergeTree(create_time)
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, code)-- 插入测试数据
insert into replac_merge_ver_test values('A000', 'code1', '2020-07-10 21:35:30'),('A000', 'code1', '2020-07-15 21:35:30'),('A000', 'code1', '2020-07-05 21:35:30'),('A000', 'code1', '2020-06-05 21:35:30');-- 查询数据
select * from replac_merge_ver_test;
┌─id───┬─code──┬─────────create_time─┐
│ A000 │ code1 │ 2020-06-05 21:35:30 │
└──────┴───────┴─────────────────────┘
┌─id───┬─code──┬─────────create_time─┐
│ A000 │ code1 │ 2020-07-10 21:35:30 │
│ A000 │ code1 │ 2020-07-15 21:35:30 │
│ A000 │ code1 │ 2020-07-05 21:35:30 │
└──────┴───────┴─────────────────────┘-- 强制进行分区合并
optimize table replac_merge_ver_test FINAL;-- 查询数据
select * from replac_merge_ver_test;
┌─id───┬─code──┬─────────create_time─┐
│ A000 │ code1 │ 2020-07-15 21:35:30 │
└──────┴───────┴─────────────────────┘
┌─id───┬─code──┬─────────create_time─┐
│ A000 │ code1 │ 2020-06-05 21:35:30 │
└──────┴───────┴─────────────────────┘#由于上述创建表是以create_time的年月来进行分区的,可以看出不同的数据分区,ReplacingMergeTree并不会进行去重,并且在相同数据分区内,指定ver参数后,会保留同一组数据内create_time时间最大的那一行数据。

  • SummingMergeTree 【聚合树】
    • SummingMergeTree概述

    该引擎继承自 MergeTree。区别在于,当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值。如果主键的组合方式使得单个键值对应于大量的行,则可以显著的减少存储空间并加快数据查询的速度。

    我们推荐将该引擎和 MergeTree 一起使用。例如,在准备做报告的时候,将完整的数据存储在 MergeTree 表中,并且使用 SummingMergeTree 来存储聚合数据。这种方法可以使你避免因为使用不正确的主键组合方式而丢失有价值的数据。

    • 建表语句

      CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
      (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...
      ) ENGINE = SummingMergeTree([columns])
      [PARTITION BY expr]
      [ORDER BY expr]
      [SAMPLE BY expr]
      [SETTINGS name=value, ...]
    • 演示

      # 创建表
      drop table summtt;
      CREATE TABLE summtt
      (key String,name String,value Int32
      )
      ENGINE = SummingMergeTree()
      ORDER BY key;# 插入数据
      insert into summtt values('a', 'xiaoa1', 1);
      insert into summtt values('a', 'xiaoa2', 2);
      insert into summtt values('b', 'xiaob1', 10);
      insert into summtt values('b', 'xiaob2', 20);
      insert into summtt values('c', 'xiaob1', 10);
      insert into summtt values('c', 'xiaob2', -10);# 查询数据发现聚合
      SELECT * FROM summtt;# 执行合并命令,为0的会被删除
      optimize table summtt;
      select * from summtt;select key, sum(value) from summtt group by key;
    • 总结

      • SummingMergeTree使用ORDER BY排序键作为聚合数据的依据
      • 如果SummingMergeTree((col1,col2,…))指定了字段,只会聚合指定字段,如果没有指定,会对所有非ORDER BY字段以外的非数值字段进行聚合
      • 只有在合并分区时才会触发汇总逻辑
      • 聚合只会发生在同分区内,不同分区的数据不会发生聚合
      • 支持嵌套结构,但列字段名必须以Map为后缀结尾,任何名称以Key 、Id、Type为后缀结尾的字段,都将和ORDER BEY指定的聚合字段组合成一个复合的key,进行聚合
      • 汇总会对聚合的数值类型字段进行sum,对非聚合类型字段或者是非数值类型字段会取相同key分区的第一条

4.2、公司业务分析

  • 现主要需求在于频繁的以增取代删除和更新、MUTATION去通过【Alter Table】实现数据的更新、删除等。是一种较重的操作。

    它与标准SQL语法中的UPDATE、DELETE不同,是异步执行的,对于批量数据不频繁的更新或删除比较有用、那么通过分析、满足需要的引擎大致可用一下三种:CollapsingMergeTree、VersionedCollapsingMergeTree、ReplacingMergeTree

通过上述具体介绍了三种引擎适用情况。

VersionedCollapsingMergeTree:主要是通过sign值和版本号去标识数据。那么更新删除可能都要指定对应的值,进行折叠数据。

弊端仍然是Merge之前、前后数据都是分区形式存在的。那么必然在执行Merge前可能会查到差异数据。 现在想法可能是分布式事务去处理更新删除流程。达到一个最终一致性。

ReplacingMergeTree:通过阅读华为开发者社区文章、ReplacingMergeTree去实现更新删除可能会更加方便。替换树在保证主键唯一性的基础下、最新的数据会被保留。在内部其实表里的数据仍然分区形式存在。但查询语句做一些处理过滤掉老数据。函数argMax(a, b)可以按照b的最大值取a的值。

这里引入华为云社区:谈论使用情况。
ReplacingMergeTree:实现Clickhouse数据更新

五、物化视图

  • 基础概念

​ SQL里普通视图可以简单理解事对一些复杂语句进行抽象化、本来不携带任何数据。而物化视图是包括一个查询结果的数据库对象,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,简单的来理解就是它在普通视图的基础上加上了视图中select后所存储的数据

待更新

分析型数据库__ClickHouse_ 深度学习相关推荐

  1. 分析型数据库 AnalyticDB学习 ----基本介绍

    分析型数据库 AnalyticDB学习 ----基本介绍 AnalyticDB简介 阿里巴巴自主研发的海量数据实时并发在线分析的云计算服务,可以在毫秒级针对千亿级数据进行多维分析和业务探索.具备海量数 ...

  2. 回首2018 | 分析型数据库AnalyticDB:不忘初心 砥砺前行...

    导读 分析型数据库AnalyticDB(下文简称"ADB"),是阿里巴巴自主研发.唯一经过超大规模以及核心业务验证的PB级实时数据仓库.截止目前,现有外部支撑客户既包括传统的大中型 ...

  3. 悠星网络基于阿里云分析型数据库PostgreSQL版的数据实践

    悠星网络基于阿里云分析型数据库PostgreSQL版的数据实践 说到"大数据",当下这个词很火,各行各业涉及到数据的,目前都在提大数据,提数据仓库,数据挖掘或者机器学习,但同时另外 ...

  4. 回首2018 | 分析型数据库AnalyticDB:不忘初心 砥砺前行

    导读 分析型数据库AnalyticDB(下文简称"ADB"),是阿里巴巴自主研发.唯一经过超大规模以及核心业务验证的PB级实时数据仓库.截止目前,现有外部支撑客户既包括传统的大中型 ...

  5. 回首2018 | 分析型数据库AnalyticDB: 不忘初心 砥砺前行...

    题记 分析型数据库AnalyticDB(下文简称ADB),是阿里巴巴自主研发.唯一经过超大规模以及核心业务验证的PB级实时数据仓库.截止目前,现有外部支撑客户既包括传统的大中型企业和政府机构,也包括众 ...

  6. AnalyticDB分析型数据库

    AnalyticDB分析型数据库 知识点结构图 本文初衷是为了学习归纳,若有错误,请指出. 修改记录 时间 内容 2020年9月13日 第一次发布 一.概述 1.1 定义 ​ 分析型数据库Analyt ...

  7. 如何打造一款极速分析型数据库

    一 极速 OLAP 数据库:预计算 VS 现场计算 1.1 Materialized View 1.2 预聚合 1.3 索引 1.4 Cache二 极速 OLAP 数据库:可扩展性三 极速 OLAP ...

  8. 2022爱分析· 中国分析型数据库市场研究报告

    报告编委 爱分析 黄勇 合伙人&首席分析师 张扬 合伙人&首席分析师 洪逸群 高级分析师 任理 分析师 中国信通院云大所 魏凯 中国信通院云计算与大数据研究所副所长 序言 数字化时代, ...

  9. 分析型数据库受大数据市场追捧

    文章讲的是分析型数据库受大数据市场追捧,近期,大数据领域有一些值得关注的动向.首先,EMC和VMware正式公布成立新公司Pivotal.其次,Actian公司宣布收购大规模并行处理(MPP)分析数据 ...

最新文章

  1. python3 import execjs ModuleNotFoundError: No module named ‘execjs‘
  2. 思科无线AP胖瘦互转
  3. excel中对比两个sheet,找出匹配不上的
  4. 零基础如何学python-零基础小白如何学python,想请教大家,求指导 ?
  5. uva 714 Copying Books
  6. 【论文】本周论文推荐(迁移学习、阅读理解、对话系统、图神经网络、对抗生成网络等)...
  7. T-SQL之条件链接
  8. 在DataList、Repeater的HeaderTemplate和FooterTemplate模板中寻找控件FindControl
  9. 【预测模型】基于matlab GUI BP神经网络+最小二乘法预测模型【含Matlab源码 208期】
  10. 系统间通讯实现数据信息实时同步解决方案
  11. jQuery closest() 方法
  12. 消防应急照明和疏散指示系统
  13. 本地字体上传到网络后运用到微信小程序详细过程
  14. MySQL精简版安装教程
  15. 2022 年了,重新理解一波设备驱动 | Linux 驱动
  16. word合并文档php,使用PHPWord合并Word文档,在文档指定页插入另一个文档的内容
  17. Rails+Nginx+Passenger安装配置 简洁可靠
  18. 397_压缩图片到一定大小(质量)
  19. Javascript在线美化,格式化,js美化
  20. 顶会竟然攀比起了拒稿率?教授发文怒斥「挑刺式审稿」

热门文章

  1. httpd服务器的守护程序级别如何修改,apachectl 和 httpd的关系
  2. HDI PCB 板 0.1mm 以下的孔不好造?它不服
  3. 【Qt】有一种Bug叫“麻烦制造者——福昕PDF阅读器”
  4. NIO多路复用之Selector的使用
  5. 【机器学习杂记】自注意力机制(Self-attention)
  6. 2011年度10大iPad游戏
  7. Java多线程案例:模拟12306火车站售票系统
  8. hexo stellar主题修改字体
  9. 唱吧mp3 mp4 免费提取
  10. 三菱PLC批量传送指令BMOV