嘘~ 正在从服务器偷取页面 . . . 可尝试再次刷新 . . .

ClickHouse单机部署配置使用(21.9.4.35)


ClickHouse

官网:https://clickhouse.com/

官方文档:https://clickhouse.com/docs/en/intro

tgz包下载:https://packages.clickhouse.com/tgz/stable/

1. ClickHouse简介:

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

1.1 什么是 OLAP ?

OLAP 场景需要在大型数据集之上实时响应复杂的分析查询,具有以下特征:

  • 数据集可能非常庞大 - 数十亿或数万亿行
  • 数据组织在包含许多列的表中
  • 仅选择几列来回答任何特定查询
  • 结果必须以毫秒或秒为单位返回

1.2 面向列与面向行的数据库

在面向行的 DBMS 中,数据存储在行中,与行相关的所有值物理上彼此相邻存储。

在面向列的 DBMS 中,数据存储在列中,同一列中的值存储在一起。

1.3 ClickHouse 的特点

1.3.1 列式存储

以下面的表为例:

idnameage
1张三18
2李四22
3王五34

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

image-20240129150508584

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

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

image-20240129150559349

​ 这时想查所有人的年龄只需把年龄那一列拿出来就可以了

3)列式储存的好处:

  • 对于列的聚合,计数,求和等统计操作原因优于行式存储。
  • 由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列 选择更优的数据压缩算法,大大提高了数据的压缩比重。
  • 由于数据压缩比更好,一方面节省了磁盘空间,另一方面对于 cache 也有了更大的 发挥空间。

1.3.2 DBMS 的功能

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

1.3.3 多样化引擎

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

1.3.4 高吞吐写入能力

​ ClickHouse 采用类LSM Tree的结构,数据写入后定期在后台 Compaction。通过类 LSM tree 的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台 compaction 时也是多个段 merge sort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞 吐能力,即便在 HDD 上也有着优异的写入性能。

​ 官方公开 benchmark 测试显示能够达到 50MB-200MB/s 的写入吞吐能力,按照每行 100Byte 估算,大约相当于 50W-200W 条/s 的写入速度。

1.3.5 数据分区与线程级并行

​ ClickHouse 将数据划分为多个 partition,每个 partition 再进一步划分为多个 index granularity(索引粒度),然后通过多个 CPU核心分别处理其中的一部分来实现并行数据处理。 在这种设计下,单条 Query 就能利用整机所有 CPU。极致的并行处理能力,极大的降低了查 询延时。

​ 所以,ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端 就是对于单条查询使用多 cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务, ClickHouse 并不是强项。

1.4 Clickhouse安装包:

包名作用
clickhouse-client包含 clickhouse 客户端交互工具
clickhouse-common包含 clickhouse 服务端执行脚本
clickhouse-server包含 clickhouse 服务端配置文件

1.5 安装后主要分布目录:

路径说明
/etc/clickhouse-serverclickhouse 服务端配置文件目录
/etc/clickhouse-clientclickhouse 客户端配置文件目录
/var/lib/clickhouseclickhouse 默认数据目录
/var/log/clickhouse-serverclickhouse 默认日志目录
/etc/init.d/clickhouse-serverclickhouse 服务端启动脚本
     关于tgz包安装部署方法,官网也写有安装脚本(个人感觉脚本拉取tgz包太慢,于是直接下载到了本地,再上传到linux服务器)

==官网tgz包安装Clickhouse脚本==:

#!/bin/bash
LATEST_VERSION=$(curl -s https://packages.clickhouse.com/tgz/stable/ | \
    grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | sort -V -r | head -n 1)
export LATEST_VERSION

case $(uname -m) in
  x86_64) ARCH=amd64 ;;
  aarch64) ARCH=arm64 ;;
  *) echo "Unknown architecture $(uname -m)"; exit 1 ;;
esac

for PKG in clickhouse-common-static clickhouse-common-static-dbg clickhouse-server clickhouse-client clickhouse-keeper
do
  curl -fO "https://packages.clickhouse.com/tgz/stable/$PKG-$LATEST_VERSION-${ARCH}.tgz" \
    || curl -fO "https://packages.clickhouse.com/tgz/stable/$PKG-$LATEST_VERSION.tgz"
done

tar -xzvf "clickhouse-common-static-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-common-static-$LATEST_VERSION.tgz"
sudo "clickhouse-common-static-$LATEST_VERSION/install/doinst.sh"

tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION.tgz"
sudo "clickhouse-common-static-dbg-$LATEST_VERSION/install/doinst.sh"

tar -xzvf "clickhouse-server-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-server-$LATEST_VERSION.tgz"
sudo "clickhouse-server-$LATEST_VERSION/install/doinst.sh" configure
sudo /etc/init.d/clickhouse-server start

tar -xzvf "clickhouse-client-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-client-$LATEST_VERSION.tgz"
sudo "clickhouse-client-$LATEST_VERSION/install/doinst.sh"

2. 手动部署clickhouse

2.1 解压安装包

[root@master opt]# mkdir /opt/module/clickhouse
[root@master opt]# tar -zxf clickhouse-common-static-21.9.4.35.tgz -C /opt/module/clickhouse
[root@master opt]# tar -zxf clickhouse-common-static-dbg-21.9.4.35.tgz -C /opt/module/clickhouse
[root@master opt]# tar -zxf clickhouse-server-21.9.4.35.tgz -C /opt/module/clickhouse
[root@master opt]# tar -zxf clickhouse-client-21.9.4.35.tgz -C /opt/module/clickhouse

2.2 解压后依照顺序执行每个clickhouse/install下的doinst.sh命令

[root@master clickhouse-common-static-21.9.4.35]# install/doinst.sh
[root@master clickhouse-common-static-dbg-21.9.4.35]# install/doinst.sh
[root@master clickhouse-server-21.9.4.35]# install/doinst.sh

[root@bigdata1 clickhouse]# sh clickhouse-common-static-21.9.4.35/install/doinst.sh
[root@bigdata1 clickhouse]# sh clickhouse-common-static-dbg-21.9.4.35/install/doinst.sh 
[root@bigdata1 clickhouse]# sh clickhouse-server-21.9.4.35/install/doinst.sh 

执行 clickhouse-server 会创建一个用户 default 并让你设置密码,直接回车密码设置为空

image-20230825134747331

是否设置ClickHouse服务器的网络连接权限(选择y)

[root@bigdata1 clickhouse]# sh clickhouse-client-21.9.4.35/install/doinst.sh

设置远程访问并移除默认监听文件(listen.xml),同时由于9000端口被hadoop占用,需要将clickhouse的端口更改为9001

