跳至主要内容
跳至主要内容

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 版本支持吗?
24.x
22.x
20.x
18.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.12.024.8+

客户端可能也能与旧版本兼容;但是,这属于尽力而为的支持,不能保证。如果您使用的是低于 23.3 的 ClickHouse 版本,请参阅 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 后添加到 ClickHouse URL 中。''带有路径名的代理文档
request_timeout?: number请求超时时间,以毫秒为单位。30_000-
compression?: { **response**?: boolean; **request**?: boolean }启用压缩。-压缩文档
username?: string代表请求执行的用户名称。默认-
password?: string用户密码。''-
application?: string使用 Node.js 客户端的应用程序名称。clickhouse-js-
database?: string要使用的数据库名称。默认-
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 }--保持活动文档
http_agent?: http.Agent | https.Agent
实验性功能。 了解更多。
客户端的自定义 HTTP 代理。-HTTP 代理文档
set_basic_auth_header?: boolean
实验性功能。 了解更多。
使用基本身份验证凭据设置 Authorization 标头。true此设置在 HTTP 代理文档中的用法

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,您需要这些信息

参数描述
HOSTPORT通常,使用 TLS 时的端口为 8443,不使用 TLS 时的端口为 8123。
数据库名称默认情况下,有一个名为 default 的数据库,使用您要连接的数据库的名称。
USERNAMEPASSWORD默认情况下,用户名是 default。使用适合您用例的用户名。

您的 ClickHouse Cloud 服务的详细信息可在 ClickHouse Cloud 控制台中找到。选择一个服务并单击 Connect

选择 HTTPS。连接详细信息显示在一个示例 curl 命令中。

如果您使用的是自托管 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 的连接池以供重用,利用保持活动机制。默认情况下,保持活动已启用,连接池的大小设置为 10,但可以使用 max_open_connections 配置选项 进行更改。

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

另请参阅:保持活动配置

查询 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>
}

查询方法

这用于大多数可以有响应的语句,例如 SELECT,或用于发送 DDL,例如 CREATE TABLE,并且应该被 await。返回的结果集预计将在应用程序中被消费。

注意

有一个专门的方法 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)使用 for await const 语法以 JSONEachRow 格式流式传输查询结果。这与经典的 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())
  })
}

插入方法

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

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>。另请参阅有关 文件流式传输 的本节。

应该 await insert 方法;但是,可以指定一个输入流,并在流完成时稍后 await 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',
})

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

给定一些表定义,例如

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> }
}

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

命令方法

当格式子句不适用时,或者您根本不感兴趣响应时,可以使用它来执行没有输出的语句。此类语句的示例可以是 CREATE TABLEALTER TABLE

应该被 await。

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

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/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 }

/** Parameters for the health-check request - using the built-in `/ping` endpoint. 
 *  This is the default behavior for the Node.js version. */
export type PingParamsWithEndpoint = {
  select: false
  /** AbortSignal instance to cancel a request in progress. */
  abort_signal?: AbortSignal
  /** Additional HTTP headers to attach to this particular request. */
  http_headers?: Record<string, string>
}
/** Parameters for the health-check request - using a SELECT query.
 *  This is the default behavior for the Web version, as the `/ping` endpoint does not support CORS.
 *  Most of the standard `query` method params, e.g., `query_id`, `abort_signal`, `http_headers`, etc. will work, 
 *  except for `query_params`, which does not make sense to allow in this method. */
export type PingParamsWithSelectQuery = { select: true } & Omit<
  BaseQueryParams,
  'query_params'
>
export type PingParams = PingParamsWithEndpoint | PingParamsWithSelectQuery

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

Ping 可能是应用程序启动时检查服务器是否可用的一个有用工具,尤其是在使用 ClickHouse Cloud 时,一个实例可能处于空闲状态,并在收到 ping 后唤醒:在这种情况下,你可能希望在延迟之间多次重试它。

请注意,默认情况下,Node.js 版本使用 /ping 端点,而 Web 版本使用简单的 SELECT 1 查询来实现类似的结果,因为 /ping 端点不支持 CORS。

示例: (Node.js/Web) 向 ClickHouse 服务器实例发送一个简单的 ping。注意:对于 Web 版本,捕获的错误会有所不同。 源代码

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

示例: 如果你想在调用 ping 方法时也检查凭据,或者指定其他参数,例如 query_id,你可以这样使用它

const result = await client.ping({ select: true, /* query_id, abort_signal, http_headers, or any other query params */ });

ping 方法将允许大部分标准的 query 方法参数 - 请参阅 PingParamsWithSelectQuery 类型定义。

关闭 (仅 Node.js)

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

await client.close()

流式文件 (仅 Node.js)

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

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

支持的数据格式

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

如果将 format 指定为 JSON 格式族中的一种(JSONEachRowJSONCompactEachRow 等),则客户端将在通过线路进行通信时序列化和反序列化数据。

