跳到主要内容
跳到主要内容

ClickHouse JS

用于连接 ClickHouse 的官方 JS 客户端。该客户端使用 TypeScript 编写,并为客户端公共 API 提供类型定义。

它零依赖,针对最佳性能进行了优化,并经过各种 ClickHouse 版本和配置(本地单节点、本地集群和 ClickHouse Cloud)的测试。

该客户端有两个不同的版本可用于不同的环境

  • @clickhouse/client - 仅限 Node.js
  • @clickhouse/client-web - 浏览器 (Chrome/Firefox)、Cloudflare workers

当使用 TypeScript 时,请确保它至少是 4.5 版本,它启用了内联导入和导出语法

客户端源代码可在 ClickHouse-JS GitHub 仓库中找到。

环境要求 (Node.js)

Node.js 必须在环境中可用才能运行客户端。该客户端与所有维护的 Node.js 版本兼容。

一旦 Node.js 版本接近生命周期结束,客户端将停止对其的支持,因为它被认为是过时且不安全的。

当前 Node.js 版本支持

Node.js 版本是否支持?
22.x
20.x
18.x
16.x尽力支持

环境要求 (Web)

Web 版本的客户端已通过最新的 Chrome/Firefox 浏览器官方测试,并且可以用作依赖项,例如在 React/Vue/Angular 应用程序或 Cloudflare workers 中。

安装

要安装最新的稳定 Node.js 客户端版本,请运行

npm i @clickhouse/client

Web 版本安装

npm i @clickhouse/client-web

与 ClickHouse 的兼容性

客户端版本ClickHouse
1.8.023.3+

客户端很可能也适用于旧版本;但是,这是尽力而为的支持,并且不保证。如果您的 ClickHouse 版本早于 23.3,请参阅ClickHouse 安全策略并考虑升级。

示例

我们的目标是通过客户端仓库中的示例来涵盖客户端使用的各种场景。

概述可在示例 README 中找到。

如果示例或以下文档中有任何不清楚或遗漏的内容,请随时联系我们

客户端 API

大多数示例应与客户端的 Node.js 和 Web 版本兼容,除非另有明确说明。

创建客户端实例

您可以使用 createClient 工厂根据需要创建任意数量的客户端实例

import { createClient } from '@clickhouse/client' // or '@clickhouse/client-web'

const client = createClient({
/* configuration */
})

如果您的环境不支持 ESM 模块,则可以使用 CJS 语法代替

const { createClient } = require('@clickhouse/client');

const client = createClient({
/* configuration */
})

客户端实例可以在实例化期间进行预配置

配置

创建客户端实例时,可以调整以下连接设置

设置描述默认值另请参阅
url?: stringClickHouse 实例 URL。https://:8123URL 配置文档
pathname?: string可选路径名,在客户端解析 ClickHouse URL 后添加到其中。''带有路径名的代理文档
request_timeout?: number请求超时时间,以毫秒为单位。30_000-
compression?: { **response**?: boolean; **request**?: boolean }启用压缩。-压缩文档
username?: string代表其发出请求的用户的名称。default-
password?: string用户密码。''-
application?: string使用 Node.js 客户端的应用程序的名称。clickhouse-js-
database?: string要使用的数据库名称。default-
clickhouse_settings?: ClickHouseSettings要应用于所有请求的 ClickHouse 设置。{}-
log?: { **LoggerClass**?: Logger, **level**?: ClickHouseLogLevel }内部客户端日志配置。-日志记录文档
session_id?: string可选的 ClickHouse 会话 ID,随每个请求一起发送。--
keep_alive?: { **enabled**?: boolean }在 Node.js 和 Web 版本中默认启用。--
http_headers?: Record<string, string>传出 ClickHouse 请求的其他 HTTP 标头。-带有身份验证的反向代理文档
roles?: string | string[]要附加到传出请求的 ClickHouse 角色名称。-将角色与 HTTP 接口一起使用

Node.js 特定的配置参数

设置描述默认值另请参阅
max_open_connections?: number每个主机允许的最大连接套接字数。10-
tls?: { **ca_cert**: Buffer, **cert**?: Buffer, **key**?: Buffer }配置 TLS 证书。-TLS 文档
keep_alive?: { **enabled**?: boolean, **idle_socket_ttl**?: number }--Keep Alive 文档
http_agent?: http.Agent | https.Agent
实验性功能。 了解更多。
客户端的自定义 HTTP 代理。-HTTP 代理文档
set_basic_auth_header?: boolean
实验性功能。 了解更多。
使用基本身份验证凭据设置 Authorization 标头。trueHTTP 代理文档中的此设置用法

URL 配置

信息

URL 配置将始终覆盖硬编码的值,在这种情况下将记录警告。

可以使用 URL 配置大多数客户端实例参数。URL 格式为 http[s]://[username:password@]hostname:port[/database][?param1=value1&param2=value2]。几乎在所有情况下,特定参数的名称都反映了其在配置选项接口中的路径,但有少数例外。支持以下参数

参数类型
pathname任意字符串。
application_id任意字符串。
session_id任意字符串。
request_timeout非负数。
max_open_connections非负数,大于零。
compression_request布尔值。请参见下文 (1)
compression_response布尔值。
log_level允许的值:OFFTRACEDEBUGINFOWARNERROR
keep_alive_enabled布尔值。
clickhouse_setting_*ch_*请参见下文 (2)
http_header_*请参见下文 (3)
(仅限 Node.js) keep_alive_idle_socket_ttl非负数。
  • (1) 对于布尔值,有效值为 true/1false/0
  • (2) 任何以 clickhouse_setting_ch_ 为前缀的参数都将删除此前缀,并将其余部分添加到客户端的 clickhouse_settings。例如,?ch_async_insert=1&ch_wait_for_async_insert=1 将与以下内容相同
