首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ClickHouse是如何批量写入的?

ClickHouse是如何批量写入的?

作者头像
陆道峰
发布于 2021-01-06 02:17:20
发布于 2021-01-06 02:17:20
7.9K00
代码可运行
举报
运行总次数:0
代码可运行

简介

批量写入又称为bulk write,对于单表插入多条数据的场景,可以减少插入请求数量,提高吞吐量和效率。clickhouse官方Golang驱动clickhouse-go[1]支持该关键特性,但是文档的介绍不是很详细,只有一句:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Bulk write support : begin->prepare->(in loop exec)->commit

并没有详细介绍用法和原理,笔者在开发业务时使用的库是sqlx[2],sql也支持clickhouse-go驱动。参考了官方样例代码[3]:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...
tx, err := connect.Begin()
checkErr(err)
stmt, err := tx.Prepare("INSERT INTO example (country_code, os_id, browser_id, categories, action_day, action_time) VALUES (?, ?, ?, ?, ?, ?)")
checkErr(err)

for i := 0; i < 100; i++ {
 if _, err := stmt.Exec(
  "RU",
  10+i,
  100+i,
  []int16{1, 2, 3},
  time.Now(),
  time.Now(),
 ); err != nil {
  log.Fatal(err)
 }
}
...

我写的bulk write类似上面的代码,但是提交给同事review时,他提出了疑问:stmt.Exec是每次执行都发送写请求到数据库?这个问题其实我不敢肯定,官方文档也说得不明确。考虑到严谨性,让自己的PR更有说服力,自己去翻看了相关源代码。

这里需要指出,如果利用编辑器里的代码跳转功能会跳到database/sql库中的Exec函数实现,实际上我们要看的代码是clickhouse-go中的实现,至于编辑器跳转到database/sql中的原因,书写此文时笔者也没弄清楚,先挖个坑吧

核心实现

stmt.Exec的核心代码如下[4]:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (stmt *stmt) execContext(ctx context.Context, args []driver.Value) (driver.Result, error) {
 if stmt.isInsert {
  stmt.counter++
  if err := stmt.ch.block.AppendRow(args); err != nil {
   return nil, err
  }
  if (stmt.counter % stmt.ch.blockSize) == 0 {
   stmt.ch.logf("[exec] flush block")
   if err := stmt.ch.writeBlock(stmt.ch.block); err != nil {
    return nil, err
   }
   if err := stmt.ch.encoder.Flush(); err != nil {
    return nil, err
   }
  }
  return emptyResult, nil
 }
 if err := stmt.ch.sendQuery(stmt.bind(convertOldArgs(args))); err != nil {
  return nil, err
 }
 if err := stmt.ch.process(); err != nil {
  return nil, err
 }
 return emptyResult, nil
}

上面的代码不多,非常清晰,当执行Exec时,stmt.ch.block.AppendRow(args)会先把sql参数附加到本地缓存block中,然后(stmt.counter % stmt.ch.blockSize)判断本地缓存大小是否到达阈值,到达则执行Flush(),将数据写入远端。综上,clickhouse-go中的核心实现逻辑是:

  1. 底层维护一个缓存block,同时设置block_size控制缓存大小
  2. 执行stmt.Exec时,不会直接写入远程ClickHouse中,而是将插入参数Append到block中
  3. 每次Append后,判断block的size和block_size的关系,如果正好整除,则刷新block(即写入clickhouse)

因此block_size这个参数很重要,它表示本地缓存的上限,如果很大的话,程序会占用一些内存。笔者起初设置为100000,在调试日志中看不到stmt.ch.logf("[exec] flush block")打印的log,设置小后就看到下面的输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...
[clickhouse][connect=1][begin] tx=false, data=false
[clickhouse][connect=1][prepare]
[clickhouse][connect=1][read meta] <- data: packet=1, columns=6, rows=0
[clickhouse][connect=1][exec] flush block
[clickhouse][connect=1][exec] flush block
....

总结

很多数据库驱动都支持bulk write特性,clickhouse-go这个驱动也不例外,但是它的文档写得不是很详细,只是在文档中指明要放在begin/commit中做。再加上clickhouse不支持事务,begin/commit这种写法会让人困惑。

本文通过分析clickhouse-go的源代码,了解bulk write的执行过程,帮助大家梳理其具体实现。

参考资料

[1]

clickhouse-go: https://github.com/ClickHouse/clickhouse-go

[2]

sqlx: https://github.com/jmoiron/sqlx

[3]

官方样例代码: https://github.com/ClickHouse/clickhouse-go/blob/master/examples/sqlx.go#L35-L51

[4]

核心代码如下: https://github.com/clickhouse/clickhouse-go/blob/master/stmt.go#L44-L68

[5]

INSERT INTO Statement: https://clickhouse.tech/docs/en/sql-reference/statements/insert-into/

[6]