(没有就不理)
[root@master ~]# rm -rf /etc/clickhouse-server/listen.xml

修改前先备份

[root@master ~]# cp /etc/clickhouse-server/config.xml /etc/clickhouse-server/config.xml.bak

给config.xml加权限

[root@master ~]# chmod 777 /etc/clickhouse-server/config.xml

编辑config.xml 文件

[root@master ~]# vi /etc/clickhouse-server/config.xml

取消掉的注释变为:0.0.0.0
将里面所有的9000变成9001

快速修改指令:

sed -i "s/9000/9001/g" /etc/clickhouse-server/config.xml

2.3 clickhouse启动

方式1:

[root@master clickhouse-server-21.9.4.35]# clickhouse start

img

方式2(推荐):

启动:
[root@master ~]# systemctl start clickhouse-server
查看服务状态:
[root@master ~]# systemctl status clickhouse-server

image-20240114124027390

2.4 连接clickhouse-client

语法:

clickhouse-client –host=主机地址 –port 端口 –user=用户 –password=密码

参数:

  • –host:指定要连接的ClickHouse服务器的主机名或IP地址。
  • –port:指定要连接的ClickHouse服务器的端口号,默认为9000。
  • –user:指定要使用的用户名进行身份验证,默认为”default”。
  • –password:指定要使用的密码进行身份验证。
  • –database:指定要使用的默认数据库。
  • –query:指定要在连接后立即执行的查询。
clickhouse-client --host=localhost --port 9001 --user=default --password 

2.5 执行SQL语句验证

master :) show databases;

SHOW DATABASES

Query id: 6d54b636-f017-441a-adfc-1eb6e60e4676

┌─name────┐
│ default │
│ system  │
└─────────┘

2 rows in set. Elapsed: 0.002 sec.

结果:image-20230825134052335

2.6 修改default用户密码

源文件只读文件,添加可写(w)权限再进行修改

chmod 755 /etc/clickhouse-server/users.xml

修改配置后重启服务

[root@master ~]# vi /etc/clickhouse-server/users.xml

修改第68行参数(安装时回车已经设置密码为空,现在进行添加密码)

image-20240109224506535

例子参考:image-20240109224556801

重启服务

1.关闭服务
clickhouse stop
systemctl stop clickhouse-server.service
2.启动服务
clickhouse start
systemctl start clickhouse-server.service

再次登录:

[root@master ~]# clickhouse-client --host=localhost --port 9001 --user=default --password 123456
ClickHouse client version 21.9.4.35 (official build).
Connecting to localhost:9001 as user default.
Connected to ClickHouse server version 21.9.4 revision 54449.

master :) show databases;

SHOW DATABASES

Query id: 6ad41603-b367-4021-956a-c024b1af5175

┌─name────┐
│ default │
│ system  │
└─────────┘

2 rows in set. Elapsed: 0.003 sec. 

master :)

登录成功,密码设置成功

2.7 开启远程连接

修改配置文件:

[root@master ~]# vim /etc/clickhouse-server/config.xml

将以下注释打开(大概156行左右):

(这里会出现问题,直接跳到下面的问题解决)

<listen_host>::</listen_host>

修改后如下:

image-20240126204844344

:warning: 修改后重启服务报错

image-20240126214736560

查看日志发现关键报错信息:

[root@master ~]# tailf /var/log/clickhouse-server/clickhouse-server.log

image-20240126214815077

OS禁用IPv6后配置:::,该配置支持了IPv4和IPv6,而OS又禁用了IPv6会导致报错如下:

<Error> Application: DB::Exception: Listen [::]:8123 failed: Poco::Exception. Code: 1000, e.code() = 0, DNS error: EAI: Address family for hostname not supported (version 21.9.4.35 (official build))
尖叫提示(1):
在禁用了ipv6时,如果使用<listen_host>::</listen_host>配置,会报如下错误
Application: DB::Exception: Listen [::]:8123 failed: Poco::Exception. Code: 1000, e.code() =0, e.displayText() = DNS error: EAI: -9
<!-- 如果禁用了ipv6,使用下面配置-->
<listen_host>0.0.0.0</listen_host>
<!-- 如果没有禁用ipv6,使用下面配置
<listen_host>::</listen_host>
-->

:star:问题解决:

::改成0.0.0.0

image-20240126220044758

2.8 安装脚本(参考)

#!/bin/bash
#脚本放再tgz包目录执行
mkdir /opt/clickhouse
client_tgz=`ls | grep "client"`
commondbg_tgz=`ls | grep 'common' | awk "NR==2{print $1}"`
common_tgz=`ls | grep 'common' | awk "NR==1{print $1}"`
server_tgz=`ls | grep "server"`

cat <<EOF
************************
** 开始安装clickhouse **
************************
EOF
echo ">>开始解压client"
tar -zxf $client_tgz -C /opt/clickhouse
echo ">>开始解压common-dbg"
tar -zxf $commondbg_tgz -C /opt/clickhouse
echo ">>开始解压common"
tar -zxf $common_tgz -C /opt/clickhouse
echo ">>开始解压server"
tar -zxf $server_tgz -C /opt/clickhouse

client=`ls /opt/clickhouse| grep "client"`
commondbg=`ls /opt/clickhouse | grep "common-static-dbg"`
common=`ls /opt/clickhouse | grep "common" | awk "NR==1{print $1}"`
server=`ls /opt/clickhouse | grep "server"`

sh /opt/clickhouse/$common/install/doinst.sh
sh /opt/clickhouse/$commondbg/install/doinst.sh
# 执行 clickhouse-server 会创建一个用户 default 并让你设置密码,直接回车密码设置为空
echo  "=====设置default用户密码(回车设置密码为空)======" 
cat <<EOF
*******************************************
** 设置default用户密码(回车设置密码为空) **
** 是否设置ClickHouse服务器的网络连接权限**
*******************************************
EOF
sh /opt/clickhouse/$server/install/doinst.sh 2>1 /dev/null
sh /opt/clickhouse/$client/install/doinst.sh

read -p "是否修改clickhouse默认端口(y/n):" yn
if [ $yn == 'y' ];then
  read -p "修改的端口为:" port
  sed -i "s/9000/$port/g" /etc/clickhouse-server/config.xml
  if [ $? -eq 0 ];then
   echo ">clickhouse端口已更改成:$port"
  else 
   echo "修改失败!!"
  fi
else
  echo ">clickhouse默认端口为: 9000 "
fi

#启动
echo "=====启动====="
clickhouse start
echo "=====状态====="
clickhouse status