createClient({
clickhouse_settings: {
async_insert: 1,
wait_for_async_insert: 1,
},
})

注意:clickhouse_settings 的布尔值应在 URL 中作为 1/0 传递。

  • (3) 与 (2) 类似,但适用于 http_header 配置。例如,?http_header_x-clickhouse-auth=foobar 将等效于
createClient({
http_headers: {
'x-clickhouse-auth': 'foobar',
},
})

连接

收集您的连接详细信息

要通过 HTTP(S) 连接到 ClickHouse,您需要以下信息

  • HOST 和 PORT:通常,使用 TLS 时端口为 8443,不使用 TLS 时端口为 8123。

  • 数据库名称:开箱即用,有一个名为 default 的数据库,请使用您要连接的数据库的名称。

  • 用户名和密码:开箱即用,用户名为 default。请使用适合您用例的用户名。

您的 ClickHouse Cloud 服务的详细信息可在 ClickHouse Cloud 控制台中找到。选择您要连接的服务,然后单击连接

ClickHouse Cloud service connect button

选择 HTTPS,详细信息将在 curl 命令示例中提供。

ClickHouse Cloud HTTPS connection details

如果您使用的是自管理的 ClickHouse,则连接详细信息由您的 ClickHouse 管理员设置。

连接概述

客户端通过 HTTP(s) 协议实现连接。RowBinary 支持正在进行中,请参阅相关问题

以下示例演示了如何设置与 ClickHouse Cloud 的连接。它假设 url(包括协议和端口)和 password 值通过环境变量指定,并使用 default 用户。

示例: 使用环境变量配置创建 Node.js 客户端实例。

import { createClient } from '@clickhouse/client'

const client = createClient({
url: process.env.CLICKHOUSE_HOST ?? 'https://:8123',
username: process.env.CLICKHOUSE_USER ?? 'default',
password: process.env.CLICKHOUSE_PASSWORD ?? '',
})

客户端仓库包含多个使用环境变量的示例,例如在 ClickHouse Cloud 中创建表使用异步插入,以及其他许多示例。

连接池(仅限 Node.js)

为了避免每次请求都建立连接的开销,客户端创建了一个 ClickHouse 连接池以重用,利用 Keep-Alive 机制。默认情况下,Keep-Alive 已启用,连接池大小设置为 10,但您可以使用 max_open_connections 配置选项更改它。

除非用户设置 max_open_connections: 1,否则不保证池中的同一连接将用于后续查询。这种情况很少需要,但对于用户使用临时表的情况可能需要。

另请参阅:Keep-Alive 配置

查询 ID

每个发送查询或语句的方法 (commandexecinsertselect) 都将在结果中提供 query_id。此唯一标识符由客户端为每个查询分配,如果服务器配置中启用了 system.query_log,则可能有助于从中获取数据,或取消长时间运行的查询(请参阅示例)。如有必要,用户可以在 command/query/exec/insert 方法参数中覆盖 query_id

提示

如果您要覆盖 query_id 参数,则需要确保每次调用它的唯一性。随机 UUID 是一个不错的选择。

所有客户端方法的基本参数

有几个参数可以应用于所有客户端方法(query/command/insert/exec)。

interface BaseQueryParams {
// ClickHouse settings that can be applied on query level.
clickhouse_settings?: ClickHouseSettings
// Parameters for query binding.
query_params?: Record<string, unknown>
// AbortSignal instance to cancel a query in progress.
abort_signal?: AbortSignal
// query_id override; if not specified, a random identifier will be generated automatically.
query_id?: string
// session_id override; if not specified, the session id will be taken from the client configuration.
session_id?: string
// credentials override; if not specified, the client's credentials will be used.
auth?: { username: string, password: string }
// A specific list of roles to use for this query. Overrides the roles set in the client configuration.
role?: string | Array<string>
}

Query 方法

这用于大多数可以有响应的语句,例如 SELECT,或用于发送 DDL,例如 CREATE TABLE,应等待其完成。返回的结果集预计将在应用程序中使用。

注意

有一个专用的方法 insert 用于数据插入,以及 command 用于 DDL。

interface QueryParams extends BaseQueryParams {
// Query to execute that might return some data.
query: string
// Format of the resulting dataset. Default: JSON.
format?: DataFormat
}

interface ClickHouseClient {
query(params: QueryParams): Promise<ResultSet>
}

另请参阅:所有客户端方法的基本参数

提示

请勿在 query 中指定 FORMAT 子句,请改用 format 参数。

结果集和行抽象

ResultSet 为应用程序中的数据处理提供了几种便捷方法。

Node.js ResultSet 实现使用底层的 Stream.Readable,而 Web 版本使用 Web API ReadableStream

您可以通过在 ResultSet 上调用 textjson 方法来使用 ResultSet,并将查询返回的整套行加载到内存中。

您应该尽快开始使用 ResultSet,因为它保持响应流打开,从而使底层连接保持繁忙。客户端不会缓冲传入数据,以避免应用程序可能过度使用内存。

或者,如果数据太大而无法一次装入内存,您可以调用 stream 方法,并在流模式下处理数据。每个响应块都将转换为相对较小的行数组(此数组的大小取决于客户端从服务器接收的特定块的大小,因为它可能会有所不同,以及单个行的大小),一次一个块。