go-clickhouse-batchinsert: https://github.com/MaruHyl/go-clickhouse-batchinsert/blob/master/batch.go#L349-L354

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器学习与系统 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
clickhouse的go客户端实现插入分布式clickhouse集群方式
之前的文章有说clickhouse的分布式集群做数据插入有两种方式,一种是随机选个节点插入数据,另外是直接插入分布式表。如果我们直接插入分布式表,分布式表会经历过把数据同步到其他节点的过程,会造成批量插入的时候性能出现瓶颈。我们一般实现都通过随机选节点插入。
公众号-利志分享
2022/04/25
3.3K0
GO web 开发 实战三,数据库预处理
了解什么是预处理,我们可以来对比一下,普通的 sql 语句执行过程和 预处理的执行过程
阿兵云原生
2023/02/16
4140
Go语言入门(十) Mysql与Redis操作
Mysql与Redis操作 Mysql开发 安装mysql,创建test库 创建表 mysql> CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT '', `age` int(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; mysql> in
alexhuiwang
2020/09/24
1K0
Go语言读写数据库
我用的驱动是:https://github.com/Go-SQL-Driver/MySQL 理由跟 https://github.com/astaxie/build-web-application-with-golang/blob/master/05.2.md 的一样。 比较新,更新维护的比较好; 完全支持database/sql接口 支持keepalive,保持长连接。 安装 $ go get github.com/go-sql-driver/mysql 数据库连接语句 import "database/
李海彬
2018/03/22
1.1K0
Go起步及创建WEB项目(iris)
假如我们想要处理 http://localhost:8080/static/**/* 的请求都访问项目目录下的 ./static 文件夹,那么按照如下配置就可以了
码客说
2021/04/19
9950
go_databasetest
go_databasetest go语言的数据库测试: go get github.com/Go-SQL-Driver/MySQL package main import ( _"github.com/Go-SQL-Driver/MySQL" "database/sql" "fmt" ) func main() { //链接数据库 db, err := sql.Open("mysql", "root:1111@tcp(127.0.0.1:3306)/test
机器学习和大数据挖掘
2019/07/02
2640
如何使用mysql数据库【Golang 入门系列十】
之前,已经讲过一些Golang的基础的东西,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.html,
章为忠学架构
2019/08/09
1.1K0
Go起步及创建WEB项目
Go语言在多核并发上拥有原生的设计优势,Go语言从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验。
码客说
2021/04/14
9650
windows版本go使用sqlite3
windows版本的go很简单,直接从go官方网站下载一个go的压缩包,解压即可。
呱牛笔记
2024/08/06
3140
go爬虫项目
github.com/PuerkitoBio/goquery包内内置了Find函数,”Find 获取当前匹配元素集中每个元素的后代,由选择器过滤。,它返回一个包含这些匹配元素的新选择对象。”
h3110_w0r1d
2024/02/19
2030
go爬虫项目
Golang如何优雅连接MYSQL数据库?
Go原生就支持连接数据库,所以在使用 Golang 开发时,当需要数据库交互时,即可使用database/sql包。
JavaEdge
2021/02/23
13.3K0
Golang如何优雅连接MYSQL数据库?
18 . Go之操作Mysql和sqlx使用
bindvars的一个常见误解是,它们用来在sql语句中插入值。它们其实仅用于参数化,不允许更改SQL语句的结构。例如,使用bindvars尝试参数化列或表名将不起作用:
iginkgo18
2022/05/09
1.9K0
Go-MySQL-Driver,让Go语言拥抱MySQL
闫同学
2023/10/14
6590
手把手带你从0搭建一个Golang ORM框架(上)!
导语 | 当我深入的学习和了解了GORM、XORM后,我觉得它们不够简洁和优雅,有些笨重,有很大的学习成本。本着学习和探索的目的,于是我自己实现了一个简单且优雅的go语言版本的ORM。本文主要从基础原理开始介绍,到一步一步步骤实现,继而完成整个简单且优雅的MySQL ORM。 一、前置学习 (一)为什么要用ORM 我们在使用各种语言去做需求的时候,不管是PHP,Golang还是C++等语言,应该都接触使用过用ORM去链接数据库,这些ORM有些是项目组自己整合实现的,也有些是用的开源的组件。特别在1个
腾讯云开发者
2021/12/28
7990
Go-数据库操作(五)
在Go语言中,要使用事务,需要使用DB.Begin()方法创建一个事务,并使用Tx.Commit()方法提交事务。如果在事务过程中出现错误,需要使用Tx.Rollback()方法回滚事务。以下是一个使用事务插入多条数据的示例::
堕落飞鸟
2023/04/23
2100
使用Go语言操作MySQL数据库的思路与步骤
最近在做注册登录服务时,学习用Go语言操作MySQL数据库实现用户数据的增删改查,现将个人学习心得总结如下,另外附有代码仓库地址,欢迎各位有兴趣的fork。
Zoctopus
2018/08/28
9030
使用Go语言操作MySQL数据库的思路与步骤
2.Go语言项目操作MySQL数据库实践
快速了解 MySQL 数据库 MySQL 是目前主流关系型的数据库,它的胞胎兄弟 MariaDB (MySQL 的一个分支),除此之外使用最多的就是 Oracle 和 PostgreSQL 数据库。
全栈工程师修炼指南
2022/09/29
6.9K0
2.Go语言项目操作MySQL数据库实践
Go 语言操作 MySQL 之 SQLX 包
友情提示:此篇文章大约需要阅读 14分钟5秒,不足之处请多指教,感谢你的阅读。 、
Meng小羽
2020/07/08
1.9K0
golang sql数据库已关闭,数据库too many connections
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
hotqin888
2019/10/22
2.1K0
手把手带你从0搭建一个Golang ORM框架(全)!
导语 | 当我深入的学习和了解了GORM、XORM后,我觉得它们不够简洁和优雅,有些笨重,有很大的学习成本。本着学习和探索的目的,于是我自己实现了一个简单且优雅的go语言版本的ORM。本文主要从基础原理开始介绍,到一步一步步骤实现,继而完成整个简单且优雅的MySQL ORM。 一、前置学习 (一)为什么要用ORM 我们在使用各种语言去做需求的时候,不管是PHP,Golang还是C++等语言,应该都接触使用过用ORM去链接数据库,这些ORM有些是项目组自己整合实现的,也有些是用的开源的组件。特别在1个
腾讯云开发者
2021/12/30
1.4K0
相关推荐
clickhouse的go客户端实现插入分布式clickhouse集群方式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档