if [ $? -eq 0 ];then
  read -p "是否查看登录帮助(y/n)" help
  if [ $help == 'y' ];then
cat <<EOF
语法:clickhouse-client --host=主机地址 --port 端口 --user=用户 --password=密码 
参数:
	--host:指定要连接的ClickHouse服务器的主机名或IP地址。
	--port:指定要连接的ClickHouse服务器的端口号,默认为9000。
	--user:指定要使用的用户名进行身份验证,默认为"default"。
	--password:指定要使用的密码进行身份验证。
	--database:指定要使用的默认数据库。
	--query:指定要在连接后立即执行的查询。
EOF
fi

cat <<EOF
************************
** clickhouse安装完成 **
************************
EOF
else
cat <<EOF
**************************************
** clickhouse安装失败(请检查配置) **
**************************************
EOF
fi

3. 数据类型

3.1 整型

固定长度的整型,包括有符号整型或无符号整型。
整型范围(-2n-1~2n-1-1):
Int8 - [-128 : 127]
Int16 - [-32768 : 32767]
Int32 - [-2147483648 : 2147483647]
Int64 - [-9223372036854775808 : 9223372036854775807]
无符号整型范围(0~2n-1):
UInt8 - [0 : 255]
UInt16 - [0 : 65535]
UInt32 - [0 : 4294967295]
UInt64 - [0 : 18446744073709551615]
使用场景: 个数、数量、也可以存储型 id。

3.2 浮点型

Float32 - float
Float64 – double

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

image-20240129153115084

使用场景:一般数据值比较小,不涉及大量的统计计算,精度要求不高的时候。比如 保存商品的重量。

3.3 布尔型

没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1。

3.4 Decimal 型

有符号的浮点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会 被丢弃(不舍入)。

有三种声明:

➢ Decimal32(s),相当于 Decimal(9-s,s),有效位数为 1~9
➢ Decimal64(s),相当于 Decimal(18-s,s),有效位数为 1~18
➢ Decimal128(s),相当于 Decimal(38-s,s),有效位数为 1~38

s 标识小数位

使用场景:

一般金额字段、汇率、利率等字段为了保证小数点精度,都使用 Decimal 进行存储。

3.5 字符串

1)String :star:

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

2)FixedString(N)

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

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

使用场景:

​ 名称、文字描述、字符型编码。 固定长度的可以保存一些定长的内容,比 如一些编码,性别等但是考虑到一定的变化风险,带来收益不够明显,所以定长字符串使用 意义有限。

3.6 枚举类型

包括 Enum8 和 Enum16 类型。Enum 保存 'string'= integer 的对应关系。
Enum8 用 'String'= Int8 对描述。
Enum16 用 'String'= Int16 对描述。

1)用法演示

创建一个带有一个枚举 Enum8(‘hello’ = 1, ‘world’ = 2) 类型的列

CREATE TABLE t_enum
(
 x Enum8('hello' = 1, 'world' = 2)
)
ENGINE = TinyLog;

2)这个 x 列只能存储类型定义中列出的值:’hello’或’world’

master :)  INSERT INTO t_enum VALUES ('hello'), ('world'), ('hello');

image-20240129153658091

3)如果尝试保存任何其他值,ClickHouse 抛出异常

master :)  insert into t_enum values('a')

image-20240129153754911

4)如果需要看到对应行的数值,则必须将 Enum 值转换为整数类型

SELECT CAST(x, 'Int8') FROM t_enum;

image-20240129153917241

使用场景:

​ 对一些状态、类型的字段算是一种空间优化,也算是一种数据约束。但是实 际使用中往往因为一些数据内容的变化增加一定的维护成本,甚至是数据丢失问题。所以谨 慎使用。

3.7 时间类型

目前 ClickHouse 有三种时间类型:

➢ Date 接受年--日的字符串比如 ‘2019-12-16’
➢ Datetime 接受年--日 时:分:秒的字符串比如 ‘2019-12-16 20:50:10’
➢ Datetime64 接受年--日 时:分:秒.亚秒的字符串比如‘2019-12-16 20:50:10.66’

日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。 还有很多数据结构,可以参考官方文档:https://clickhouse.yandex/docs/zh/data_types/

3.8 数组

Array(T):由 T 类型元素组成的数组。

​ T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ClickHouse 对多维数组 的支持有限。例如,不能在 MergeTree 表中存储多维数组。

(1)创建数组方式 1,使用 array 函数

array(T)
master :) SELECT array(1, 2) AS x, toTypeName(x) ;

image-20240129154216776

(2)创建数组方式 2:使用方括号

[]
master :) SELECT [1, 2] AS x, toTypeName(x);

image-20240129154312218

4. 表引擎

4.1 表引擎的使用

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

➢ 数据的存储方式和位置,写到哪里以及从哪里读取数据。
➢ 支持哪些查询以及如何支持。
➢ 并发数据访问。
➢ 索引的使用(如果存在)。
➢ 是否可以执行多线程请求。
➢ 数据复制参数。

表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关 参数。

:warning: 特别注意:引擎的名称大小写敏感

4.2 TinyLog

​ 以列文件的形式保存在磁盘上,不支持索引没有并发控制。一般保存少量数据的小表, 生产环境上作用有限。可以用于平时练习测试用。

如:

create table t_tinylog ( id String, name String) engine=TinyLog;

image-20240129154558392

4.3 Memory

​ 内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。 读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过 10G/s)。

​ 一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太 大(上限大概 1 亿行)的场景。

4.4 MergeTree

​ ClickHouse 中最强大的表引擎当属 MergeTree(合并树)引擎及该系列(*MergeTree) 中的其他引擎,支持索引和分区,地位可以相当于 innodb 之于 Mysql。而且基于 MergeTree, 还衍生除了很多小弟,也是非常有特色的引擎。

1)建表语句

create table t_order_mt(
 id UInt32,
 sku_id String,
 total_amount Decimal(16,2),
 create_time Datetime
) engine =MergeTree
 partition by toYYYYMMDD(create_time)
 primary key (id)
 order by (id,sku_id);

image-20240129154852133

2)插入数据

insert into t_order_mt values
(101,'sku_001',1000.00,'2023-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2023-06-01 11:00:00'),
(102,'sku_004',2500.00,'2023-06-01 12:00:00'),
(102,'sku_002',2000.00,'2023-06-01 13:00:00'),
(102,'sku_002',12000.00,'2023-06-01 13:00:00'),
(102,'sku_002',600.00,'2023-06-02 12:00:00');

image-20240129155022732

查询:

master :) select * from t_order_mt;

SELECT *
FROM t_order_mt

Query id: c1c4210f-596c-4165-8057-8c41072a865b

┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │         10002023-06-01 12:00:00 │
│ 102 │ sku_002 │         20002023-06-01 11:00:00 │
│ 102 │ sku_002 │         20002023-06-01 13:00:00 │
│ 102 │ sku_002 │        120002023-06-01 13:00:00 │
│ 102 │ sku_004 │         25002023-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │          6002023-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘

6 rows in set. Elapsed: 0.003 sec.

​ MergeTree 其实还有很多参数(绝大多数用默认值即可),但是三个参数是更加重要的, 也涉及了关于 MergeTree 的很多概念。

4.4.1 partition by 分区(可选)

1)作用

学过 hive 的应该都不陌生,分区的目的主要是降低扫描的范围,优化查询速度

2)如果不填

只会使用一个分区。

3)分区目录

​ MergeTree 是以列文件+索引文件+表定义文件组成的,但是如果设定了分区那么这些文 件就会保存到不同的分区目录中。

4)并行

分区后,面对涉及跨分区的查询统计,ClickHouse 会以分区为单位并行处理。

5)数据写入与分区合并

​ 任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入 后的某个时刻(大概 10-15 分钟后),ClickHouse 会自动执行合并操作(等不及也可以手动 通过 optimize 执行),把临时分区的数据,合并到已有分区中。

​ 在 ClickHouse 中,OPTIMIZE TABLE 的目的是优化表的性能和磁盘使用。FINAL 关键字表示执行最终的优化,以便尽可能地减小表的体积和提高查询性能。

​ 需要注意的是,OPTIMIZE TABLE 可能是一项消耗资源的操作,它可能会导致表在执行期间被锁定。因此,在执行这类操作之前,建议在系统低峰期进行,以避免对正在进行的其他操作造成干扰。

​ 执行完这个命令后,ClickHouse 将尝试对表 t_order_rmt 进行最终优化,包括合并分区、清理无效数据等。这可以帮助提高查询性能,并释放不再需要的磁盘空间。

optimize table xxxx final;

6)例如

再次执行上面的插入操作:

insert into t_order_mt values
(101,'sku_001',1000.00,'2023-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2023-06-01 11:00:00'),
(102,'sku_004',2500.00,'2023-06-01 12:00:00'),
(102,'sku_002',2000.00,'2023-06-01 13:00:00'),
(102,'sku_002',12000.00,'2023-06-01 13:00:00'),
(102,'sku_002',600.00,'2023-06-02 12:00:00');

查看数据并没有纳入任何分区:

image-20240129155530953

手动 optimize 之后:

master :) optimize table t_order_mt final

再次查询:

master :) select * from t_order_mt;

image-20240129155726376

4.4.2 primary key 主键(可选)

​ ClickHouse 中的主键,和其他数据库不太一样,它只提供了数据的一级索引,但是却不 是唯一约束。这就意味着是可以存在相同 primary key 的数据的。

​ 主键的设定主要依据是查询语句中的 where 条件。

​ 根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity,避 免了全表扫描。

​ index granularity: 直接翻译的话就是索引粒度,指在稀疏索引中两个相邻索引对应数 据的间隔。ClickHouse 中的 MergeTree 默认是 8192。官方不建议修改这个值,除非该列存在 大量重复值,比如在一个分区中几万行才有一个不同数据。

稀疏索引:

image-20240129155928459

​ 稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索 引粒度的第一行,然后再进行进行一点扫描。

4.4.3 order by(必选)

​ order by 设定了分区内的数据按照哪些字段顺序进行有序保存。
​ order by 是 MergeTree 中唯一一个必填项,甚至比 primary key 还重要,因为当用户不
设置主键的情况,很多处理会依照 order by 的字段进行处理(比如后面会讲的去重和汇总)。
要求:主键必须是 order by 字段的前缀字段。
​ 比如 order by 字段是 (id,sku_id) 那么主键必须是 id 或者(id,sku_id)

4.4.4 二级索引

​ 目前在 ClickHouse 的官网上二级索引的功能在v20.1.2.4 之前是被标注为实验性的,在 这个版本之后默认是开启的。

1)老版本使用二级索引前需要增加设置

是否允许使用实验性的二级索引``(v20.1.2.4 开始,这个参数已被删除,默认开启)`

set allow_experimental_data_skipping_indices=1;

2)创建测试表

create table t_order_mt2(
 id UInt32,
 sku_id String,
 total_amount Decimal(16,2),
 create_time Datetime,
INDEX a total_amount TYPE minmax GRANULARITY 5
) engine =MergeTree
 partition by toYYYYMMDD(create_time)
 primary key (id)
 order by (id, sku_id);

image-20240129160609251

其中 GRANULARITY N 是设定二级索引对于一级索引粒度的粒度。

3)插入数据

insert into t_order_mt2 values
(101,'sku_001',1000.00,'2023-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2023-06-01 11:00:00'),
(102,'sku_004',2500.00,'2023-06-01 12:00:00'),
(102,'sku_002',2000.00,'2023-06-01 13:00:00'),
(102,'sku_002',12000.00,'2023-06-01 13:00:00'),
(102,'sku_002',600.00,'2023-06-02 12:00:00');

4)对比效果

[root@master ~]# clickhouse-client --host master --user default --port 9001 --password 123456 --send_logs_level=trace <<< 'select * from t_order_mt2 where total_amount > toDecimal32(900., 2)';

image-20240129164539979

4.4.5 数据 TTL

TTL 即 Time To Live,MergeTree 提供了可以管理数据表或者列的生命周期的功能。

1)列级别 TTL

(1)创建测试表

create table t_order_mt3(
 id UInt32,
 sku_id String,
 total_amount Decimal(16,2) TTL create_time+interval 10 SECOND,
 create_time Datetime 
) engine =MergeTree
partition by toYYYYMMDD(create_time)
 primary key (id)
 order by (id, sku_id);

主要的修改是将 TTL 设置应用到 total_amount 字段,并使用 create_time 加上 10 秒作为过期时间。这样,一旦数据的 create_time 字段的值超过了当前时间加上 10 秒,相关的数据行就会被自动删除。

(2)插入数据(注意:根据实际时间改变)

insert into t_order_mt3 values
(106,'sku_001',1000.00,'2024-01-29 17:00:00'),
(107,'sku_002',2000.00,'2024-01-29 17:00:00'),
(110,'sku_003',600.00,'2024-01-29 17:00:00');

(3)手动合并,查看效果 到期后,指定的字段数据归 0

master :) optimize table t_order_mt3 final;

image-20240129170300068

2)表级 TTL

下面的这条语句是数据会在 create_time 之后 10 秒丢失

alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;

涉及判断的字段必须是 Date 或者 Datetime 类型,推荐使用分区的日期字段。

能够使用的时间周期:

- SECOND
- MINUTE
- HOUR
- DAY
- WEEK
- MONTH
- QUARTER
- YEAR

4.5 ReplacingMergeTree

​ ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全``继承 MergeTree,只是多了一个去重的功能`。 尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束 的功能。如果你想处理掉重复的数据,可以借助这个 ReplacingMergeTree。