请参阅支持的数据格式列表,以确定哪种格式最适合您的情况下的流式传输。例如,如果您要流式传输 JSON 对象,则可以选择 JSONEachRow,每行将被解析为 JS 对象,或者,也许更紧凑的 JSONCompactColumns 格式,这将导致每行成为一个紧凑的值数组。另请参阅:流式传输文件

信息

如果 ResultSet 或其流未完全使用,则将在 request_timeout 不活动期后销毁它。

interface BaseResultSet<Stream> {
// See "Query ID" section above
query_id: string

// Consume the entire stream and get the contents as a string
// Can be used with any DataFormat
// Should be called only once
text(): Promise<string>

// Consume the entire stream and parse the contents as a JS object
// Can be used only with JSON formats
// Should be called only once
json<T>(): Promise<T>

// Returns a readable stream for responses that can be streamed
// Every iteration over the stream provides an array of Row[] in the selected DataFormat
// Should be called only once
stream(): Stream
}

interface Row {
// Get the content of the row as a plain string
text: string

// Parse the content of the row as a JS object
json<T>(): T
}

示例: (Node.js/Web) 以 JSONEachRow 格式查询结果数据集,使用整个流并将内容解析为 JS 对象。源代码

const resultSet = await client.query({
query: 'SELECT * FROM my_table',
format: 'JSONEachRow',
})
const dataset = await resultSet.json() // or `row.text` to avoid parsing JSON

示例: (仅限 Node.js) 使用经典 on('data') 方法以 JSONEachRow 格式流式传输查询结果。这与 for await const 语法可互换。源代码

const rows = await client.query({
query: 'SELECT number FROM system.numbers_mt LIMIT 5',
format: 'JSONEachRow', // or JSONCompactEachRow, JSONStringsEachRow, etc.
})
const stream = rows.stream()
stream.on('data', (rows: Row[]) => {
rows.forEach((row: Row) => {
console.log(row.json()) // or `row.text` to avoid parsing JSON
})
})
await new Promise((resolve, reject) => {
stream.on('end', () => {
console.log('Completed!')
resolve(0)
})
stream.on('error', reject)
})

示例: (仅限 Node.js) 使用经典 on('data') 方法以 CSV 格式流式传输查询结果。这与 for await const 语法可互换。源代码

const resultSet = await client.query({
query: 'SELECT number FROM system.numbers_mt LIMIT 5',
format: 'CSV', // or TabSeparated, CustomSeparated, etc.
})
const stream = resultSet.stream()
stream.on('data', (rows: Row[]) => {
rows.forEach((row: Row) => {
console.log(row.text)
})
})
await new Promise((resolve, reject) => {
stream.on('end', () => {
console.log('Completed!')
resolve(0)
})
stream.on('error', reject)
})

示例: (仅限 Node.js) 以 JSONEachRow 格式流式传输查询结果作为 JS 对象,使用 for await const 语法使用。这与经典 on('data') 方法可互换。源代码

const resultSet = await client.query({
query: 'SELECT number FROM system.numbers LIMIT 10',
format: 'JSONEachRow', // or JSONCompactEachRow, JSONStringsEachRow, etc.
})
for await (const rows of resultSet.stream()) {
rows.forEach(row => {
console.log(row.json())
})
}
注意

for await const 语法比 on('data') 方法的代码少一些,但可能会对性能产生负面影响。有关更多详细信息,请参阅 Node.js 仓库中的此问题

示例: (仅限 Web) 迭代对象的 ReadableStream

const resultSet = await client.query({
query: 'SELECT * FROM system.numbers LIMIT 10',
format: 'JSONEachRow'
})

const reader = resultSet.stream().getReader()
while (true) {
const { done, value: rows } = await reader.read()
if (done) { break }
rows.forEach(row => {
console.log(row.json())
})
}

Insert 方法

这是数据插入的主要方法。

export interface InsertResult {
query_id: string
executed: boolean
}

interface ClickHouseClient {
insert(params: InsertParams): Promise<InsertResult>
}

返回类型最小,因为我们不希望从服务器返回任何数据,并且立即耗尽响应流。

如果为空数组提供给 insert 方法,则 insert 语句将不会发送到服务器;相反,该方法将立即解析为 { query_id: '...', executed: false }。如果在这种情况下方法参数中未提供 query_id,则结果中将为空字符串,因为返回客户端生成的随机 UUID 可能会造成混淆,因为具有此类 query_id 的查询将不存在于 system.query_log 表中。

如果 insert 语句已发送到服务器,则 executed 标志将为 true

Node.js 中的 Insert 方法和流式传输

它可以与 Stream.Readable 或纯 Array<T> 一起使用,具体取决于为 insert 方法指定的数据格式。另请参阅有关文件流式传输的此部分。

Insert 方法应该等待完成;但是,可以指定输入流并在稍后等待 insert 操作,仅当流完成时(这也将解析 insert promise)。这对于事件侦听器和类似场景可能很有用,但错误处理对于客户端来说可能很复杂,并且存在许多边缘情况。相反,请考虑使用 异步插入,如此示例所示。

提示

如果您有自定义的 INSERT 语句,难以使用此方法建模,请考虑使用 command 方法

您可以在 INSERT INTO ... VALUESINSERT INTO ... SELECT 示例中查看其用法。

