ClickHouse JS
连接到 ClickHouse 的官方 JS 客户端。该客户端是用 TypeScript 编写的,并为客户端公共 API 提供类型。
它没有依赖项,针对最大性能进行了优化,并使用各种 ClickHouse 版本和配置(本地单节点、本地集群、ClickHouse Cloud)进行了测试。
有两种不同的客户端版本可用于不同的环境
@clickhouse/client
- 仅限 Node.js@clickhouse/client-web
- 浏览器(Chrome/Firefox)、CloudFlare Workers
使用 TypeScript 时,请确保它至少是 版本 4.5,它启用了 内联导入和导出语法。
环境要求(Node.js)
环境中必须存在 Node.js 才能运行客户端。该客户端与所有 维护的 Node.js 版本兼容。
一旦 Node.js 版本接近生命周期结束,客户端就会停止对其的支持,因为它被认为已过时且不安全。
当前 Node.js 版本支持
Node.js 版本 | 支持? |
---|---|
21.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.0.0 | 23.3+ |
该客户端很可能也能与旧版本一起使用;但是,这是尽力而为的支持,不能保证。如果您使用的 ClickHouse 版本早于 23.3,请参考 ClickHouse 安全策略 并考虑升级。
示例
我们的目标是使用客户端存储库中的 示例 涵盖客户端使用的各种场景。
概述可在 示例自述文件 中找到。
如果示例或以下文档中存在不清楚或缺失的内容,请随时 联系我们。
客户端 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?: string - ClickHouse 实例 URL。默认值:
https://127.0.0.1:8123
。另请参见:URL 配置文档。 - 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 标头。另请参见:使用身份验证的反向代理文档
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 } - 请参见 保持活动文档
URL 配置
URL 配置将始终覆盖硬编码值,在这种情况下会记录警告。
可以使用 URL 配置大多数客户端实例参数。URL 格式为 http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2]
。在几乎所有情况下,特定参数的名称都反映了其在配置选项接口中的路径,但有一些例外。支持以下参数
参数 | 类型 |
---|---|
pathname | 任意字符串。 |
application_id | 任意字符串。 |
session_id | 任意字符串。 |
request_timeout | 非负数。 |
max_open_connections | 非负数,大于零。 |
compression_request | 布尔值。见下文[1]. |
compression_response | 布尔值。 |
log_level | 允许的值:OFF 、TRACE 、DEBUG 、INFO 、WARN 、ERROR 。 |
keep_alive_enabled | 布尔值。 |
clickhouse_setting_* 或 ch_* | 见下文[2]. |
http_header_* | 见下文[3]. |
(仅限 Node.js) keep_alive_idle_socket_ttl | 非负数。 |
[1]对于布尔值,有效值为 true
/1
和 false
/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,您需要以下信息
主机和端口:通常,使用 TLS 时端口为 8443,不使用 TLS 时端口为 8123。
数据库名称:开箱即用,有一个名为
default
的数据库,请使用您要连接到的数据库的名称。用户名和密码:默认情况下,用户名为
default
。请根据您的使用场景选择合适的用户名。
您 ClickHouse Cloud 服务的详细信息可在 ClickHouse Cloud 控制台中找到。选择您要连接的服务,然后点击连接
选择HTTPS,详细信息可在示例 curl
命令中找到。
如果您使用的是自托管 ClickHouse,连接详细信息由您的 ClickHouse 管理员设置。
连接概述
客户端通过 HTTP(s) 协议实现连接。RowBinary 支持正在进行中,请查看相关问题。
以下示例演示了如何针对 ClickHouse Cloud 设置连接。假设 host
(包括协议和端口)和 password
值通过环境变量指定,并使用 default
用户。
示例:使用环境变量进行配置创建 Node.js 客户端实例。
import { createClient } from '@clickhouse/client'
const client = createClient({
host: process.env.CLICKHOUSE_HOST ?? 'https://127.0.0.1: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
每个发送查询或语句(command
、exec
、insert
、select
)的方法都会在结果中提供 query_id
。此唯一标识符由客户端为每个查询分配,可能有助于从 system.query_log
中获取数据(如果在服务器配置中启用),或取消长时间运行的查询(请查看示例)。如果需要,用户可以在 command
/query
/exec
/insert
方法参数中覆盖 query_id
。
如果您要覆盖 query_id
参数,则需要确保每次调用时的唯一性。随机 UUID 是一个不错的选择。
查询方法
用于大多数可能具有响应的语句,例如 SELECT
,或用于发送 DDL,例如 CREATE TABLE
。应等待。预期应用程序将使用返回的结果集。
interface QueryParams {
// Query to execute that might return some data.
query: string
// Format of the resulting dataset.
format?: DataFormat
// 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
}
interface ClickHouseClient {
query(params: QueryParams): Promise<ResultSet>
}
不要在 query
中指定 FORMAT 子句,请改用 format
参数。
ResultSet 和 Row 抽象
ResultSet 为您应用程序中的数据处理提供了多种便捷方法。
Node.js ResultSet 实现使用 Stream.Readable
作为底层,而 Web 版本使用 Web API ReadableStream
。
您应该尽快开始使用 ResultSet,因为它会保持响应流打开,从而使底层连接保持繁忙状态;客户端不会缓冲传入数据,以避免应用程序潜在的过度内存使用。
您可以通过调用 text
或 json
方法来使用 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
格式流式传输查询结果作为 JS 对象。这与经典的 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>
}
返回类型是最小的,因为我们不希望服务器返回任何数据,并立即清空响应流。
如果向插入方法提供了空数组,则不会将插入语句发送到服务器;相反,该方法将立即解析为 { query_id: '...', executed: false }
。如果在这种情况下,在方法参数中未提供 query_id
,则它将在结果中为空字符串,因为返回客户端生成的随机 UUID 会令人困惑,因为具有此 query_id
的查询将不存在于 system.query_log
表中。
如果将插入语句发送到服务器,则 executed
标志将为 true
。
插入方法和 Node.js 中的流式传输
它可以与 Stream.Readable
或普通 Array<T>
一起使用,具体取决于向 insert
方法指定的数据格式。另请参阅有关文件流式传输的本节。
插入方法应该被等待;但是,可以指定一个输入流,并在流完成后(这也会解析 insert
承诺)才等待 insert
操作。这对于事件侦听器和类似场景可能很有用,但错误处理可能很复杂,并且客户端侧存在许多边缘情况。相反,请考虑使用异步插入,就像此示例中所示。
如果您有一个难以用此方法建模的自定义 INSERT 语句,请考虑使用command;请查看INSERT INTO ... VALUES 或INSERT INTO ... SELECT 示例中的使用方法。
interface InsertParams<T> {
// 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
// ClickHouse settings that can be applied on statement level.
clickhouse_settings?: ClickHouseSettings
// Parameters for query binding.
query_params?: Record<string, unknown>
// AbortSignal instance to cancel an insert in progress.
abort_signal?: AbortSignal
// query_id override; if not specified, a random identifier will be generated automatically.
query_id?: string
// 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> {
// Table name to insert the data into
table: string
// A dataset to insert.
values: ReadonlyArray<T>
// Format of the dataset to insert.
format?: DataFormat
// ClickHouse settings that can be applied on statement level.
clickhouse_settings?: ClickHouseSettings
// Parameters for query binding.
query_params?: Record<string, unknown>
// AbortSignal instance to cancel an insert in progress.
abort_signal?: AbortSignal
// query_id override; if not specified, a random identifier will be generated automatically.
query_id?: string
// 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 TABLE
或 ALTER TABLE
。
应等待。
响应流会立即被销毁,这意味着底层套接字将被释放。
interface CommandParams {
// Statement to execute.
query: string
// 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 request in progress.
abort_signal?: AbortSignal
// query_id override; if not specified, a random identifier will be generated automatically.
query_id?: 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
取消的请求不能保证语句未被服务器执行。
执行方法
如果您有一个不适合 query
/insert
的自定义查询,并且您对结果感兴趣,您可以使用 exec
作为 command
的替代方法。
exec
返回一个可读流,该流必须在应用程序端被消费或销毁。
interface ExecParams {
// Statement to execute.
query: string
// 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 request in progress.
abort_signal?: AbortSignal
// query_id override; if not specified, a random identifier will be generated automatically.
query_id?: 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
调用的格式(JSONEachRow
、CSV
等)和输出文件名。
支持的数据格式
客户端将数据格式处理为 JSON 或文本。
如果您将 format
指定为 JSON 家族中的一个(JSONEachRow
、JSONCompactEachRow
等),客户端将在有线通信期间序列化和反序列化数据。
以“原始”文本格式(CSV
、TabSeparated
和 CustomSeparated
家族)提供的数据将在没有额外转换的情况下通过有线传输。
JSON 作为通用格式和 ClickHouse JSON 格式 之间可能存在混淆。
客户端支持使用诸如 JSONEachRow 之类的格式流式传输 JSON 对象(有关其他支持流式传输的格式,请参阅表格概述;另请参阅客户端存储库中的 select_streaming_
示例)。
只是诸如 ClickHouse JSON 之类的格式以及其他一些格式在响应中表示为单个对象,并且无法由客户端流式传输。
格式 | 输入(数组) | 输入(对象) | 输入/输出(流) | 输出(JSON) | 输出(文本) |
---|---|---|---|---|---|
JSON | ❌ | ✔️ | ❌ | ✔️ | ✔️ |
JSONCompact | ❌ | ✔️ | ❌ | ✔️ | ✔️ |
JSONObjectEachRow | ❌ | ✔️ | ❌ | ✔️ | ✔️ |
JSONColumnsWithMetadata | ❌ | ✔️ | ❌ | ✔️ | ✔️ |
JSONStrings | ❌ | ❌️ | ❌ | ✔️ | ✔️ |
JSONCompactStrings | ❌ | ❌ | ❌ | ✔️ | ✔️ |
JSONEachRow | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONStringsEachRow | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactEachRow | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactStringsEachRow | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactEachRowWithNames | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactEachRowWithNamesAndTypes | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactStringsEachRowWithNames | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
JSONCompactStringsEachRowWithNamesAndTypes | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
CSV | ❌ | ❌ | ✔️ | ❌ | ✔️ |
CSVWithNames | ❌ | ❌ | ✔️ | ❌ | ✔️ |
CSVWithNamesAndTypes | ❌ | ❌ | ✔️ | ❌ | ✔️ |
TabSeparated | ❌ | ❌ | ✔️ | ❌ | ✔️ |
TabSeparatedRaw | ❌ | ❌ | ✔️ | ❌ | ✔️ |
TabSeparatedWithNames | ❌ | ❌ | ✔️ | ❌ | ✔️ |
TabSeparatedWithNamesAndTypes | ❌ | ❌ | ✔️ | ❌ | ✔️ |
CustomSeparated | ❌ | ❌ | ✔️ | ❌ | ✔️ |
CustomSeparatedWithNames | ❌ | ❌ | ✔️ | ❌ | ✔️ |
CustomSeparatedWithNamesAndTypes | ❌ | ❌ | ✔️ | ❌ | ✔️ |
Parquet | ❌ | ❌ | ✔️ | ❌ | ✔️❗- 请参阅下文 |
对于 Parquet,选择的主要用例可能是将结果流写入文件。请参阅客户端存储库中的 示例。
ClickHouse 输入和输出格式的完整列表可在此处获得 此处。
支持的 ClickHouse 数据类型
类型 | 状态 | 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 |
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',
})
但是,如果您使用的是 DateTime
或 DateTime64
列,则可以使用字符串和 JS Date 对象。JS Date 对象可以按原样传递给 insert
,并将 date_time_input_format
设置为 best_effort
。有关更多详细信息,请参阅此 示例。
Decimal*类型注意事项
可以使用 JSON*
家族格式插入十进制数。假设我们有一个定义为
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 默认情况下会将十进制数返回为数字,这会导致精度丢失。为了避免这种情况,您可以在查询中将十进制数强制转换为字符串
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 版本支持两者。
通过有线传输处理大型数据集的数据应用程序可以从启用压缩中受益。目前,仅使用 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
- 有关保持活动套接字生命周期的低级信息DEBUG
- 响应信息(不含授权标头和主机信息)INFO
- 大部分未被使用,将在客户端初始化时打印当前日志级别WARN
- 非致命错误;失败的ping
请求被记录为警告,因为底层错误包含在返回的结果中ERROR
- 来自query
/insert
/exec
/command
方法的致命错误,例如失败的请求
您可以在此处找到默认的日志记录器实现 此处。
TLS 证书(仅限 Node.js)
Node.js 客户端可以选择支持基本(仅证书颁发机构)和双向(证书颁发机构和客户端证书)TLS。
基本 TLS 配置示例,假设您的证书位于 certs
文件夹中,CA 文件名为 CA.pem
const client = createClient({
host: 'https://<hostname>:<port>',
username: '<username>',
password: '<password>', // if required
tls: {
ca_cert: fs.readFileSync('certs/CA.pem'),
},
})
使用客户端证书的双向 TLS 配置示例
const client = createClient({
host: '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`),
},
})
保持活动配置(仅限 Node.js)
客户端默认情况下在底层 HTTP 代理中启用保持活动,这意味着连接的套接字将被重用于后续请求,并且将发送 Connection: keep-alive
头。默认情况下,空闲的套接字将保留在连接池中 2500 毫秒(请参阅 有关调整此选项的说明)。
keep_alive.idle_socket_ttl
的值应该比服务器/负载均衡器配置的值低很多。主要原因是由于 HTTP/1.1 允许服务器在不通知客户端的情况下关闭套接字,如果服务器或负载均衡器在客户端之前关闭连接,客户端可能会尝试重用已关闭的套接字,从而导致 socket hang up
错误。
如果您正在修改 keep_alive.idle_socket_ttl
,请记住它应该始终与您的服务器/负载均衡器保持活动配置同步,并且应该始终低于该配置,以确保服务器永远不会先关闭打开的连接。
调整 idle_socket_ttl
客户端将 keep_alive.idle_socket_ttl
设置为 2500 毫秒,因为它可以被认为是最安全的默认值;在服务器端,keep_alive_timeout
可能会被设置为 在 23.11 之前的 ClickHouse 版本中低至 3 秒,无需修改 config.xml
。
如果您对性能感到满意并且没有遇到任何问题,建议您不要增加 keep_alive.idle_socket_ttl
设置的值,因为它可能会导致潜在的“套接字挂起”错误;此外,如果您的应用程序发送大量查询并且它们之间没有太多停机时间,默认值应该足够,因为套接字不会长时间处于空闲状态,并且客户端会将它们保留在池中。
您可以通过运行以下命令在服务器响应头中找到正确的保持活动超时值
curl -v --data-binary "SELECT 1" <clickhouse_url>
检查响应中 Connection
和 Keep-Alive
头的值。例如
< Connection: Keep-Alive
< Keep-Alive: timeout=10
在这种情况下,keep_alive_timeout
为 10 秒,您可以尝试将 keep_alive.idle_socket_ttl
增加到 9000 甚至 9500 毫秒,以使空闲套接字保持打开状态的时间比默认值更长。注意潜在的“套接字挂起”错误,这将表明服务器在客户端之前关闭了连接,并将该值降低,直到错误消失。
保持活动故障排除
如果您在使用保持活动时遇到 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 接口的“功能”来实现,即在连接丢失时不会取消变异。有关更多详细信息,请参阅 此示例(第 2 部分)。
保持活动功能可以完全禁用。在这种情况下,客户端还会将
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': '...',
},
})
已知限制(Node.js/Web)
- 结果集没有数据映射器,因此只使用语言原语。某些数据类型映射器计划与 RowBinary 格式支持 一起使用。
- 有一些 Decimal* 和 Date* / DateTime* 数据类型注意事项。
- 当使用 JSON* 系列格式时,大于 Int32 的数字将表示为字符串,因为 Int64+ 类型最大值大于
Number.MAX_SAFE_INTEGER
。有关更多详细信息,请参阅 整数类型 部分。
已知限制(Web)
- 选择查询的流工作,但它被禁用以插入(在类型级别也是如此)。
- 请求压缩被禁用,并且配置被忽略。响应压缩有效。
- 尚未支持日志记录。
性能优化技巧
- 为了减少应用程序内存消耗,请考虑在适用时对大型插入(例如,来自文件)和选择使用流。对于事件监听器和类似用例,异步插入 可能是另一个不错的选择,允许最小化甚至完全避免客户端的批处理。异步插入示例在 客户端存储库 中可用,文件名前缀为
async_insert_
。 - 客户端默认情况下不会启用请求或响应压缩。但是,在选择或插入大型数据集时,您可以考虑通过
ClickHouseClientConfigOptions.compression
(仅针对request
或response
,或两者)来启用它。 - 压缩会导致明显的性能损失。分别为
request
或response
启用它会对选择或插入的速度产生负面影响,但会减少应用程序传输的网络流量。
联系我们
如果您有任何问题或需要帮助,请随时在 社区 Slack(#clickhouse-js
频道)或通过 GitHub 问题 联系我们。