1)去重时机

数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预 先作出计划。有一些数据可能仍未被处理。

2)去重范围

如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。

​ 所以 ReplacingMergeTree 能力有限, ReplacingMergeTree 适用于在后台清除重复的数 据以节省空间,但是它不保证没有重复的数据出现。

3)案例演示

(1)创建表

create table t_order_rmt(
 id UInt32,
 sku_id String,
 total_amount Decimal(16,2) ,
 create_time Datetime 
) engine =ReplacingMergeTree(create_time)
 partition by toYYYYMMDD(create_time)
 primary key (id)
 order by (id, sku_id);

:warning:注意:

​ ReplacingMergeTree() 填入的参数为版本字段,重复数据保留版本字段值最大的。 如果不填版本字段,默认按照插入顺序保留最后一条。

(2)向表中插入数据

insert into t_order_rmt values
(101,'sku_001',1000.00,'2024-01-29 12:00:00') ,
(102,'sku_002',2000.00,'2024-01-29 11:00:00'),
(102,'sku_004',2500.00,'2024-01-29 12:00:00'),
(102,'sku_002',2000.00,'2024-01-29 13:00:00'),
(102,'sku_002',12000.00,'2024-01-29 13:00:00'),
(102,'sku_002',600.00,'2024-01-29 12:00:00');

(3)执行第一次查询

select * from t_order_rmt;

image-20240129173118745

(4)手动合并

master :) OPTIMIZE TABLE t_order_rmt FINAL;

(5)再执行一次查询

select * from t_order_rmt;

image-20240129173127038

4)通过测试得到结论

➢ 实际上是使用 order by 字段作为唯一键
➢ 去重不能跨分区
➢ 只有同一批插入(新版本)或合并分区时才会进行去重
➢ 认定重复的数据保留,版本字段值最大的
➢ 如果版本字段相同则按插入顺序保留最后一笔

4.6 SummingMergeTree

​ 对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的MergeTree 的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。

​ ClickHouse 为了这种场景,提供了一种能够“预聚合”的引擎 SummingMergeTree

1)案例演示

(1)创建表

create table t_order_smt(
 id UInt32,
 sku_id String,
 total_amount Decimal(16,2) ,
 create_time Datetime 
) engine =SummingMergeTree(total_amount)
 partition by toYYYYMMDD(create_time)
 primary key (id)
 order by (id,sku_id );

(2)插入数据

insert into t_order_smt values
(101,'sku_001',1000.00,'2020-06-01 12:00:00'),
(102,'sku_002',2000.00,'2020-06-01 11:00:00'),
(102,'sku_004',2500.00,'2020-06-01 12:00:00'),
(102,'sku_002',2000.00,'2020-06-01 13:00:00'),
(102,'sku_002',12000.00,'2020-06-01 13:00:00'),
(102,'sku_002',600.00,'2020-06-02 12:00:00');

(3)执行第一次查询

master :) select * from t_order_smt;

image-20240129175941509

(4)手动合并

master :) OPTIMIZE TABLE t_order_smt FINAL;

(5)再执行一次查询

master :) select * from t_order_smt;

image-20240129175953202

2)通过结果可以得到以下结论

➢ 以 SummingMergeTree()中指定的列作为汇总数据列
➢ 可以填写多列必须数字列,如果不填,以所有非维度列且为数字列的字段为汇总数
据列
➢ 以 order by 的列为准,作为维度列
➢ 其他的列按插入顺序保留第一行
➢ 不在一个分区的数据不会被聚合
➢ 只有在同一批次插入(新版本)或分片合并时才会进行聚合

3)开发建议

设计聚合表的话,唯一键值、流水号可以去掉,所有字段全部是维度、度量或者时间戳。

4)问题

能不能直接执行以下 SQL 得到汇总值:

select total_amount from XXX where province_name=’’ and create_date=’xxx’

不行,可能会包含一些还没来得及聚合的临时明细

​ 如果要是获取汇总值,还是需要使用 sum 进行聚合,这样效率会有一定的提高,但本 身 ClickHouse 是列式存储的,效率提升有限,不会特别明显。

select sum(total_amount) from province_name=’’ and create_date=‘xxx’

5. SQL 操作

​ 基本上来说传统关系型数据库(以 MySQL 为例)的 SQL 语句,ClickHouse 基本都支持, 这里不会从头讲解 SQL 语法只介绍 ClickHouse 与标准 SQL(MySQL)不一致的地方。

5.1 Insert

基本与标准 SQL(MySQL)基本一致:

(1)标准

insert into [table_name] values(),(.) 

(2)从表到表的插入

insert into [table_name] select a,b,c from [table_name_2]

5.2 Update 和 Delete

​ ClickHouse 提供了 Delete 和 Update 的能力,这类操作被称为 Mutation 查询,它可以看 做 Alter 的一种。

​ 虽然可以实现修改和删除,但是和一般的 OLTP 数据库不一样,Mutation 语句是一种很 “重”的操作,而且不支持事务。

​ “重”的原因主要是每次修改或者删除都会导致放弃目标数据的原有分区,重建新分区。 所以尽量做批量的变更,不要进行频繁小数据的操作。

(1)删除操作

alter table t_order_smt delete where sku_id ='sku_001';

(2)修改操作

alter table t_order_smt update total_amount=toDecimal32(2000.00,2) where id = 102;

​ 由于操作比较“重”,所以 Mutation 语句分两步执行,同步执行的部分其实只是进行 新增数据新增分区和并把旧分区打上逻辑上的失效标记。直到触发分区合并的时候才会删 除旧数据释放磁盘空间,一般不会开放这样的功能给用户,由管理员完成。

5.3 查询操作

ClickHouse 基本上与标准 SQL 差别不大:

➢ 支持子查询
➢ 支持 CTE(Common Table Expression 公用表表达式 with 子句)
➢ 支持各种 JOIN,但是 JOIN 操作无法使用缓存,所以即使是两次相同的 JOIN 语句,ClickHouse 也会视为两条新 SQL
➢ 窗口函数(官方正在测试中...)
➢ 不支持自定义函数
➢ GROUP BY 操作增加了 with rollup\with cube\with total 用来计算小计和总计。