interface InsertParams<T> extends BaseQueryParams {
// Table name to insert the data into
table: string
// A dataset to insert.
values: ReadonlyArray<T> | Stream.Readable
// Format of the dataset to insert.
format?: DataFormat
// Allows to specify which columns the data will be inserted into.
// - An array such as `['a', 'b']` will generate: `INSERT INTO table (a, b) FORMAT DataFormat`
// - An object such as `{ except: ['a', 'b'] }` will generate: `INSERT INTO table (* EXCEPT (a, b)) FORMAT DataFormat`
// By default, the data is inserted into all columns of the table,
// and the generated statement will be: `INSERT INTO table FORMAT DataFormat`.
columns?: NonEmptyArray<string> | { except: NonEmptyArray<string> }
}

另请参阅:所有客户端方法的基本参数

信息

使用 abort_signal 取消的请求不保证数据插入未发生,因为服务器可能在取消之前已收到一些流式数据。

示例: (Node.js/Web) 插入一个值数组。源代码

await client.insert({
table: 'my_table',
// structure should match the desired format, JSONEachRow in this example
values: [
{ id: 42, name: 'foo' },
{ id: 42, name: 'bar' },
],
format: 'JSONEachRow',
})

示例: (仅限 Node.js) 从 CSV 文件插入流。源代码。另请参阅:文件流式传输

await client.insert({
table: 'my_table',
values: fs.createReadStream('./path/to/a/file.csv'),
format: 'CSV',
})

示例:从 insert 语句中排除某些列。

给定一些表定义,例如

CREATE OR REPLACE TABLE mytable
(id UInt32, message String)
ENGINE MergeTree()
ORDER BY (id)

仅插入特定列

// Generated statement: INSERT INTO mytable (message) FORMAT JSONEachRow
await client.insert({
table: 'mytable',
values: [{ message: 'foo' }],
format: 'JSONEachRow',
// `id` column value for this row will be zero (default for UInt32)
columns: ['message'],
})

排除某些列

// Generated statement: INSERT INTO mytable (* EXCEPT (message)) FORMAT JSONEachRow
await client.insert({
table: tableName,
values: [{ id: 144 }],
format: 'JSONEachRow',
// `message` column value for this row will be an empty string
columns: {
except: ['message'],
},
})

有关更多详细信息,请参阅源代码

示例:插入到与提供给客户端实例的数据库不同的数据库中。源代码

await client.insert({
table: 'mydb.mytable', // Fully qualified name including the database
values: [{ id: 42, message: 'foo' }],
format: 'JSONEachRow',
})

Web 版本限制

目前,@clickhouse/client-web 中的插入仅适用于 Array<T>JSON* 格式。由于浏览器兼容性差,Web 版本尚不支持插入流。

因此,Web 版本的 InsertParams 接口与 Node.js 版本略有不同,因为 values 仅限于 ReadonlyArray<T> 类型

interface InsertParams<T> extends BaseQueryParams {
// Table name to insert the data into
table: string
// A dataset to insert.
values: ReadonlyArray<T>
// Format of the dataset to insert.
format?: DataFormat
// Allows to specify which columns the data will be inserted into.
// - An array such as `['a', 'b']` will generate: `INSERT INTO table (a, b) FORMAT DataFormat`
// - An object such as `{ except: ['a', 'b'] }` will generate: `INSERT INTO table (* EXCEPT (a, b)) FORMAT DataFormat`
// By default, the data is inserted into all columns of the table,
// and the generated statement will be: `INSERT INTO table FORMAT DataFormat`.
columns?: NonEmptyArray<string> | { except: NonEmptyArray<string> }
}

这在未来可能会发生变化。另请参阅:所有客户端方法的基本参数

Command 方法

它可以用于没有任何输出的语句,当 format 子句不适用时,或者当您根本不关心响应时。此类语句的示例可以是 CREATE TABLEALTER TABLE

应该等待完成。

响应流会立即销毁,这意味着底层套接字已释放。

interface CommandParams extends BaseQueryParams {
// Statement to execute.
query: string
}

interface CommandResult {
query_id: string
}

interface ClickHouseClient {
command(params: CommandParams): Promise<CommandResult>
}

另请参阅:所有客户端方法的基本参数

示例: (Node.js/Web) 在 ClickHouse Cloud 中创建表。源代码

await client.command({
query: `
CREATE TABLE IF NOT EXISTS my_cloud_table
(id UInt64, name String)
ORDER BY (id)
`,
// Recommended for cluster usage to avoid situations where a query processing error occurred after the response code,
// and HTTP headers were already sent to the client.
// See https://clickhouse.ac.cn/docs/en/interfaces/http/#response-buffering
clickhouse_settings: {
wait_end_of_query: 1,
},
})

示例: (Node.js/Web) 在自托管 ClickHouse 实例中创建表。 源代码

await client.command({
query: `
CREATE TABLE IF NOT EXISTS my_table
(id UInt64, name String)
ENGINE MergeTree()
ORDER BY (id)
`,
})

示例: (Node.js/Web) INSERT FROM SELECT

await client.command({
query: `INSERT INTO my_table SELECT '42'`,
})
信息

使用 abort_signal 取消的请求不保证服务器没有执行该语句。

Exec 方法

如果您有一个不适合 query/insert 的自定义查询,并且您对结果感兴趣,则可以使用 exec 作为 command 的替代方法。

exec 返回一个可读流,必须在应用程序端被消费或销毁。

interface ExecParams extends BaseQueryParams {
// Statement to execute.
query: string
}

interface ClickHouseClient {
exec(params: ExecParams): Promise<QueryResult>
}

另请参阅:所有客户端方法的基本参数

Node.js 和 Web 版本中的流返回类型不同。