以“原始”文本格式(CSVTabSeparatedCustomSeparated 族)提供的数据在通过线路传输时不会进行额外的转换。

提示

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

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

只是像 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,选择的主要用例可能是将结果流写入文件。请参阅客户端仓库中的 示例

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[]
(new) JSON✔️object
Variant(T1, T2...)✔️T (取决于变体)
Dynamic✔️T (取决于变体)
嵌套✔️T[]
Tuple(T1, T2, ...)✔️[T1, T2, ...]
Tuple(n1 T1, n2 T2...)✔️{ n1: T1; n2: T2; ...}
Nullable(T)✔️T 或 null 的 JS 类型
IPv4✔️string
IPv6✔️string
Point✔️[ number, number ]
Ring✔️Array<Point>
Polygon✔️Array<Ring>
MultiPolygon✔️Array<Polygon>
Map(K, V)✔️Record<K, V>
Time/Time64✔️string

完整的 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_formatbest_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 设置

客户端可以通过 settings 机制调整 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/interfaces/cli#cli-queries-with-parameters-syntax

压缩

注意:请求压缩当前在 Web 版本中不可用。响应压缩正常工作。Node.js 版本支持两者。

通过线路处理大型数据集的数据应用程序可以通过启用压缩来受益。目前,仅使用 zlib 支持 GZIP

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 socket 生命周期的小级别信息
  • DEBUG - 响应信息(不包含授权头和主机信息)
  • INFO - 很少使用,客户端初始化时会打印当前的日志级别
  • WARN - 非致命错误;失败的 ping 请求记录为警告,因为底层错误包含在返回的结果中
  • ERROR - 来自 query/insert/exec/command 方法的致命错误,例如请求失败

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

TLS 证书(仅限 Node.js)

Node.js 客户端可选地支持基本的(仅证书颁发机构)和互通的(证书颁发机构和客户端证书)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 的完整示例,请参阅仓库。

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

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

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

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

调整 idle_socket_ttl

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

注意

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

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

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

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

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

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

故障排除

即使在使用最新版本的客户端时遇到 socket hang up 错误,也有以下选项可以解决此问题

  • 启用至少 WARN 日志级别的日志。 这将允许检查应用程序代码中是否存在未消耗或悬挂的流:传输层将在 WARN 级别记录它,因为这可能会导致服务器关闭 socket。

    const client = createClient({
      log: { level: ClickHouseLogLevel.WARN },
    })
    
  • 使用启用了 no-floating-promises ESLint 规则检查您的应用程序代码,这将有助于识别可能导致悬挂流和 socket 的未处理的 promise。

  • 稍微降低 ClickHouse 服务器配置中的 keep_alive.idle_socket_ttl 设置。 在某些情况下,例如客户端和服务器之间的网络延迟较高,将 keep_alive.idle_socket_ttl 降低 200–500 毫秒可能会有益,从而排除服务器在客户端发出请求之前关闭 socket 的情况。

  • 如果此错误发生在没有数据进出(例如,长时间运行的 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 版本中,接收到的 header 的总大小限制为 16KB;在接收到一定数量的进度 header(在我们的测试中约为 70-80)后,将生成一个异常。

    也可以使用完全不同的方法,完全避免线路上的等待时间;可以通过利用 HTTP 接口的“特性”来实现,即当连接丢失时不会取消变异。 请参阅 此示例(第 2 部分) 以获取更多详细信息。

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

    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 设置提供必要的 header

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

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

注意

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

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

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

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

自定义 agent 用法示例

使用不带证书的自定义 HTTP(s) Agent

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 Agent

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 Agent

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 Agent 时,可能需要禁用默认的授权头信息,通过 set_basic_auth_header 设置(引入于 1.2.0 版本),因为它与 TLS 头信息冲突。所有 TLS 头信息应手动提供。

已知限制 (Node.js/web)

已知限制 (web)

  • SELECT 查询的流式传输有效,但 INSERT 的流式传输被禁用(在类型级别)。
  • 请求压缩被禁用且配置被忽略。响应压缩有效。
  • 尚不支持日志记录。

性能优化技巧

  • 为了减少应用程序内存消耗,请考虑对大型 INSERT(例如从文件)和 SELECT(如果适用)使用流。对于事件监听器和类似用例,异步 INSERT 可能是另一个不错的选择,可以最大限度地减少或完全避免客户端批处理。异步 INSERT 示例可在 客户端仓库中找到,文件名前缀为 async_insert_
  • 客户端默认情况下不启用请求或响应压缩。但是,在选择或插入大型数据集时,您可以考虑通过 ClickHouseClientConfigOptions.compression(仅针对 requestresponse,或两者)启用它。
  • 压缩会带来显著的性能损失。为 requestresponse 启用它将对 SELECT 或 INSERT 的速度产生负面影响,但会减少应用程序传输的网络流量。

联系我们

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

    © . This site is unofficial and not affiliated with ClickHouse, Inc.