(1)插入数据

WHERE 1=1 的条件将匹配表中的所有行,因此执行后将删除整个表中的数据。这种方式不会释放表所占用的磁盘空间,而只是将数据标记为已删除。如果你需要释放磁盘空间,可以考虑使用 OPTIMIZE TABLE 命令。

master :) alter table t_order_mt delete where 1=1;

insert into t_order_mt values
(101,'sku_001',1000.00,'2020-06-01 12:00:00'),
(101,'sku_002',2000.00,'2020-06-01 12:00:00'),
(103,'sku_004',2500.00,'2020-06-01 12:00:00'),
(104,'sku_002',2000.00,'2020-06-01 12:00:00'),
(105,'sku_003',600.00,'2020-06-02 12:00:00'),
(106,'sku_001',1000.00,'2020-06-04 12:00:00'),
(107,'sku_002',2000.00,'2020-06-04 12:00:00'),
(108,'sku_004',2500.00,'2020-06-04 12:00:00'),
(109,'sku_002',2000.00,'2020-06-04 12:00:00'),
(110,'sku_003',600.00,'2020-06-01 12:00:00');

(2)with rollup:从右至左去掉维度进行小计

select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with rollup;

image-20240129181236785

(3)with cube : 从右至左去掉维度进行小计,再从左至右去掉维度进行小计

select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with cube;

image-20240129181412681

(4)with totals: 只计算合计

select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with totals;

image-20240129181501461

5.4 alter 操作

同 MySQL 的修改字段基本一致

1)新增字段

alter table tableName add column newcolname String after col1;

2)修改字段类型

alter table tableName modify column newcolname String;

3)删除字段

alter table tableName drop column newcolname;

5.5 导出数据 :star:

clickhouse-client --query "select * from t_order_mt where create_time='2020-06-01 12:00:00'" --format CSVWithNames> /opt/module/data/rs1.csv

简写:

clickhouse-client --q "select * from t_order_mt where create_time='2020-06-01 12:00:00'" --format CSVWithNames> /opt/module/data/rs1.csv

更多支持格式参照:

Formats for Input and Output Data | ClickHouse Docs

例子1: 导出students表所有数据

:one:数据准备:

-- 创建学生表
CREATE TABLE students (
    student_id UInt32,
    name String,
    age UInt8,
    grade String
) ENGINE = MergeTree
ORDER BY (student_id);

-- 插入示例数据
INSERT INTO students (student_id, name, age, grade) VALUES
    (1, 'Alice', 20, 'A'),
    (2, 'Bob', 22, 'B'),
    (3, 'Charlie', 21, 'A'),
    (4, 'David', 23, 'C'),
    (5, 'Eva', 19, 'B'),
    (6, 'Frank', 20, 'A'),
    (7, 'Grace', 21, 'B'),
    (8, 'Henry', 22, 'C'),
    (9, 'Ivy', 20, 'A'),
    (10, 'Jack', 21, 'B'),
    (11, 'Kelly', 23, 'C'),
    (12, 'Leo', 19, 'A'),
    (13, 'Mia', 22, 'B'),
    (14, 'Nina', 20, 'A'),
    (15, 'Oscar', 21, 'B'),
    (16, 'Peter', 22, 'C'),
    (17, 'Quinn', 20, 'A'),
    (18, 'Rachel', 21, 'B'),
    (19, 'Sam', 23, 'C'),
    (20, 'Tyler', 19, 'A');
    
-- 查询所有学生数据
SELECT * FROM students;

:two:导出students表数据

csv:

写法1:

# 导出带表头的csv文件
clickhouse-client --port 9001 --password 123456 -q "select * from students;" --format CSVWithNames > /opt/data/stuents.csv
# 导出不带表头的csv文件
clickhouse-client --port 9001 --password 123456 -q "select * from students;" --format CSV > /opt/data/stuents.csv

写法2:

# 导出带表头的csv文件
clickhouse-client --port 9001 --password 123456 -q "select * from students format CSVWithNames"  > /opt/data/stuents.csv
# 导出不带表头的csv文件
clickhouse-client --port 9001 --password 123456 -q "select * from students format CSV"  > /opt/data/stuents.csv

查看导出结果:

image-20240129224402125

5.6 导入数据 :star:

:one:清除students表数据做测试

:warning:生产环境禁用

TRUNCATE TABLE students;

:two: 导入/opt/data/stuents.csv文件

写法1:

# 导入带表头的csv文件
clickhouse-client --password 123456 --port 9001 -d default -q " insert into students FORMAT CSVWithNames"  <  /opt/data/stuents.csv

# 导入不表头的csv文件
clickhouse-client --password 123456 --port 9001 -d default -q " insert into students FORMAT CSV"  <  /opt/data/stuents.csv

写法2:

# 导入带表头的csv文件
clickhouse-client --password 123456 --port 9001 -d default -q " insert into students FORMAT CSVWithNames"  <  /opt/data/stuents.csv

# 导入不表头的csv文件
clickhouse-client --password 123456 --port 9001 -d default -q " insert into students FORMAT CSV"  <  /opt/data/stuents.csv

查看导入结果:

image-20240129224322821

5.6.1 案例需求1:

需求:

将MySQL某张表的数据导入到clickhouse中

数据库ip密码
MySQL(服务器)192.168.45.13123456
clickhouse(本地)192.168.1.10123456

步骤如下:

:star:导入pom.xml包

<!-- MySQL JDBC Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<!-- clickhouse JDBC Driver -->
<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.2.6</version>
</dependency>

:one:导出MySQL 某张表数据,这里以company.staff表做案例

首先查看MySQL表结构,在clickhouse创建一个相同结构的表

mysql> desc company.staff;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(4)       | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | YES  |     | NULL    |                |
| sex   | varchar(255) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

查看该表数据:

mysql> select * from company.staff;
+----+----------+--------+
| id | name     | sex    |
+----+----------+--------+
|  1 | Thomas   | Male   |
|  2 | Catalina | FeMale |
+----+----------+--------+
2 rows in set (0.00 sec)

:two:在clickhouse创建结构相同的表

CREATE TABLE gxjzy (
    id UInt32,
    name String,
    sex String
) ENGINE = MergeTree
ORDER BY id;

:three:编写scala代码

import java.io.PrintWriter
import java.sql.DriverManager