Node.js

export interface QueryResult {
stream: Stream.Readable
query_id: string
}

Web

export interface QueryResult {
stream: ReadableStream
query_id: string
}

Ping

提供的 ping 方法用于检查连接状态,如果可以访问服务器,则返回 true

如果服务器无法访问,则底层错误也会包含在结果中。

type PingResult =
| { success: true }
| { success: false; error: Error }

interface ClickHouseClient {
ping(): Promise<PingResult>
}

Ping 可能是一个有用的工具,用于检查应用程序启动时服务器是否可用,特别是对于 ClickHouse Cloud,实例可能处于空闲状态,并在 ping 后唤醒。

示例: (Node.js/Web) Ping ClickHouse 服务器实例。注意:对于 Web 版本,捕获的错误将有所不同。 源代码

const result = await client.ping();
if (!result.success) {
// process result.error
}

注意:由于 /ping 端点未实现 CORS,Web 版本使用简单的 SELECT 1 来实现类似的结果。

关闭(仅限 Node.js)

关闭所有打开的连接并释放资源。在 Web 版本中无操作。

await client.close()

流式传输文件(仅限 Node.js)

客户端存储库中有几个使用流行数据格式(NDJSON、CSV、Parquet)的文件流式传输示例。

将其他格式流式传输到文件应该类似于 Parquet,唯一的区别将在于用于 query 调用的格式(JSONEachRowCSV 等)和输出文件名。

支持的数据格式

客户端将数据格式处理为 JSON 或文本。

如果您将 format 指定为 JSON 系列之一(JSONEachRowJSONCompactEachRow 等),客户端将在通过网络进行通信期间序列化和反序列化数据。

以“原始”文本格式(CSVTabSeparatedCustomSeparated 系列)提供的数据在网络上传输时不会进行额外的转换。

提示

JSON 作为通用格式和 ClickHouse JSON 格式 之间可能存在混淆。

客户端支持使用诸如 JSONEachRow 之类的格式流式传输 JSON 对象(有关其他流式传输友好格式,请参阅表概述;另请参阅客户端存储库中的 select_streaming_ 示例)。

只有像 ClickHouse JSON 和其他一些格式在响应中表示为单个对象,并且客户端无法流式传输。

格式输入(数组)输入(对象)输入/输出(流)输出(JSON)输出(文本)
JSON✔️✔️✔️
JSONCompact✔️✔️✔️
JSONObjectEachRow✔️✔️✔️
JSONColumnsWithMetadata✔️✔️✔️
JSONStrings❌️✔️✔️
JSONCompactStrings✔️✔️
JSONEachRow✔️✔️✔️✔️
JSONEachRowWithProgress❌️✔️ ❗ - 见下文✔️✔️
JSONStringsEachRow✔️✔️✔️✔️
JSONCompactEachRow✔️✔️✔️✔️
JSONCompactStringsEachRow✔️✔️✔️✔️
JSONCompactEachRowWithNames✔️✔️✔️✔️
JSONCompactEachRowWithNamesAndTypes✔️✔️✔️✔️
JSONCompactStringsEachRowWithNames✔️✔️✔️✔️
JSONCompactStringsEachRowWithNamesAndTypes✔️✔️✔️✔️
CSV✔️✔️
CSVWithNames✔️✔️
CSVWithNamesAndTypes✔️✔️
TabSeparated✔️✔️
TabSeparatedRaw✔️✔️
TabSeparatedWithNames✔️✔️
TabSeparatedWithNamesAndTypes✔️✔️
CustomSeparated✔️✔️
CustomSeparatedWithNames✔️✔️
CustomSeparatedWithNamesAndTypes✔️✔️
Parquet✔️✔️ ❗ - 见下文

对于 Parquet,selects 的主要用例可能是将结果流写入文件。请参阅客户端存储库中的示例

JSONEachRowWithProgress 是一种仅输出格式,支持在流中报告进度。 有关更多详细信息,请参阅此示例

ClickHouse 输入和输出格式的完整列表可在此处获取

支持的 ClickHouse 数据类型

注意

相关的 JS 类型与任何 JSON* 格式相关,但表示所有内容为字符串的格式(例如 JSONStringEachRow)除外

类型状态JS 类型
UInt8/16/32✔️number
UInt64/128/256✔️ ❗ - 见下文string
Int8/16/32✔️number
Int64/128/256✔️ ❗ - 见下文string
Float32/64✔️number
Decimal✔️ ❗ - 见下文number
Boolean✔️boolean
String✔️string
FixedString✔️string
UUID✔️string
Date32/64✔️string
DateTime32/64✔️ ❗ - 见下文string
Enum✔️string
LowCardinality✔️string
Array(T)✔️T[]
(新)JSON✔️object
Variant(T1, T2...)✔️T(取决于变体)
Dynamic✔️T(取决于变体)
Nested✔️T[]
Tuple✔️Tuple
Nullable(T)✔️T 的 JS 类型或 null
IPv4✔️string
IPv6✔️string
Point✔️[ number, number ]
Ring✔️Array<Point>
Polygon✔️Array<Ring>
MultiPolygon✔️Array<Polygon>
Map(K, V)✔️Record<K, V>

支持的 ClickHouse 格式的完整列表可在此处获取

Date/Date32 类型注意事项

由于客户端插入值时没有额外的类型转换,因此 Date/Date32 类型的列只能作为字符串插入。

示例: 插入 Date 类型的值。 源代码

await client.insert({
table: 'my_table',
values: [ { date: '2022-09-05' } ],
format: 'JSONEachRow',
})

