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.5.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 } - 请参阅 保持活动文档
- 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¶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 是一个不错的选择。
所有客户端方法的基本参数
有几个参数可以应用于所有客户端方法(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 }
}
查询方法
用于大多数可能具有响应的语句,例如 SELECT
,或用于发送 DDL,例如 CREATE TABLE
。应等待。预期应用程序将在应用程序中使用返回的结果集。
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 和 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>
}
返回类型是最小的,因为我们不希望服务器返回任何数据,并立即清空响应流。
如果向 insert 方法提供了空数组,则不会将 insert 语句发送到服务器;相反,该方法将立即解析为 { query_id: '...', executed: false }
。在这种情况下,如果在方法参数中未提供 query_id
,则结果将为空字符串,因为返回客户端生成的随机 UUID 可能会令人困惑,因为 system.query_log
表中将不存在具有此类 query_id
的查询。
如果将 insert 语句发送到服务器,则 executed
标志将为 true
。
插入方法和 Node.js 中的流式传输
它可以与 Stream.Readable
或普通的 Array<T>
一起使用,具体取决于为 insert
方法指定了数据格式。另请参阅有关文件流式传输的此部分。
插入方法应该等待;但是,可以指定输入流并在稍后等待 insert
操作,只有在流完成时(这也会解析 insert
promise)。这对于事件监听器和类似场景可能有用,但错误处理可能不简单,客户端存在很多极端情况。相反,请考虑使用异步插入,如此示例所示。
如果您有一个难以使用此方法建模的自定义 INSERT 语句,请考虑使用command;请参阅INSERT INTO ... VALUES或INSERT 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 TABLE
或 ALTER 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
调用(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
- 关于 Keep-Alive 套接字生命周期的低级信息DEBUG
- 响应信息(不包括授权标头和主机信息)INFO
- 大多未使用,在客户端初始化时将打印当前日志级别WARN
- 非致命错误;失败的ping
请求被记录为警告,因为基础错误包含在返回的结果中ERROR
- 来自query
/insert
/exec
/command
方法的致命错误,例如失败的请求
您可以在此处找到默认的 Logger 实现。
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 代理中启用 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
可能会被设置为在 23.11 之前的 ClickHouse 版本中低至 3 秒,无需修改config.xml
。
如果您对性能感到满意并且没有遇到任何问题,建议不要增加keep_alive.idle_socket_ttl
设置的值,因为它可能会导致潜在的“Socket hang-up”错误;此外,如果您的应用程序发送大量查询并且它们之间没有太多停机时间,则默认值应该足够了,因为套接字不会空闲足够长的时间,并且客户端会将它们保存在池中。
您可以通过运行以下命令在服务器响应标头中找到正确的 Keep-Alive 超时值
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”错误,这将表明服务器在客户端之前关闭连接,并降低该值,直到错误消失。
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 接口的“特性”来完成,即在连接丢失时不会取消变异。请参阅此示例(第 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_connections
、keep_alive.enabled
、tls
)配置底层 HTTP(s) 代理,该代理将处理与 ClickHouse 服务器的连接。此外,如果使用 TLS 证书,则底层代理将使用必要的证书进行配置,并将强制执行正确的 TLS 身份验证标头。
在 1.2.0 之后,可以向客户端提供自定义 HTTP(s) 代理,替换默认的底层代理。在复杂的网络配置的情况下,这可能很有用。如果提供了自定义代理,则适用以下条件
max_open_connections
和tls
选项将无效,并且客户端将忽略它们,因为它是底层代理配置的一部分。keep_alive.enabled
仅调节Connection
标头的默认值(true
->Connection: keep-alive
,false
->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)
- 结果集没有数据映射器,因此只使用语言原语。某些数据类型映射器计划与RowBinary 格式支持一起提供。
- 一些Decimal* 和 Date* / DateTime* 数据类型存在注意事项。
- 当使用 JSON* 系列格式时,大于 Int32 的数字将表示为字符串,因为 Int64+ 类型最大值大于
Number.MAX_SAFE_INTEGER
。有关更多详细信息,请参阅整数类型部分。
已知限制 (Web)
- select 查询的流式处理有效,但插入操作的流式处理已禁用(在类型级别也已禁用)。
- 请求压缩已禁用且配置被忽略。响应压缩有效。
- 尚不支持日志记录。
性能优化技巧
- 为了减少应用程序内存消耗,请考虑在适用情况下对大型插入(例如,来自文件)和选择使用流。对于事件监听器和类似用例,异步插入可能是另一种不错的选择,可以最大程度地减少甚至完全避免客户端端的批处理。异步插入示例可在客户端存储库中找到,文件名前缀为
async_insert_
。 - 客户端默认情况下不启用请求或响应压缩。但是,在选择或插入大型数据集时,您可以考虑通过
ClickHouseClientConfigOptions.compression
启用它(仅对request
或response
,或两者都启用)。 - 压缩会带来明显的性能损失。分别为
request
或response
启用它会对选择或插入的速度产生负面影响,但会减少应用程序传输的网络流量。
联系我们
如果您有任何疑问或需要帮助,请随时在社区 Slack(#clickhouse-js
频道)或通过GitHub 问题与我们联系。