object ExportMySQLToCSV {
  def main(args: Array[String]): Unit = {
    // MySQL connection properties MySQL 连接属性
    val mysqlUrl = "jdbc:mysql://master:3306/company"
    val mysqlUser = "root"
    val mysqlPassword = "123456"

    // JDBC connection  创建 MySQL 连接,使用 DriverManager 获取 MySQL 连接。
    val mysqlConnection = DriverManager.getConnection(mysqlUrl, mysqlUser, mysqlPassword)

    //尝试执行查询并将结果导出到 CSV:
    try {
      // Query to fetch data from MySQL 定义 SQL 查询,选择了 company.staff 表中的所有列。
      val query = "SELECT * FROM company.staff"

      // Create a statement and execute the query 创建 Statement 对象并执行查询
      val statement = mysqlConnection.createStatement()
      val resultSet = statement.executeQuery(query)

      // CSV file path  指定 CSV 文件路径
      val csvFilePath = "/home/gxjzy/桌面/huangjing/company_staff.csv"

      // Write data to CSV file 创建 PrintWriter 对象,用于将数据写入文件。
      val writer = new PrintWriter(csvFilePath)
      while (resultSet.next()) {
        // 根据表列修改此行   遍历查询结果集,将每一行的数据格式化为字符串,写入 CSV 文件。
        val rowData = s"${resultSet.getInt("id")},${resultSet.getString("name")},${resultSet.getString("sex")}"
        writer.println(rowData)
      }
      writer.close()

      println(s"Data exported to CSV file: $csvFilePath")
    } catch {
      case e: Exception => e.printStackTrace()
    } finally {
      // Close the MySQL connection 无论是否发生异常,都会在最终块中尝试关闭 MySQL 连接
      if (mysqlConnection != null) {
        mysqlConnection.close()
      }
    }
  }
}

运行结果:

image-20240130105718242

:four:开始导入数据到clickhouse

(因为使用代码导出数据时,没有设置csv的表头,所以这里使用无表头的方法导入csv文件)

# 1.将成功导出的csv文件上传至服务器中
gxjzy@gxjzy:~/桌面/huangjing$ sudo docker cp company_staff.csv dj-master:/root/data
# 2.导入不表头的csv文件
[root@master data]# clickhouse-client --port 9001  --password 123456 -d default -q " insert into gxjzy FORMAT CSV" < company_staff.csv

登录clickhouse数据库查看数据是否导入成功:

master :) select * from gxjzy;

SELECT *
FROM gxjzy

Query id: e0e4b00c-b48d-4ce4-ac16-7aec5bc26df4

┌─id─┬─name─────┬─sex────┐
│  1 │ Thomas   │ Male   │
│  2 │ Catalina │ FeMale │
└────┴──────────┴────────┘

2 rows in set. Elapsed: 0.003 sec.

成功如图:image-20240130110534109

5.6.2 比赛需求模拟:star:

需求:

将采集到fink采集到的数据 shtd_result.order_info 导入到clickhouse中

步骤如下:

:star:导入pom.xml包

<!-- MySQL JDBC Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<!-- clickhouse JDBC Driver -->
<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.2.6</version>
</dependency>

:one:首先查看shtd_result.order_info表结构,在clickhouse创建一个相同结构的表

mysql> desc shtd_result.order_info;
+--------------------+--------------+------+-----+---------+-------+
| Field              | Type         | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+-------+
| id                 | varchar(255) | YES  |     | NULL    |       |
| consignee          | varchar(255) | YES  |     | NULL    |       |
| consignee_tel      | varchar(255) | YES  |     | NULL    |       |
| final_total_amount | double(10,2) | YES  |     | NULL    |       |
| feight_fee         | double(10,2) | YES  |     | NULL    |       |
+--------------------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

:two:在clickhouse创建结构相同的表

#方式1
CREATE TABLE order_info (
    id String,
    consignee String,
    consignee_tel String,
    final_total_amount Float64,
    feight_fee Float64
) ENGINE = MergeTree() ORDER BY id;

#方式2:
CREATE TABLE order_info (
    id String,
    consignee String,
    consignee_tel String,
    final_total_amount String,
    feight_fee String
) ENGINE = MergeTree() ORDER BY id;

注意:

ClickHouse 中的数据类型与 MySQL 有些许不同,因此在创建表时需要确保字段类型匹配。在这个例子中,varchar(255) 在 ClickHouse 中映射为 String,而 double(10,2) 映射为 Float64

:three:编写scala代码

import java.io.PrintWriter
import java.sql.DriverManager

object MySQLToClickHouse {
  def main(args: Array[String]): Unit = {
    // MySQL connection properties MySQL 连接属性
    val mysqlUrl = "jdbc:mysql://192.168.45.13:3306/shtd_result"
    val mysqlUser = "root"
    val mysqlPassword = "123456"

    // JDBC connection  创建 MySQL 连接,使用 DriverManager 获取 MySQL 连接。
    val mysqlConnection = DriverManager.getConnection(mysqlUrl, mysqlUser, mysqlPassword)

    //尝试执行查询并将结果导出到 CSV:
    try {
      // Query to fetch data from MySQL 定义 SQL 查询,选择了 company.staff 表中的所有列。
      val query = "SELECT * FROM shtd_result.order_info"

      // Create a statement and execute the query 创建 Statement 对象并执行查询
      val statement = mysqlConnection.createStatement()
      val resultSet = statement.executeQuery(query)

      // CSV file path  指定 CSV 文件路径
      val csvFilePath = "/home/gxjzy/桌面/shtd_result.order_info.csv"

      // Write data to CSV file 创建 PrintWriter 对象,用于将数据写入文件。
      val writer = new PrintWriter(csvFilePath)
      while (resultSet.next()) {
        // 根据表列修改此行   遍历查询结果集,将每一行的数据格式化为字符串,写入 CSV 文件。
        // val rowData = s"${resultSet.getInt("id")},${resultSet.getString("name")},${resultSet.getString("sex")}"
        val rowData = s"${resultSet.getString("id")},${resultSet.getString("consignee")},${resultSet.getString("consignee_tel")},${resultSet.getString("final_total_amount")},${resultSet.getString("feight_fee")}"
        writer.println(rowData)
      }
      writer.close()

      println(s"Data exported to CSV file: $csvFilePath")
    } catch {
      case e: Exception => e.printStackTrace()
    } finally {
      // Close the MySQL connection 无论是否发生异常,都会在最终块中尝试关闭 MySQL 连接
      if (mysqlConnection != null) {
        mysqlConnection.close()
      }
    }
  }
}

运行结果:

image-20240130151735218

:four:开始导入数据到clickhouse

(因为使用代码导出数据时,没有设置csv的表头,所以这里使用无表头的方法导入csv文件)