但是,如果您使用 DateTimeDateTime64 列,则可以使用字符串和 JS Date 对象。 JS Date 对象可以使用 date_time_input_format 设置为 best_effort 直接传递给 insert。 有关更多详细信息,请参阅此示例

Decimal* 类型注意事项

可以使用 JSON* 系列格式插入 Decimal。 假设我们有一个表定义为

CREATE TABLE my_table
(
id UInt32,
dec32 Decimal(9, 2),
dec64 Decimal(18, 3),
dec128 Decimal(38, 10),
dec256 Decimal(76, 20)
)
ENGINE MergeTree()
ORDER BY (id)

我们可以使用字符串表示形式插入值而不会丢失精度

await client.insert({
table: 'my_table',
values: [{
id: 1,
dec32: '1234567.89',
dec64: '123456789123456.789',
dec128: '1234567891234567891234567891.1234567891',
dec256: '12345678912345678912345678911234567891234567891234567891.12345678911234567891',
}],
format: 'JSONEachRow',
})

但是,当在 JSON* 格式中查询数据时,ClickHouse 默认情况下会将 Decimal 作为数字返回,这可能会导致精度损失。 为了避免这种情况,您可以在查询中将 Decimal 转换为字符串

await client.query({
query: `
SELECT toString(dec32) AS decimal32,
toString(dec64) AS decimal64,
toString(dec128) AS decimal128,
toString(dec256) AS decimal256
FROM my_table
`,
format: 'JSONEachRow',
})

有关更多详细信息,请参阅此示例

整数类型:Int64、Int128、Int256、UInt64、UInt128、UInt256

尽管服务器可以将其作为数字接受,但在 JSON* 系列输出格式中,它作为字符串返回,以避免整数溢出,因为这些类型的最大值大于 Number.MAX_SAFE_INTEGER

但是,可以使用 output_format_json_quote_64bit_integers 设置 修改此行为。

示例: 调整 64 位数字的 JSON 输出格式。

const resultSet = await client.query({
query: 'SELECT * from system.numbers LIMIT 1',
format: 'JSONEachRow',
})

expect(await resultSet.json()).toEqual([ { number: '0' } ])
const resultSet = await client.query({
query: 'SELECT * from system.numbers LIMIT 1',
format: 'JSONEachRow',
clickhouse_settings: { output_format_json_quote_64bit_integers: 0 },
})

expect(await resultSet.json()).toEqual([ { number: 0 } ])

ClickHouse 设置

客户端可以通过设置机制调整 ClickHouse 行为。 可以在客户端实例级别设置设置,以便将它们应用于发送到 ClickHouse 的每个请求

const client = createClient({
clickhouse_settings: {}
})

或者可以在请求级别配置设置

client.query({
clickhouse_settings: {}
})

包含所有受支持的 ClickHouse 设置的类型声明文件可以在此处找到。

信息

确保代表其发出查询的用户有足够的权限更改设置。

高级主题

带参数的查询

您可以创建带参数的查询,并从客户端应用程序将值传递给它们。 这允许避免在客户端格式化具有特定动态值的查询。

像往常一样格式化查询,然后将要从应用程序参数传递到查询的值放在花括号中,格式如下

{<name>: <data_type>}

其中

  • name — 占位符标识符。
  • data_type - 应用程序参数值的数据类型

示例::带参数的查询。 源代码

await client.query({
query: 'SELECT plus({val1: Int32}, {val2: Int32})',
format: 'CSV',
query_params: {
val1: 10,
val2: 20,
},
})

有关其他详细信息,请查看https://clickhouse.ac.cn/docs/en/interfaces/cli#cli-queries-with-parameters-syntax

压缩

注意:Web 版本目前不提供请求压缩。 响应压缩正常工作。 Node.js 版本同时支持两者。

处理网络上大型数据集的数据应用程序可以从启用压缩中受益。 目前,仅支持使用 zlibGZIP

createClient({
compression: {
response: true,
request: true
}
})

配置参数为

  • response: true 指示 ClickHouse 服务器以压缩的响应体进行响应。 默认值:response: false
  • request: true 在客户端请求体上启用压缩。 默认值:request: false

日志记录(仅限 Node.js)

信息

日志记录是一项实验性功能,将来可能会发生更改。

默认的日志记录器实现通过 console.debug/info/warn/error 方法将日志记录发出到 stdout。 您可以通过提供 LoggerClass 自定义日志记录逻辑,并通过 level 参数选择所需的日志级别(默认为 OFF

import type { Logger } from '@clickhouse/client'

// All three LogParams types are exported by the client
interface LogParams {
module: string
message: string
args?: Record<string, unknown>
}
type ErrorLogParams = LogParams & { err: Error }
type WarnLogParams = LogParams & { err?: Error }

class MyLogger implements Logger {
trace({ module, message, args }: LogParams) {
// ...
}
debug({ module, message, args }: LogParams) {
// ...
}
info({ module, message, args }: LogParams) {
// ...
}
warn({ module, message, args }: WarnLogParams) {
// ...
}
error({ module, message, args, err }: ErrorLogParams) {
// ...
}
}

const client = createClient({
log: {
LoggerClass: MyLogger,
level: ClickHouseLogLevel
}
})

目前,客户端将记录以下事件

  • TRACE - 有关 Keep-Alive 套接字生命周期的低级别信息
  • DEBUG - 响应信息(不包含授权标头和主机信息)
  • INFO - 大部分未使用,将在客户端初始化时打印当前日志级别
  • WARN - 非致命错误; 失败的 ping 请求记录为警告,因为底层错误包含在返回的结果中
  • ERROR - 来自 query/insert/exec/command 方法的致命错误,例如请求失败

您可以在此处找到默认的 Logger 实现。

TLS 证书(仅限 Node.js)

Node.js 客户端可选择支持基本 TLS(仅证书颁发机构)和相互 TLS(证书颁发机构和客户端证书)。

基本 TLS 配置示例,假设您的证书位于 certs 文件夹中,并且 CA 文件名为 CA.pem

const client = createClient({
url: 'https://<hostname>:<port>',
username: '<username>',
password: '<password>', // if required
tls: {
ca_cert: fs.readFileSync('certs/CA.pem'),
},
})

使用客户端证书的相互 TLS 配置示例

const client = createClient({
url: 'https://<hostname>:<port>',
username: '<username>',
tls: {
ca_cert: fs.readFileSync('certs/CA.pem'),
cert: fs.readFileSync(`certs/client.crt`),
key: fs.readFileSync(`certs/client.key`),
},
})

请参阅存储库中基本 TLS 和相互 TLS 的完整示例。

Keep-Alive 配置(仅限 Node.js)

客户端默认在底层 HTTP 代理中启用 Keep-Alive,这意味着连接的套接字将重用于后续请求,并且将发送 Connection: keep-alive 标头。 默认情况下,空闲的套接字将在连接池中保留 2500 毫秒(请参阅有关调整此选项的说明)。

keep_alive.idle_socket_ttl 的值应略低于服务器/LB 配置。 主要原因是由于 HTTP/1.1 允许服务器在不通知客户端的情况下关闭套接字,如果服务器或负载均衡器客户端之前关闭连接,则客户端可能会尝试重用已关闭的套接字,从而导致 socket hang up 错误。

如果您要修改 keep_alive.idle_socket_ttl,请记住它应始终与您的服务器/LB Keep-Alive 配置同步,并且应始终低于该配置,以确保服务器永远不会首先关闭打开的连接。

调整 idle_socket_ttl

客户端将 keep_alive.idle_socket_ttl 设置为 2500 毫秒,因为它可以被认为是安全默认值; 在服务器端,keep_alive_timeout 可能会设置为低至 3 秒(在 23.11 之前的 ClickHouse 版本中),而无需修改 config.xml

危险

如果您对性能感到满意并且没有遇到任何问题,建议不要增加 keep_alive.idle_socket_ttl 设置的值,因为它可能会导致潜在的“Socket hang-up”错误; 此外,如果您的应用程序发送大量查询并且它们之间没有太多停机时间,则默认值应该足够,因为套接字不会空闲足够长的时间,并且客户端会将它们保留在池中。

您可以通过运行以下命令在服务器响应标头中找到正确的 Keep-Alive 超时值

curl -v --data-binary "SELECT 1" <clickhouse_url>

检查响应中 ConnectionKeep-Alive 标头的值。 例如

< Connection: Keep-Alive
< Keep-Alive: timeout=10

在这种情况下,keep_alive_timeout 为 10 秒,您可以尝试将 keep_alive.idle_socket_ttl 增加到 9000 甚至 9500 毫秒,以使空闲套接字保持打开状态的时间比默认时间更长。 密切关注潜在的“Socket hang-up”错误,这将表明服务器在客户端之前关闭了连接,并降低该值,直到错误消失。

Keep-Alive 故障排除

如果您在使用 Keep-Alive 时遇到 socket hang up 错误,则有以下选项可以解决此问题

  • 稍微降低 ClickHouse 服务器配置中的 keep_alive.idle_socket_ttl 设置。 在某些情况下,例如,客户端和服务器之间的高网络延迟,将 keep_alive.idle_socket_ttl 再降低 200-500 毫秒可能是有益的,从而排除传出请求可能获得服务器即将关闭的套接字的情况。

  • 如果此错误发生在长时间运行的查询期间,并且没有数据传入或传出(例如,长时间运行的 INSERT FROM SELECT),这可能是由于负载均衡器关闭了空闲连接。 您可以尝试通过组合以下 ClickHouse 设置来强制在长时间运行的查询期间传入一些数据

    const client = createClient({
    // Here we assume that we will have some queries with more than 5 minutes of execution time
    request_timeout: 400_000,
    /** These settings in combination allow to avoid LB timeout issues in case of long-running queries without data coming in or out,
    * such as `INSERT FROM SELECT` and similar ones, as the connection could be marked as idle by the LB and closed abruptly.
    * In this case, we assume that the LB has idle connection timeout of 120s, so we set 110s as a "safe" value. */
    clickhouse_settings: {
    send_progress_in_http_headers: 1,
    http_headers_progress_interval_ms: '110000', // UInt64, should be passed as a string
    },
    })

    但是请记住,在最近的 Node.js 版本中,接收到的标头的总大小限制为 16KB; 在收到一定数量的进度标头后(在我们的测试中约为 70-80 个),将生成异常。

    也可以使用完全不同的方法,完全避免网络上的等待时间; 这可以通过利用 HTTP 接口“功能”来实现,即当连接丢失时,不会取消 mutation。 有关更多详细信息,请参阅此示例(第 2 部分)

  • 可以完全禁用 Keep-Alive 功能。 在这种情况下,客户端还将向每个请求添加 Connection: close 标头,并且底层 HTTP 代理将不会重用连接。 keep_alive.idle_socket_ttl 设置将被忽略,因为不会有空闲套接字。 这将导致额外的开销,因为将为每个请求建立新连接。

    const client = createClient({
    keep_alive: {
    enabled: false,
    },
    })

只读用户

当将客户端与 readonly=1 用户 一起使用时,无法启用响应压缩,因为它需要 enable_http_compression 设置。 以下配置将导致错误

const client = createClient({
compression: {
response: true, // won't work with a readonly=1 user
},
})

请参阅示例,其中更详细地介绍了 readonly=1 用户的限制。

带有路径名的代理

如果您的 ClickHouse 实例位于代理之后,并且 URL 中包含路径名,例如 http://proxy:8123/clickhouse_server,请将 clickhouse_server 指定为 pathname 配置选项(带或不带前导斜杠); 否则,如果直接在 url 中提供,它将被视为 database 选项。 支持多个段,例如 /my_proxy/db

const client = createClient({
url: 'http://proxy:8123',
pathname: '/clickhouse_server',
})

带有身份验证的反向代理

如果您的 ClickHouse 部署前端有带有身份验证的反向代理,则可以使用 http_headers 设置在此处提供必要的标头

const client = createClient({
http_headers: {
'My-Auth-Header': '...',
},
})

自定义 HTTP/HTTPS 代理(实验性,仅限 Node.js)

危险

这是一项实验性功能,将来版本中可能会以向后不兼容的方式更改。 客户端提供的默认实现和设置应足以满足大多数用例。 仅当您确定需要此功能时才使用它。

默认情况下,客户端将使用客户端配置中提供的设置(例如 max_open_connectionskeep_alive.enabledtls)配置底层 HTTP(s) 代理,这将处理与 ClickHouse 服务器的连接。 此外,如果使用 TLS 证书,则将使用必要的证书配置底层代理,并强制执行正确的 TLS 身份验证标头。

在 1.2.0 之后,可以向客户端提供自定义 HTTP(s) 代理,以替换默认的底层代理。 在网络配置复杂的情况下,这可能很有用。 如果提供自定义代理,则以下条件适用

  • max_open_connectionstls 选项将不起作用,并且将被客户端忽略,因为它是底层代理配置的一部分。
  • keep_alive.enabled 将仅调节 Connection 标头的默认值(true -> Connection: keep-alivefalse -> Connection: close)。
  • 虽然空闲 keep-alive 套接字管理仍然有效(因为它不绑定到代理,而是绑定到特定的套接字本身),但现在可以通过将 keep_alive.idle_socket_ttl 值设置为 0 来完全禁用它。

自定义代理使用示例

使用没有证书的自定义 HTTP(s) 代理

const agent = new http.Agent({ // or https.Agent
keepAlive: true,
keepAliveMsecs: 2500,
maxSockets: 10,
maxFreeSockets: 10,
})
const client = createClient({
http_agent: agent,
})

使用带有基本 TLS 和 CA 证书的自定义 HTTPS 代理

const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 2500,
maxSockets: 10,
maxFreeSockets: 10,
ca: fs.readFileSync('./ca.crt'),
})
const client = createClient({
url: 'https://myserver:8443',
http_agent: agent,
// With a custom HTTPS agent, the client won't use the default HTTPS connection implementation; the headers should be provided manually
http_headers: {
'X-ClickHouse-User': 'username',
'X-ClickHouse-Key': 'password',
},
// Important: authorization header conflicts with the TLS headers; disable it.
set_basic_auth_header: false,
})