# 1.将成功导出的csv文件上传至服务器中
gxjzy@gxjzy:~/桌面/sql_CSV$ sudo docker cp shtd_result.order_info.csv dj-master:/root/data
# 2.导入不表头的csv文件
[root@master data]# clickhouse-client --port 9001  --password 123456 -d default -q " insert into order_info FORMAT CSV" < shtd_result.order_info.csv

登录clickhouse数据库查看数据是否导入成功:

master :) select * from order_info;

SELECT *
FROM order_info

Query id: b816d87c-478a-4f58-8ae4-9bec876dfa34

┌─id───┬─consignee─┬─consignee_tel─┬─final_total_amount─┬─feight_fee─┐
│ 3570 │ 狄兴良    │ 13948905139451.007.00       │
│ 3571 │ 岑坚      │ 13220439586682.0016.00      │
│ 3572 │ 贺兰凤    │ 13022309909285.0010.00      │
│ 3573 │ 邬艺咏    │ 137529526126673.0013.00      │
│ 3574 │ 宋春      │ 1390151134314691.008.00       │
│ 3575 │ 史晨辰    │ 131461906554339.0013.00      │
│ 3576 │ 邬素      │ 136617343724270.0012.00      │
│ 3577 │ 姜奇      │ 13060645675468.009.00       │
│ 3578 │ 张娣      │ 1358681384326718.0018.00      │
└──────┴───────────┴───────────────┴────────────────────┴────────────┘

9 rows in set. Elapsed: 0.005 sec. 

结果:image-20240130152811062

完成

6. clickhouse的使用

6.1 在ClickHouse中创建表

与大多数数据库管理系统一样,ClickHouse 按逻辑将表分组到数据库中。使用CREATE DATABASE命令在ClickHouse中创建新数据库:

CREATE DATABASE IF NOT EXISTS helloworld

image-20230825140404003

同样,使用CREATE TABLE来定义一个新表。(如果不指定数据库名称,则该表将在 default数据库中。)以下名为的表my_first_table在数据库中helloworld

CREATE TABLE helloworld.my_first_table(user_id UInt32,message String,timestamp DateTime,metric Float32) ENGINE = MergeTree() PRIMARY KEY (user_id, timestamp)

image-20230825140621498

在上面的示例中,my_first_table是一个MergeTree包含四列的表:

1、user_id: 32 位无符号整数

2、message:一种String数据类型,它替换其他数据库系统中的VARCHARBLOBCLOB等类型

3、timestamp: 一个DateTime值,代表时间的一个瞬间

4、metric:32位浮点数

表结构:image-20230825140809783

笔记

表引擎确定:

1、数据的存储方式和位置

2、支持哪些查询

3、数据是否被复制

有许多引擎可供选择,但对于单节点 ClickHouse 服务器上的简单表,MergeTree可能是您的选择。

主键

在继续之前,了解主键在 ClickHouse 中的工作原理非常重要(主键的实现可能看起来出乎意料!):

  • ClickHouse 中的主键对于表中的每一行来说不是唯一的

ClickHouse 表的主键决定了数据写入磁盘时的排序方式。每 8,192 行或 10MB 数据(称为索引粒度)在主键索引文件中创建一个条目。这种粒度概念创建了一个可以轻松放入内存的稀疏索引SELECT,并且颗粒表示查询期间处理的最小列数据量的条带。

主键可以使用PRIMARY KEY参数定义。如果定义表时未PRIMARY KEY指定,则键将成为ORDER BY子句中指定的元组。如果您同时指定 aPRIMARY KEY和 an ORDER BY,则主键必须是排序顺序的子集。

主键也是排序键,它是 的元组(user_id, timestamp)。因此,每个列文件中存储的数据将按user_id,然后排序timestamp

#创建表

data :) create TABLE my_first_table ( user_id UInt32,message String,timestamp DateTime,metric Float32)ENGINE = MergeTree PRIMARY KEY (user_id, timestamp)

结果:image-20230825093650584

6.2 将数据插入 ClickHouse:)

huangjing :)  INSERT INTO my_first_table (user_id, message, timestamp, metric) VALUES (101, 'Hello, ClickHouse!',              now(),-1.0),(102, 'Insert a lot of rows per batch',            yesterday(), 1.41421 ),(102, 'Sort your data based on your commonly-used queries', today(),2.718),(101, 'Granules are the smallest chunks of data read',now() + 5,3.14159 )

结果:image-20230825093935032

#查看插入的数据

data :) select * from my_first_table

结果:image-20230825094025963

6.3 ClickHouse 中的 SELECT 查询

SELECTClickHouse 是一个 SQL 数据库,您可以通过编写您已经熟悉的相同类型的查询来查询数据。例如:

SELECT * FROM helloworld.my_first_table ORDER BY timestamp

响应以良好的表格格式返回:

image-20230825142026059

添加一个子句来指定ClickHouse 支持的多种输出格式FORMAT之一:

SELECT * FROM helloworld.my_first_table ORDER BY timestamp FORMAT TabSeparated

在上面的查询中,输出以制表符分隔的形式返回:

image-20230825142212843

6.4 更新数据

使用以下ALTER TABLE...UPDATE命令更新表中的行:

ALTER TABLE [<database>.]<table> UPDATE <column> = <expression> WHERE <filter_expr>

<expression>是满足 的列的新值<filter_expr>。必须<expression>与列具有相同的数据类型,或者可以使用CAST运算符转换为相同的数据类型。应该为每行数据<filter_expr>返回一个UInt8(零或非零)值。多个UPDATE <column>语句可以组合在一个ALTER TABLE命令中,并用逗号分隔。

例子

  1. 更新数据,将userid为101的内容改成huangjing

    ALTER TABLE helloworld.my_first_table UPDATE message = 'huangjing' WHERE user_id='101'

    结果:image-20230825143022048

  2. 更新排序键列

    ALTER TABLE helloworld.my_first_table UPDATE user_id='110' WHERE message = 'huangjing'

    结果(发现并没有更新):image-20230825143322671

无法更新属于主键或排序键的列。

6.5 删除数据

使用ALTER TABLE命令删除行:

ALTER TABLE [<database>.]<table> DELETE WHERE <filter_expr>

应该<filter_expr>为每行数据返回一个 UInt8 值。

例子

  1. 删除metric中的-1的值:

    ALTER TABLE helloworld.my_first_table DELETE WHERE metric='-1'

    结果:image-20230825143613961

要删除表中的所有数据,使用commandTRUNCATE TABLE [<database].]<table>命令效率更高。这个命令也可以执行ON CLUSTER

查看DELETE声明文档页面了解更多详细信息。


文章作者: 無以菱
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 無以菱 !
评论
  目录