使用带有相互 TLS 的自定义 HTTPS 代理

const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 2500,
maxSockets: 10,
maxFreeSockets: 10,
ca: fs.readFileSync('./ca.crt'),
cert: fs.readFileSync('./client.crt'),
key: fs.readFileSync('./client.key'),
})
const client = createClient({
url: 'https://myserver:8443',
http_agent: agent,
// With a custom HTTPS agent, the client won't use the default HTTPS connection implementation; the headers should be provided manually
http_headers: {
'X-ClickHouse-User': 'username',
'X-ClickHouse-Key': 'password',
'X-ClickHouse-SSL-Certificate-Auth': 'on',
},
// Important: authorization header conflicts with the TLS headers; disable it.
set_basic_auth_header: false,
})

对于证书自定义 HTTPS 代理,可能需要通过 set_basic_auth_header 设置(在 1.2.0 中引入)禁用默认的授权标头,因为它与 TLS 标头冲突。 所有 TLS 标头都应手动提供。

已知限制(Node.js/Web)

已知限制(Web)

  • select 查询的流式传输有效,但对于 inserts 禁用(也在类型级别)。
  • 请求压缩已禁用,并且配置将被忽略。 响应压缩有效。
  • 尚无日志记录支持。

性能优化技巧

  • 为了减少应用程序内存消耗,请考虑在适用时对大型 inserts(例如来自文件)和 selects 使用流。 对于事件侦听器和类似用例,异步插入可能是另一个不错的选择,允许最小化甚至完全避免客户端的批处理。 异步插入示例在客户端存储库中提供,文件名以 async_insert_ 为前缀。
  • 客户端默认情况下不启用请求或响应压缩。 但是,当选择或插入大型数据集时,您可以考虑通过 ClickHouseClientConfigOptions.compression 启用它(仅针对 requestresponse,或两者都启用)。
  • 压缩具有明显的性能损失。 为 requestresponse 启用它将分别对 selects 或 inserts 的速度产生负面影响,但会减少应用程序传输的网络流量。

联系我们

如果您有任何问题或需要帮助,请随时通过社区 Slack#clickhouse-js 频道)或通过GitHub issues 与我们联系。