-
ClickHouseClient(recomendado): Un Client de alto nivel, seguro para subprocesos y diseñado para utilizarse como instancia única. Ofrece una API asíncrona sencilla para realizar consultas e inserciones masivas. Es la mejor opción para la mayoría de las aplicaciones. -
ADO.NET (
ClickHouseDataSource,ClickHouseConnection,ClickHouseCommand): Abstracciones estándar de .NET para bases de datos. Son necesarias para integrar ORM (Dapper, Linq2db) y cuando se requiere compatibilidad con ADO.NET.ClickHouseBulkCopyes una clase auxiliar que permite insertar datos de forma eficiente mediante una conexión ADO.NET.ClickHouseBulkCopyestá obsoleta y se eliminará en una versión futura; utiliceClickHouseClient.InsertBinaryAsyncen su lugar.
Guía de migración
- Actualice el archivo
.csprojpara usar el nuevo nombre de paqueteClickHouse.Drivery la versión más reciente disponible en NuGet. - Sustituya todas las referencias a
ClickHouse.ClientporClickHouse.Driveren su base de código.
Versiones de .NET compatibles
ClickHouse.Driver es compatible con las siguientes versiones de .NET:
- .NET 6.0
- .NET 8.0
- .NET 9.0
- .NET 10.0
Versiones compatibles de ClickHouse
El Client es oficialmente compatible con las tres versiones más recientes, además de las dos últimas versiones LTS.Instalación
Instale el paquete desde NuGet:Inicio rápido
Configuración
Puede configurar la conexión a ClickHouse de dos maneras:- Cadena de conexión: Pares de clave y valor separados por punto y coma que especifican el host, las credenciales de autenticación y otras opciones de conexión.
- Objeto
ClickHouseClientSettings: Un objeto de configuración fuertemente tipado que puede cargarse desde archivos de configuración o definirse en el código.
Configuración de la conexión
Formato de datos y serialización
Gestión de sesiones
El indicador
UseSession permite mantener la sesión en el servidor, lo que hace posible usar sentencias SET y tablas temporales. Las sesiones se restablecen tras 60 segundos de inactividad (el tiempo de espera predeterminado). La duración de la sesión puede ampliarse ajustando su configuración mediante sentencias de ClickHouse o la configuración del servidor.La clase ClickHouseConnection normalmente permite ejecutar consultas en paralelo desde varios threads. Sin embargo, al habilitar el indicador UseSession, solo podrá haber una consulta activa por conexión en un momento dado (una limitación del lado del servidor).Seguridad
Configuración del Client HTTP
Registro y depuración
Ajustes personalizados y roles
Si utiliza una cadena de conexión para establecer ajustes personalizados, añada el prefijo
set_, por ejemplo, “set_max_threads=4”. Si utiliza un objeto ClickHouseClientSettings, no añada el prefijo set_.Para consultar la lista completa de ajustes disponibles, visite esta página.Ejemplos de cadenas de conexión
Conexión básica
Con ajustes personalizados de ClickHouse
QueryOptions
QueryOptions permite sobrescribir la configuración del Client para cada consulta. Todas las propiedades son opcionales y, si no se especifican, se mantienen los valores predeterminados del Client.
Ejemplo:
InsertOptions
InsertOptions amplía QueryOptions con opciones específicas para las inserciones masivas mediante InsertBinaryAsync.
Todas las propiedades de
QueryOptions también están disponibles en InsertOptions.
Ejemplo:
Omitir la consulta preliminar del esquema
De forma predeterminada,InsertBinaryAsync envía una consulta SELECT ... WHERE 1=0 antes de cada inserción para detectar los tipos de columna. En escenarios de alto caudal, puede evitar este coste adicional de dos maneras:
Opción 1: Indicar explícitamente los tipos de columna
Si conoce el esquema de la tabla en tiempo de compilación, proporciónelo directamente mediante ColumnTypes. De este modo, no se envía ninguna consulta para obtener el esquema:
UseSchemaCache = true para consultar el esquema una sola vez y reutilizarlo en las inserciones posteriores desde la misma instancia de ClickHouseClient:
ColumnTypestiene prioridad sobreUseSchemaCache. Si se establecen ambos, se utilizan los tipos explícitos.- La caché de esquemas no detecta los cambios realizados mediante
ALTER TABLE. Si modifica el esquema de la tabla, cree un nuevoClickHouseCliento no utiliceUseSchemaCachepara esa tabla. - La caché está asociada a la instancia de
ClickHouseClienty utiliza como clave el par (base de datos, tabla). Los distintos subconjuntos de columnas de una misma tabla comparten un único esquema almacenado en caché.
ClickHouseClient
ClickHouseClient es la API recomendada para interactuar con ClickHouse. Es seguro para subproceso, está diseñado para utilizarse como singleton y gestiona internamente el pool de conexiones HTTP.
Creación de un Client
Cree unClickHouseClient mediante una cadena de conexión o un objeto ClickHouseClientSettings. Consulte las opciones disponibles en la sección Configuración.
Los datos de conexión de su servicio de ClickHouse Cloud están disponibles en la consola de ClickHouse Cloud.
Seleccione un servicio y haga clic en Conectar:
Elija C#. Los datos de conexión aparecerán debajo.
Si utiliza ClickHouse autogestionado, el administrador de ClickHouse establece los datos de conexión.
Para usar una cadena de conexión:
ClickHouseClientSettings:
IHttpClientFactory:
ClickHouseClient está diseñado para tener una vida útil prolongada y compartirse en toda la aplicación. Créelo una sola vez (normalmente como un singleton) y reutilícelo para todas las operaciones de la base de datos. El Client gestiona internamente el pool de conexiones HTTP.Ejecución de consultas
UseExecuteNonQueryAsync para ejecutar sentencias que no devuelven resultados:
ExecuteScalarAsync para obtener un único valor:
Inserción de datos
Inserciones parametrizadas
Inserte datos mediante consultas parametrizadas conExecuteNonQueryAsync. Los tipos de los parámetros deben indicarse en la consulta SQL con la sintaxis {name:Type}:
Inserciones masivas
UtiliceInsertBinaryAsync para insertar grandes cantidades de filas de forma eficiente. Transmite los datos en el formato binario nativo de filas de ClickHouse, permite subir lotes en paralelo y evita los errores “URL too long” que pueden producirse con las consultas parametrizadas.
InsertOptions:
- El Client obtiene automáticamente la estructura de la tabla mediante
SELECT * FROM <table> WHERE 1=0antes de insertar datos. Los valores proporcionados deben coincidir con los tipos de las columnas de destino. Para evitar esta consulta, useInsertOptions.ColumnTypesoInsertOptions.UseSchemaCache. - Cuando
MaxDegreeOfParallelism > 1, los lotes se cargan en paralelo. Las sesiones no son compatibles con la inserción en paralelo; desactive las sesiones o establezcaMaxDegreeOfParallelism = 1. - Use
RowBinaryFormat.RowBinaryWithDefaultsenInsertOptions.Formatsi desea que el servidor aplique los valores DEFAULT a las columnas para las que no se hayan proporcionado valores.
Inserciones de POCO
En lugar de crear arraysobject[], puede insertar directamente objetos POCO fuertemente tipados. Registre el tipo una sola vez y después pase IEnumerable<T>:
Si todas las propiedades mapeadas especifican un
Type explícito, se omite por completo la consulta preliminar del esquema. Si solo algunas propiedades tienen tipos explícitos, el controlador recurre a esa consulta para el conjunto completo de columnas.
InsertBinaryAsync<T> admite las mismas opciones de InsertOptions (procesamiento por lotes, paralelismo y almacenamiento en caché del esquema) que la sobrecarga de object[].
A diferencia de la sobrecarga de
object[], InsertBinaryAsync<T> no acepta una lista explícita de columnas. Las columnas se determinan a partir de las propiedades mapeadas del tipo registrado. Para controlar qué columnas se insertan, use [ClickHouseNotMapped] para excluir propiedades o [ClickHouseColumn(Name = "...")] para cambiarles el nombre.Si se establece ColumnTypes en InsertOptions, sus valores prevalecen sobre los atributos del POCO.Evolución del esquema
Las inserciones POCO siguen funcionando sin problemas si se añaden columnas a la tabla de destino después de registrar el tipo. Como el controlador solo inserta las columnas mapeadas por el POCO, el servidor completa automáticamente las columnas nuevas que tenganDEFAULT u otra expresión de valor predeterminado. No es necesario modificar el código ni volver a registrar el tipo.
Posición de la sentencia INSERT
Una inserción binaria coloca la sentenciaINSERT INTO ... FORMAT ... en la primera línea del cuerpo de la solicitud, antes de las filas. El cuerpo se comprime de forma predeterminada, por lo que los sistemas de enrutamiento y registro que solo inspeccionan la URL no pueden ver la sentencia. Establezca InsertOptions.QueryPlacement en InsertQueryPlacement.Url para enviar la sentencia como el parámetro de URL query y reservar el cuerpo exclusivamente para las filas:
query, o cuando quiera que la sentencia quede registrada en los logs de acceso y en las herramientas de observabilidad. Esta opción debe habilitarse explícitamente porque la sentencia pasa a contar para el límite de longitud de la URL. El límite efectivo es el menor de los impuestos por el entorno de ejecución de .NET, cualquier intermediario y el servidor. Entre .NET 6 y .NET 9, System.Uri limita la URI completa y codificada de la solicitud a 65,519 caracteres; si se supera ese límite, el controlador genera una InvalidOperationException que indica que debe volver a usar InsertQueryPlacement.Body. El valor predeterminado de http_max_uri_size en ClickHouse es 1 MiB, aunque un intermediario puede imponer un límite menor. En el modo de cuerpo, la sentencia y las filas no están sujetas a ese límite de longitud de la URL; otras opciones de la solicitud pueden seguir apareciendo en ella.
Este ajuste es independiente de Compressor: el cuerpo se codifica de la misma manera en ambos modos.
Lectura de datos
UseExecuteReaderAsync para ejecutar consultas SELECT. El objeto ClickHouseDataReader que se devuelve permite acceder a las columnas del resultado con el tipo de dato correspondiente mediante métodos como GetInt64(), GetString() y GetFieldValue<T>().
Llame a Read() para pasar a la siguiente fila. Devuelve false cuando no quedan más filas. Acceda a las columnas por su índice (empezando por 0) o por su nombre.
Lectura de objetos POCO
En lugar de leer las columnas por índice o por nombre, puede obtener los resultados de la consulta directamente como instancias de sus propias clases. Registre el tipo una sola vez en el Client y, después, useQueryAsync<T>:
RegisterPocoType<T>() configura las correspondencias de inserción y lectura y valida ambas desde el principio. RegisterBinaryInsertType<T>() no cambia y sigue siendo exclusivo para inserciones por compatibilidad con versiones anteriores.
Un tipo registrado debe tener:
- Un constructor público sin parámetros.
- Al menos una propiedad pública con un setter público que no sea
init. Se admiten las propiedadesrequired.
InvalidOperationException. Por tanto, una propiedad de tipo object acepta cualquier columna.
QueryAsync<T> lee cada una de estas columnas directamente en una propiedad del tipo correspondiente:
Cada fila admite también la variante anulable del tipo de propiedad (
long?, DateOnly?, etc.),
independientemente de que la columna sea Nullable(...) o no. Se permite registrar una propiedad de tipo valor no anulable para una columna Nullable(T),
pero se genera una excepción si se recibe un valor NULL.
Las envolturas como LowCardinality(T), SimpleAggregateFunction(f, T) y Object(T) se asignan igual que T.
También se admiten las columnas compuestas, que usan el tipo del framework indicado en la
referencia de tipos de lectura: Array(T) se asigna a T[], Tuple(...)
a System.Tuple<...>, Nested(...) a Tuple<...>[], JSON a JsonObject (o a string
con JsonReadMode=String) y Variant/Dynamic a object.
Una columna Map(K, V) es un caso especial: una propiedad List<KeyValuePair<K, V>> o KeyValuePair<K, V>[]
se lee sin boxing y conserva el orden de transmisión y las claves repetidas, con cualquiera de los
valores de MapReadMode. Una propiedad Dictionary<K, V> solo funciona en el modo
predeterminado. Los tipos de clave y valor deben coincidir exactamente, por lo que
Map(String, Nullable(Int32)) requiere KeyValuePair<string, int?>.
Cuando una columna admite más de un tipo de propiedad (por ejemplo, una columna DateTime como DateTime,
DateTimeOffset o DateOnly, o una columna String como string o byte[]), el tipo de propiedad declarado determina la representación. Estas representaciones alternativas
solo están disponibles al leer objetos POCO: QueryAsync<T> las admite, pero MapTo<T> no.
Al recorrer manualmente un lector, use ClickHouseDataReader.MapTo<T>() para materializar la fila actual como un POCO registrado sin avanzar a la siguiente fila:
MapTo<T> cuando necesite controlar directamente el bucle de lectura, por ejemplo, para combinar el acceso a columnas sin procesar
con la materialización de objetos POCO. Lee la fila a partir de los valores encapsulados del lector, por lo que no admite
los tipos de propiedad alternativos mencionados arriba y realiza más asignaciones de memoria que QueryAsync<T>. Es preferible usar
QueryAsync<T> si solo necesita las filas; consulte
cómo elegir la vía de materialización para ver las cifras.
Un conversor de valores de lectura configurado a nivel de Client o por consulta se aplica a ambas rutas y
no desactiva la lectura sin boxing. El controlador convierte cada columna con la sobrecarga correspondiente a la forma en que
se leyó: la versión tipada ConvertValue<T> para una
columna sin boxing y la versión con boxing ConvertValue para una columna compuesta. Implemente ambas sobrecargas
de forma coherente; de lo contrario, una misma columna producirá resultados distintos según la ruta.
Cuando hay una LoggerFactory configurada, RegisterPocoType<T>() y RegisterBinaryInsertType<T>() generan un log de nivel Debug (categoría ClickHouse.Driver.Client) que detalla qué propiedades se asignaron a cada columna, cuáles se omitieron y por qué. Consulte Registro y diagnóstico.
Parámetros SQL
En ClickHouse, los parámetros de las consultas SQL siguen el formato estándar{parameter_name:DataType}.
Ejemplos:
Los parámetros ‘bind’ de SQL se envían como parámetros de consulta en el URI HTTP, por lo que usar demasiados puede provocar una excepción de “URL demasiado larga”. Utilice
InsertBinaryAsync para insertar datos en bloque y evitar esta limitación.Marcadores de posición @name al estilo ADO
El driver también acepta marcadores de posición @name, como los que generan los ORM, por ejemplo, Dapper. Son una
comodidad para el cliente: antes de enviar la solicitud, cada marcador se sustituye por
{name:ResolvedType}, de modo que el servidor nunca recibe un @. Consulte
resolución de tipos para saber cómo se determina el tipo. Utilice la forma explícita
{name:Type} siempre que sea posible.
Si @name no tiene un parámetro correspondiente, se deja tal cual para que el servidor lo rechace. La correspondencia distingue
entre mayúsculas y minúsculas, por lo que @ID no se vincula a un parámetro llamado id.
Para desactivar esta sustitución, establezca el modificador de AppContext
ClickHouse.Driver.DisableReplacingParameters
antes de usar el driver por primera vez. Solo se desactiva la sustitución en el texto; los parámetros se siguen enviando, por lo que
las consultas escritas con la sintaxis nativa {name:Type} siguen funcionando.Parámetros de identificador
El tipo de parámetroIdentifier permite usar de forma segura el nombre de una base de datos, una tabla o una columna como parámetro, en lugar de un literal de cadena entre comillas. Para ello, utilice la sintaxis {name:Identifier} en SQL o establezca ClickHouseDbParameter.ClickHouseType = "Identifier":
ID de consulta
A cada consulta se le asigna unquery_id único que permite consultar datos en la tabla system.query_log o cancelar consultas de larga duración. Puede especificar un ID de consulta personalizado mediante QueryOptions:
Correspondencia personalizada de tipos de parámetros
Al usar parámetros con la sintaxis@ (por ejemplo, WHERE id = @id), el driver infiere automáticamente el tipo de ClickHouse a partir del tipo del valor de .NET. Por ejemplo, int se corresponde con Int32.
Para sobrescribir estos valores predeterminados, configura ParameterTypeResolver en ClickHouseClientSettings. Esto resulta útil si quieres que todos los parámetros DateTime usen DateTime64(3) para obtener precisión de milisegundos, o que todos los valores decimales usen una escala determinada, sin tener que establecer ClickHouseType en cada parámetro.
Uso de DictionaryParameterTypeResolver para correspondencias sencillas de tipos:
IParameterTypeResolver personalizado para casos avanzados:
Para resolver el tipo en función del valor o del nombre, implemente directamente la interfaz IParameterTypeResolver. Devuelva null para recurrir a la inferencia predeterminada:
QueryOptions.ParameterTypeResolver. Si lo hace, este tiene precedencia sobre el resolvedor configurado a nivel del client.
Orden de precedencia para la resolución de tipos:
El resolvedor forma parte de una cadena de precedencia. De mayor a menor prioridad:
ClickHouseTypeestablecido explícitamente en el parámetro- Indicación de tipo SQL mediante la sintaxis
{name:Type}en la consulta IParameterTypeResolver(deQueryOptions.ParameterTypeResolver; si no está establecido, se utilizaClickHouseClientSettings.ParameterTypeResolver)- Inferencia de tipos integrada (
TypeConverter.ToClickHouseType)
ClickHouseConnection: las conexiones creadas a partir del client heredan la configuración.
Formato personalizado de los valores de los parámetros
IParameterFormatter es un punto de extensión que determina cómo se serializan los valores de los parámetros. Úselo cuando el formato integrado (p. ej., la precisión de DateTime, la configuración regional de los decimales, el escape de cadenas o la representación de números) no se ajuste a lo que esperan su esquema o las herramientas posteriores.
Establezca ParameterFormatter en ClickHouseClientSettings para usar un formateador en todas las consultas parametrizadas. El formateador recibe el valor, el nombre del tipo de ClickHouse determinado y el nombre del parámetro, y devuelve la representación en cadena que se envía al servidor. Devuelva null para usar el formateador predeterminado.
Uso de DictionaryParameterFormatter para aplicar un formato sencillo según el tipo CLR:
IParameterFormatter personalizado para escenarios avanzados:
QueryOptions.ParameterFormatter. Si se configura, tiene prioridad sobre el formateador del client.
Valores compuestos:
El formateador se aplica tanto a los parámetros de colección de nivel superior como a cada elemento de los valores compuestos (Array, Tuple, Map, Nullable, LowCardinality, Variant). Por ejemplo, una correspondencia para typeof(int) formatea por separado cada elemento Int32 de un Array(Int32).
Uso de comillas simples en contextos compuestos:
Cuando los tipos de ClickHouse que representan cadenas (String, FixedString, Enum8, Enum16, IPv4, IPv6, UUID) forman parte de un literal compuesto, el driver encierra el resultado del formateador entre comillas simples, pero no escapa su contenido. Si la cadena devuelta contiene una comilla simple o una barra invertida sin escapar, el literal compuesto no será válido y el servidor rechazará la consulta.
Los parámetros de cadena de nivel superior (que no forman parte de un valor compuesto) se utilizan tal cual, sin encerrarlos entre comillas, por lo que no es necesario escaparlos.
Orden de prioridad de los formateadores:
IParameterFormatter(deQueryOptions.ParameterFormattero, si no está disponible, deClickHouseClientSettings.ParameterFormatter). Si devuelve un valor distinto denull, se utiliza ese valor.- El formato integrado específico de cada tipo en
HttpParameterFormatter.
null o DBNull; estos siempre se serializan como el marcador de valor nulo de ClickHouse (\N).
Conversión personalizada de valores al leer
IReadValueConverter permite transformar los valores que devuelve el lector de datos después de la deserialización, sin cambiar su tipo CLR. Entre sus usos habituales están establecer DateTime.Kind = Utc en una columna DateTime sin zona horaria, eliminar espacios o normalizar cadenas y procesar una columna JSON antes de que los datos lleguen al código de la aplicación.
Configure ReadValueConverter en ClickHouseClientSettings para aplicar un conversor a todas las lecturas. El conversor se invoca una vez por cada columna de cada fila, tanto en la ruta de valores encapsulados (GetValue) como en la genérica (GetFieldValue<T>). Si no se configura ningún conversor, no se añade ninguna sobrecarga: el lector devuelve los valores directamente.
Uso de DictionaryReadValueConverter para conversiones sencillas según el tipo CLR:
For<T> pasan sin modificarse. La selección se basa en el tipo CLR exacto, así que registre el tipo que realmente produce el lector (por ejemplo, For<JsonObject> para una columna JSON en JsonReadMode.Binary).
IReadValueConverter personalizado para casos avanzados:
Si necesita seleccionar el conversor según la cadena de tipo de ClickHouse (por ejemplo, para distinguir DateTime de DateTime('UTC'), que se representan con el mismo tipo CLR), implemente IReadValueConverter directamente:
GetFieldType, GetSchemaTable) no pasan por él y deben ser coherentes con el valor devuelto.
También puede configurar un conversor para cada consulta mediante QueryOptions.ReadValueConverter; si lo hace, tendrá prioridad sobre el conversor configurado a nivel de cliente.
Alcance de la conversión:
El conversor se invoca una vez por columna y recibe el valor completo de la celda deserializada; no procesa recursivamente los contenedores compuestos. Para una columna Array(Int32), recibe un int[]; para Tuple(Int32, String), un ITuple.
Qué sobrecarga se ejecuta:
Ambas sobrecargas deben producir resultados coherentes, ya que la sobrecarga que invoca el driver depende de cómo el llamador haya leído la
columna:
ConvertValue<T>— los métodos de acceso tipadosGetByte,GetSByte,GetInt16/32/64,GetUInt16/32/64,GetFloat,GetDouble,GetGuid,GetDateTime,GetIPAddress,GetBigIntegeryGetFieldValue<T>, además de todas las columnas sin conversión a objeto en la ruta de lectura de POCO.ConvertValue(con conversión a objeto) —GetValue,GetValues, los indexadores,GetChar,GetTupley las rutas que fuerzan una conversión de tipo enGetBoolean,GetDecimalyGetString.
IsDBNull no invoca ningún conversor: lee directamente el indicador de valor nulo, por lo que un conversor nunca puede
cambiar si un valor se considera nulo. TryGetEnumOrdinal también prescinde del conversor; consulte
la lectura del valor ordinal de un enum.
El conversor también funciona con la ruta ADO.NET de ClickHouseConnection: las conexiones creadas a partir del cliente heredan la configuración.
Transmisión de datos sin procesar
UseExecuteRawResultAsync para transmitir directamente los resultados de una consulta en un formato específico, sin pasar por el lector de datos. Esto resulta útil para exportar datos a archivos o enviarlos a otros sistemas:
JSONEachRow, CSV, TSV, Parquet, Native. Consulte la documentación de formatos para ver todas las opciones.
Compresión de transporte por consulta
De forma predeterminada, el client negociazstd, lz4, gzip, deflate cuando Compression=true (el valor predeterminado de la cadena de conexión) y decodifica el flujo por sí mismo, de forma transparente.
Para las exportaciones sin procesar (p. ej., Parquet, Arrow, Native), puede ser conveniente negociar otro códec (p. ej., zstd o lz4) para reducir el consumo de ancho de banda a cambio de un mayor uso de CPU, sin modificar la configuración de toda la conexión. QueryOptions.AcceptEncoding y ClickHouseCommand.AcceptEncoding establecen el encabezado HTTP Accept-Encoding para una sola solicitud, sustituyen cualquier valor predeterminado que se hubiera añadido y fuerzan enable_http_compression=1 en la URL (ClickHouse lo requiere para respetar Accept-Encoding).
Configuración de HttpClient
No hay nada que configurar: elHttpClient que crea el driver deja AutomaticDecompression en DecompressionMethods.None, y el propio driver decodifica las respuestas. Así, Content-Encoding no se elimina a sus espaldas y el cuerpo sin procesar le llega exactamente como lo envió el servidor.
Cuerpos de error
Cuando el servidor responde con un código 4xx/5xx yenable_http_compression=1 está activado, comprime el cuerpo del error con el mismo códec que habría usado para una respuesta correcta. El driver lo descomprime si se ha utilizado alguno de los códecs que admite (lz4, zstd, gzip, deflate, br/brotli), para que el mensaje de ClickHouseServerException sea legible. Si se ha utilizado otro códec (snappy, …), devuelve un marcador de posición que indica el códec y remite a system.query_log para consultar el texto original del error.
Descompresión de respuestas
Accept-Encoding solo solicita al servidor que comprima la respuesta; después hay que descomprimirla. El driver se encarga de hacerlo según el valor de Content-Encoding de la respuesta. Así, todas las API habituales de lectura (ExecuteReaderAsync, ExecuteScalarAsync, ExecuteNonQueryAsync, QueryAsync<T>, Dapper, EF Core, linq2db) funcionan con respuestas comprimidas sin necesidad de configurar nada. Admite lz4, zstd, gzip, deflate y br; snappy no es compatible.
De forma predeterminada, el driver anuncia zstd, lz4, gzip, deflate, y ClickHouse responde con zstd. Si prefiere otra opción, establezca Accept-Encoding a nivel de client:
ClickHouseClientSettings:
enable_http_compression=1 a la URL, requisito para que ClickHouse tenga en cuenta el encabezado, incluso si UseCompression es false: indicar expresamente un códec se interpreta como una solicitud de compresión. Si no se establece ningún valor, UseCompression=false no envía el encabezado Accept-Encoding.
Accept-Encoding puede establecerse en cuatro lugares. Prevalece el primero en el que se indique un códec:
QueryOptions.AcceptEncoding(oClickHouseCommand.AcceptEncoding)CustomHeaders["Accept-Encoding"]en la consultaCustomHeaders["Accept-Encoding"]en el clientClickHouseClientSettings.AcceptEncodingo la palabra claveAcceptEncodingde la cadena de conexión
identity.
El servidor, no el client, elige el códec. ClickHouse busca los tokens de Accept-Encoding según su propio orden fijo de preferencia — zstd > br > lz4 > snappy > gzip > deflate — e ignora tanto el orden en que se enumeran como los valores q. Por tanto, el encabezado indica qué códecs se admiten, pero no exige uno en particular: la única forma de influir en la elección es omitir tokens. La lista predeterminada incluye zstd, por lo que una consulta con la configuración predeterminada recibe una respuesta comprimida con zstd; los demás tokens sirven como alternativas. br se puede decodificar, pero no se incluye en el encabezado de forma predeterminada.
El tamaño de la carga útil y el uso de CPU del servidor y del client varían según el códec, los datos, la conexión y el valor de http_zlib_compression_level del servidor (valor predeterminado de fábrica: 3). Consulte Ajuste de la compresión.
http_zlib_compression_level. Esta configuración se aplica a todos los códecs HTTP y su valor predeterminado es 3. Ajuste este valor según los datos, la velocidad de la conexión y el uso de CPU.- Un client limitado por la CPU en una conexión rápida. El driver decodifica el cuerpo de la respuesta en el thread desde el que se realiza la llamada. Por tanto, si la red no es el cuello de botella, la velocidad de decodificación del client podría convertirse en el factor limitante.
Content-Encoding así lo indica, con independencia de lo solicitado: si el encabezado no está presente o su valor es identity, el cuerpo se deja intacto; si indica un códec compatible, se decodifica; y cualquier otro valor provoca un error que lo menciona. No hay riesgo de decodificarlo dos veces: si AutomaticDecompression de un manejador proporcionado por el llamador ya ha decodificado el cuerpo, también elimina Content-Encoding, de modo que el driver recibe el texto sin cifrar y lo deja intacto.
Los resultados sin procesar no anuncian ningún códec. ExecuteRawResultAsync (y los métodos públicos PostStreamAsync / InsertRawStreamAsync) entregan el cuerpo tal como se recibió. Por eso, si no se especifica un códec, no solicitan ninguno: el driver no decodifica ese cuerpo, de modo que anunciar un códec en este caso podría convertir inadvertidamente una exportación en un archivo comprimido. La regla es sencilla y no depende de cómo esté configurado HttpClient: el cuerpo llega exactamente como lo envió el servidor, y el servidor envía datos sin cifrar a menos que se solicite un códec. Para exportar bytes comprimidos de forma deliberada, hay que solicitar un códec (para todo el client o para una consulta concreta).
Un valor explícito de AcceptEncoding (en cualquiera de los dos niveles) también se aplica a las solicitudes sin procesar. Si se desea decodificar el resultado, puede usarse ClickHouseRawResult.ReadDecompressedStreamAsync(); ReadAsStreamAsync, ReadAsByteArrayAsync, ReadAsStringAsync y CopyToAsync siempre devuelven los bytes exactamente como llegaron.
leaveOpen, por lo que desecharlo no afecta a la respuesta; si no está comprimida, se obtiene el propio flujo de contenido HTTP, por lo que desecharlo cierra el cuerpo. En ambos casos, ClickHouseRawResult es responsable de la respuesta: no llame a sus otros miembros de lectura después de desechar el flujo. Siempre debe desechar ClickHouseRawResult, y eso basta para liberar tanto la respuesta como cualquier decodificador creado aquí (los decodificadores mantienen búferes reutilizables). Por tanto, el await using anterior es opcional, aunque puede conservarse sin problemas. Las llamadas secuenciales repetidas devuelven el mismo flujo; no es seguro usar este tipo de forma concurrente.
Consulte Select_007_ResponseCompression.cs para ver un ejemplo ejecutable.
Compresión de las solicitudes de inserción
Zstd es el códec predeterminado para las inserciones:InsertOptions.Compressor se inicializa con ZstdCompressor.Default,
que corresponde a zstd con nivel de compresión 3. Asígnale otro compresor para cambiar el códec, o null para enviar el
cuerpo sin comprimir.
Default y de un constructor que acepta un nivel
y el tamaño del búfer de escritura:
Comparta las instancias de los compresores. Cada
Default es una instancia compartida, y los cuatro compresores
pueden usarse de forma segura desde varios threads a la vez. Esto ocurre cuando
InsertOptions.MaxDegreeOfParallelism es mayor que 1, ya que cada operación de inserción usa un compresor por
lote. Ninguno implementa IDisposable. Cree una instancia propia y reutilícela, tal
como se hace con Default.IClickHouseCompressor es público y basta con que una implementación proporcione dos miembros:
Content-Encoding que indiques. Los demás miembros —
Decompress, MethodByte, MaxEncodedLength, Encode y Decode — tienen implementaciones
predeterminadas que lanzan NotSupportedException, así que sobrescribe solo los que necesite tu códec.
Implementa Decompress para descomprimir también los cuerpos de las respuestas, además de comprimir las solicitudes, y haz que el flujo que devuelve lance
InvalidDataException cuando un cuerpo esté dañado o tenga un formato incorrecto.
InsertOptions.Compressor solo se aplica a las inserciones binarias. Los demás cuerpos de las solicitudes del driver se comprimen según otras reglas y ninguno pasa por él:
- Cada solicitud que contiene texto SQL (
ExecuteReaderAsync,ExecuteScalarAsync,ExecuteNonQueryAsync,QueryAsync<T>,ExecuteRawResultAsync, la capa ADO.NET) envía su sentencia conContent-Encoding: gzipsiempre queUseCompressionseatrue, es decir, de forma predeterminada. El códec no se puede configurar:AcceptEncodingsolo afecta a la respuesta, así que las opciones son gzip o ninguna compresión.Compression=falseenvía la sentencia sin comprimir. Las sentencias son pequeñas, por lo que rara vez es necesario prestar atención a esto, pero conviene saberlo al inspeccionar las solicitudes en un proxy o una captura de paquetes. - Un cuerpo multipart — una consulta cuyos parámetros se envían como datos de formulario (
UseFormDataParameters=true) — siempre se envía sin comprimir, independientemente del valor deUseCompression. - Una carga sin procesar (
InsertRawStreamAsync,PostStreamAsync) tiene su propio indicador para cada llamada y no utiliza niUseCompressionniInsertOptions.Compressor: usa gzip cuando el indicador está activado y se envía sin comprimir en caso contrario. Ten en cuenta que el parámetrouseCompressiondeInsertRawStreamAsynctienetruecomo valor predeterminado, por lo que una carga sin procesar se comprime con gzip a menos que pasesfalse, incluso si el cliente tieneCompression=false.
Ajuste de la compresión
La compresión reduce el volumen de datos a costa de un mayor uso de CPU. Que esto compense depende casi por completo de la velocidad de la conexión en comparación con la velocidad del códec. No hay ningún ajuste que sea adecuado para todos los casos.La cifra decisiva
La compresión merece la pena siempre que el códec sea más rápido que la red. En la ruta de lectura, ese umbral es más bajo de lo que suele pensarse, porque ClickHouse comprime las respuestas HTTP en un único thread en el búfer de salida. En las mediciones realizadas con un servicio de ClickHouse Cloud de 16 vCPU (hits, RowBinary, nivel 3), el servidor genera datos comprimidos a unos 100-200 MB/s.
Por tanto, con un resultado grande y si se procesa una sola consulta a la vez, la compresión deja de compensar en torno a los 100 MB/s. Un único flujo HTTPS
dentro de una misma región de la nube suele superar esa velocidad, mientras que una conexión que atraviese la Internet pública, una VPN o el límite entre regiones suele quedar por debajo.
La compresión sigue siendo útil en la ruta de inserción a velocidades de enlace más altas, porque el client comprime en un núcleo propio y suele hacerlo más rápido que el servidor al comprimir las respuestas.
Guía orientativa según el entorno de despliegue
Hay tres aspectos que esta tabla no refleja:
- Coste del tráfico de salida: si pagas por la transferencia de datos, los bytes tienen un coste más allá de la latencia, lo que inclina la balanza hacia una mayor compresión, independientemente de la velocidad de la conexión.
- Resultados pequeños: todo lo anterior se aplica a cargas útiles grandes. En las respuestas pequeñas, el códec apenas influye y predomina la sobrecarga de cada solicitud.
- Las inserciones en paralelo elevan los umbrales de inserción. Todas las cifras de caudal anteriores corresponden a un único thread. El valor predeterminado de
InsertOptions.MaxDegreeOfParallelismes1, pero, al aumentarlo, los lotes se comprimen de forma concurrente. Por tanto, la tasa de codificación total del client crece aproximadamente en proporción al número de núcleos que se le asignan. Así, en una conexión rápida, puede seguir mereciendo la pena comprimir una inserción en paralelo incluso a velocidades a las que ya no compensaría comprimirla con un solo thread. Toma las filas sobre inserciones de la tabla como un límite inferior y, si ya procesas lotes en paralelo, repite las pruebas antes de concluir que tu conexión es demasiado rápida para que la compresión resulte útil.
Elegir un códec
Niveles
La compresión de las respuestas se controla mediante una única configuración del servidor,http_zlib_compression_level, que se aplica a todos los códecs HTTP, no solo a zlib. Su valor predeterminado es 3.
No la cambie a menos que las mediciones justifiquen hacerlo. Por encima del valor predeterminado, la reducción de tamaño es mínima en relación con el aumento del uso de CPU (con zstd, pasar de 3 a 6 aproximadamente duplica el uso de CPU del servidor y reduce los bytes en torno a un 14 %), mientras que el rendimiento de br se degrada drásticamente. En el nivel 1, en cambio, la situación cambia de forma notable: lz4 consume mucha menos CPU y zstd pierde su ventaja frente a él en ese aspecto. Si lo necesita, ajuste el nivel por consulta:
Determinar su propio punto de equilibrio
La forma más rápida de elegir el códec y el nivel de compresión óptimos es cronometrar la misma consulta con varios códecs y comparar los tiempos.ProfileEvents en system.query_log — establezca
QueryOptions.QueryId para poder localizar la fila:
LIMIT n sin ORDER BY devuelve filas distintas
en cada ejecución, por lo que cada repetición comprime datos diferentes y las proporciones dejan de ser significativas. Haga la comparación
con un conjunto de resultados fijo.
Inserción desde un flujo sin procesar
UseInsertRawStreamAsync para insertar datos directamente desde flujos de archivos o de memoria en formatos como CSV, JSON y Parquet, o en cualquier otro formato compatible con ClickHouse.
Inserción desde un archivo CSV:
Consulte la documentación de configuración de formatos para conocer las opciones que permiten controlar el comportamiento de la ingestión de datos.
Más ejemplos
Para ver más ejemplos prácticos de uso, consulte el directorio de ejemplos del repositorio de GitHub.ADO.NET
La biblioteca ofrece compatibilidad completa con ADO.NET a través deClickHouseConnection, ClickHouseCommand y ClickHouseDataReader. Esta API es necesaria para integrar ORM (Dapper, Linq2db) y para utilizar las abstracciones estándar de .NET para bases de datos.
Gestión de la duración de las conexiones con ClickHouseDataSource
Cree siempre las conexiones a partir de unClickHouseDataSource para garantizar una gestión adecuada de su duración y la agrupación de conexiones. DataSource gestiona internamente un único ClickHouseClient, y todas las conexiones comparten su pool de conexiones HTTP.
Uso de ClickHouseCommand
Cree comandos desde una conexión para ejecutar consultas SQL:ExecuteNonQueryAsync()- Para sentencias INSERT, UPDATE, DELETE y DDLExecuteScalarAsync()- Devuelve el valor de la primera columna de la primera filaExecuteReaderAsync()- Devuelve unClickHouseDataReaderpara recorrer los resultados
Uso de ClickHouseDataReader
ClickHouseDataReader proporciona acceso tipado a los resultados de las consultas:
Lectura del valor ordinal de un enum
Una columnaEnum8 o Enum16 se materializa como una etiqueta: GetFieldType devuelve string, y
GetString, GetValue y GetFieldValue<string> devuelven esa etiqueta. Los accesores numéricos
lanzan InvalidCastException al usarse con una columna de tipo enum, ya que el valor almacenado es una cadena.
Use TryGetEnumOrdinal para obtener el número asociado a la etiqueta:
true y asigna un valor a value si la columna es de tipo Enum8/Enum16 o si es una columna
Nullable(Enum...) cuya celda no es NULL. Devuelve false y asigna 0 a value si la celda es NULL o si
la columna no es de tipo enum. El ordinal es el
valor con signo recibido en formato wire, por lo que puede ser negativo; además, el ordinal de un Enum16 puede ocupar más de un
byte.
Buenas prácticas
Duración y reutilización de las conexiones
ClickHouse.Driver utiliza System.Net.Http.HttpClient internamente. HttpClient mantiene un pool de conexiones por punto de conexión. Por tanto:
- Las sesiones de la base de datos se multiplexan a través de conexiones HTTP gestionadas por el pool de conexiones.
- El pool reutiliza automáticamente las conexiones HTTP.
- Las conexiones pueden permanecer abiertas después de liberar los objetos
ClickHouseClientoClickHouseConnection.
Gestión de DateTime
-
Use UTC siempre que sea posible. Almacene las marcas de tiempo en columnas
DateTime('UTC')y useDateTimeKind.Utcen su código. Así evitará ambigüedades con las zonas horarias. -
Use
DateTimeOffsetpara gestionar explícitamente la zona horaria. Siempre representa un instante concreto e incluye el desfase respecto de UTC. -
Especifique la zona horaria en las indicaciones de tipo SQL. Si usa parámetros con valores DateTime
Unspecifiedpara columnas que no están en UTC, incluya la zona horaria en la sentencia SQL:
Inserciones asíncronas
Las inserciones asíncronas trasladan del cliente al servidor la responsabilidad de agrupar los datos en lotes. En lugar de agruparlos en el cliente, el servidor almacena temporalmente los datos entrantes y los escribe en el almacenamiento cuando se alcanzan los umbrales configurados. Esto resulta útil en escenarios de alta concurrencia, como las cargas de trabajo de observabilidad, en las que muchos agentes envían cargas útiles pequeñas. Habilite las inserciones asíncronas medianteCustomSettings o la cadena de conexión:
wait_for_async_insert):
Parámetros de configuración principales:
Sesiones
Habilite las sesiones solo cuando necesite funciones del lado del servidor que mantengan el estado, por ejemplo:- Tablas temporales (
CREATE TEMPORARY TABLE) - Mantener el contexto de las consultas entre varias sentencias
- Configuración a nivel de sesión (
SET max_threads = 4)
Tipos de datos compatibles
ClickHouse.Driver admite todos los tipos de datos de ClickHouse. Las tablas siguientes muestran la correspondencia entre los tipos de ClickHouse y los tipos nativos de .NET al leer datos de la base de datos.
Correspondencia de tipos: lectura desde ClickHouse
Tipos de enteros
Tipos de coma flotante
Tipos decimales
La conversión de tipos decimales se controla mediante la configuración UseCustomDecimals.
Tipo booleano
Tipos String
De forma predeterminada, las columnas
String y FixedString(N) se devuelven como string. Para leerlas como byte[], establezca ReadStringsAsByteArrays=true en la cadena de conexión. Esto resulta útil si almacena datos binarios que podrían no tener una codificación UTF-8 válida.La configuración también afecta a las cadenas anidadas en otros tipos: Array(String) se lee como byte[][]
y Map(String, String) como Dictionary<byte[], byte[]>, incluidas las claves. La única excepción son las
columnas JSON, cuyos valores de cadena siempre se tratan como texto; consulte Tipo JSON.Tipos de fecha y hora
ClickHouse almacena internamente los valores
DateTime y DateTime64 como marcas de tiempo Unix (segundos o fracciones de segundo transcurridos desde la época Unix). Aunque el almacenamiento siempre se realiza en UTC, las columnas pueden tener una zona horaria asociada que determina cómo se muestran e interpretan los valores.
Al leer valores DateTime, la propiedad DateTime.Kind se establece según la zona horaria de la columna:
En las columnas con una zona horaria distinta de UTC, el valor
DateTime devuelto representa la hora local de esa zona horaria. Use ClickHouseDataReader.GetDateTimeOffset() para obtener un DateTimeOffset con el desplazamiento horario correspondiente:
DateTime en lugar de DateTime('Europe/Amsterdam')), el controlador devuelve un DateTime con Kind=Unspecified. Así se conserva la hora tal como se almacenó, sin suponer ninguna zona horaria.
Si necesita que se tenga en cuenta la zona horaria en columnas que no la especifican, puede:
- Especificar una zona horaria en la definición de cada columna:
DateTime('UTC')oDateTime('Europe/Amsterdam') - Aplicar la zona horaria después de leer los datos.
Tipo JSON
El tipo de retorno de las columnas JSON depende de la configuración
JsonReadMode:
-
Binary(predeterminado): DevuelveSystem.Text.Json.Nodes.JsonObject. Permite acceder a los datos JSON de forma estructurada, pero convierte los tipos especializados de ClickHouse (como las direcciones IP, los UUIDs y los valores decimales grandes) a sus representaciones en cadena dentro de la estructura JSON. -
String: Devuelve el JSON sin procesar comostring. Conserva la representación JSON exacta que proporciona ClickHouse, lo que resulta útil si necesita pasar el JSON a otro sistema sin analizarlo o encargarse de la deserialización.
None es un tercer modo. Lee los datos exactamente igual que Binary, pero no envía ninguna configuración del servidor junto con la
consulta; úselo en una conexión que no permita establecerla.
Una ruta declarada en el tipo de columna es una ruta tipada; cualquier otra ruta del documento es una
ruta dinámica. La diferencia entre ambas se manifiesta cuando un valor es nulo.
Una ruta tipada siempre aparece en el JsonObject. Si se declara como Nullable(T) o Dynamic, se devuelve
como un valor nulo de JSON tanto si el valor almacenado es nulo como si el documento no contiene esa ruta; ambos
casos son indistinguibles:
JSON(x String)
da {"x":""} y JSON(x Int64) da {"x":0}.
Una ruta dinámica cuyo valor es nulo se elimina por completo del objeto, por lo que ContainsKey devuelve
false para esa ruta. Al leer {"x":null} de una columna JSON sin más, se obtiene {}.
Las rutas tipadas anidadas crean sus rutas superiores, por lo que JSON(a.b Nullable(Int64)) produce {"a":{"b":null}}
incluso si el documento está vacío.
Esta es la representación que genera el propio servidor, por lo que los modos
Binary y String ahora coinciden. Antes de la versión 1.4.0, una
ruta tipada con un valor nulo se eliminaba del JsonObject, de modo que {"x":null} se leía como
{}; en una ruta anidada como JSON(a.b Nullable(Int64)), desaparecía todo el subárbol a.JSON siempre se devuelven como texto, sea cual sea
el valor de ReadStringsAsByteArrays: JsonValue no tiene una representación como array de bytes, por lo que un byte[] se
representaría en base64. Esto se aplica a String, FixedString y a los tipos encapsulados en
LowCardinality, Nullable o SimpleAggregateFunction, así como a las cadenas dentro de Array y Map,
incluidas las claves de los mapas.
Un array de bytes cuyo tipo no puede determinar el lector JSON sí se representa en base64: una
ruta tipada como
Variant o Dynamic contiene un valor cuyo tipo solo se conoce para cada fila, por lo que una cadena
en Variant(Array(UInt8), String) se devuelve codificada en base64. Esto ocurre con ambas configuraciones.Si el tipo de una clave de un mapa JSON no es exactamente String —por
ejemplo, Map(LowCardinality(String), String)—, se lanza NotSupportedException.JSON(a Int64, a.b Int64). Ambas rutas están presentes en cada fila, por lo que el servidor
genera una fila con una clave duplicada: {"a":0,"a":{"b":7}}. Un JsonObject no puede contener dos valores
para una misma clave, así que JsonReadMode.Binary lanza una SerializationException que identifica ambas rutas. Lo
mismo ocurre si el valor es un Map, como en JSON(a Map(String, Int64)), al leer una fila que
también contiene una ruta dinámica a.b.
Esto solo ocurre cuando ambas rutas contienen un valor en la misma fila. Si una de ellas no contiene nada —un valor nulo,
un objeto vacío o un subárbol cuyos valores son todos nulos—, prevalece la que contiene datos,
sin importar cuál envíe primero el servidor. Por tanto, cuando las rutas superpuestas se declaran con tipos Nullable, solo una aporta un valor en cada fila y la columna se lee sin
errores: JSON(a Nullable(Int64), a.b Nullable(Int64)) produce {"a":5} y {"a":{"b":7}}, como
se esperaba.
Lea la columna con JsonReadMode.String para obtener el texto JSON enviado por el servidor sin cambios, incluida la clave
duplicada.
Configure AllowDuplicateJsonKeys para seguir leyendo la columna como un JsonObject sin que se lance una excepción. El
controlador conserva entonces el último de los dos valores que aparecen en la fila y descarta el otro, por lo que se
pierde información: JSON(a Int64, a.b Int64) con {"a.b":7} se lee como {"a":0}. Si una ruta
contiene un valor y su ruta padre contiene un escalar o un array, se sigue lanzando una excepción, porque no se puede
insertar un subárbol en ninguno de ellos.
Tipo Map
Un
Map(K, V) de ClickHouse se almacena físicamente como un Array(Tuple(K, V)) y puede contener varias entradas con la misma clave. Un Dictionary no admite claves duplicadas; por eso, en el modo predeterminado, solo se conserva el último valor de cada clave repetida y se descartan los pares anteriores. La opción MapReadMode determina la representación:
-
Dictionary(predeterminado): DevuelveDictionary<K, V>. -
KeyValuePairs: DevuelveList<KeyValuePair<K, V>>en el orden en que el servidor envió los pares, de modo que se conservan todos, incluidos los que tienen claves repetidas.
Map, por lo que también afecta a GetFieldValue<T>, a los tipos del esquema que devuelve el controlador y a la correspondencia con las propiedades de POCO. Se aplica en cualquier punto del árbol de tipos de una columna donde aparezca un mapa, incluidos Array(Map(...)), Map(K, Map(...)), Tuple(..., Map(...)) y Dynamic.
Ambas representaciones se aceptan al escribir, independientemente del modo; consulta escritura de mapas.
Otros tipos
En cada fila, los tipos Dynamic y Variant se convierten al tipo correspondiente al valor subyacente real.
Tipos de geometría
Geometry es un tipo Variant que puede contener cualquiera de los tipos de geometría. Cada valor se convierte al tipo correspondiente.
Correspondencia de tipos: escritura en ClickHouse
Al insertar datos, el controlador convierte los tipos de .NET en sus tipos correspondientes de ClickHouse. Las siguientes tablas muestran qué tipos de .NET se aceptan para cada tipo de columna de ClickHouse.Tipos enteros
Tipos de coma flotante
Tipo booleano
Tipos String
Tipos de fecha y hora
Valores fuera de rangoEn la ruta de escritura binaria, los valores de
Date, Date32, DateTime y DateTime32 que quedan fuera del rango admitido generan una excepción ArgumentOutOfRangeException al ejecutar Write, que indica el tipo de columna y el rango admitido. Antes, los valores fuera de rango podían truncarse sin aviso al convertirse en enteros de 32 bits y luego ser reinterpretados por el servidor, lo que producía marcas de tiempo válidas, pero incorrectas.DateTime.Kind al escribir valores:
Los valores de
DateTimeOffset siempre conservan el instante exacto.
Ejemplo: DateTime en UTC (se conserva el instante)
DateTimeKind.Utc o DateTimeOffset en todas las operaciones con DateTime. Así, su código funcionará de forma coherente independientemente de la zona horaria del servidor, del cliente o de la columna.
Parámetros HTTP frente a copia masiva
Existe una diferencia importante entre la vinculación de parámetros HTTP y la copia masiva al escribir valores DateTimeUnspecified:
La copia masiva conoce la zona horaria de la columna de destino e interpreta correctamente los valores Unspecified según esa zona horaria.
Los parámetros HTTP no detectan automáticamente la zona horaria de la columna. Debe especificarla en la indicación de tipo SQL:
Tipos decimales
Tipo JSON
La configuración
JsonWriteMode determina cómo se escribe JSON:
-
String(predeterminado): Aceptastring,JsonObject,JsonNodeo cualquier objeto. Todas las entradas se serializan medianteSystem.Text.Json.JsonSerializery se envían como cadenas JSON para que el servidor las analice. Es el modo más flexible y no requiere registrar los tipos. -
Binary: Solo acepta tipos POCO registrados. Los datos se convierten al formato JSON binario de ClickHouse en el cliente, con compatibilidad total con indicaciones de tipo. Antes de usar este modo, es necesario llamar aconnection.RegisterJsonSerializationType<T>(). Si se escriben valoresstringoJsonNodeen este modo, se lanzaArgumentException.
JSON(id UInt64, price Decimal128(2))), el controlador las utiliza para serializar los valores sin alterar sus tipos. Así se conserva la precisión de tipos como UInt64, Decimal, UUID y DateTime64, que de otro modo la perderían al serializarse como JSON genérico.
Los POCO se pueden escribir en columnas JSON de dos maneras, según el valor de JsonWriteMode:
Modo de cadena (predeterminado): Los POCO se serializan con System.Text.Json.JsonSerializer. No es necesario registrar los tipos. Es la opción más sencilla y funciona con objetos anónimos.
Modo binario: Los POCO se serializan con el formato JSON binario del controlador, que admite indicaciones de tipo completas. Los tipos deben registrarse con connection.RegisterJsonSerializationType<T>() antes de usarlos. Este modo permite asignar rutas JSON personalizadas mediante atributos:
-
[ClickHouseJsonPath("path")]: Asigna una propiedad a una ruta JSON personalizada. Resulta útil para estructuras anidadas o cuando el nombre de la propiedad difiere de la clave JSON deseada. Solo funciona en el modo binario. -
[ClickHouseJsonIgnore]: Excluye una propiedad de la serialización. Solo funciona en el modo binario.
UserId solo coincidirá con una indicación definida como UserId, no como userid. Esto es coherente con el comportamiento de ClickHouse, que permite que rutas como userName y UserName coexistan como campos distintos.
Limitaciones (solo en Binary mode):
- Los tipos POCO deben registrarse en la conexión mediante
connection.RegisterJsonSerializationType<T>()antes de serializarlos. Si se intenta serializar un tipo no registrado, se produce una excepciónClickHouseJsonSerializationException. - Las propiedades de diccionario y array/lista necesitan indicaciones de tipo en la definición de la columna para serializarse correctamente. Si no se proporcionan, utilice String mode.
- Los valores nulos de las propiedades POCO solo se escriben si la ruta tiene una indicación de tipo
Nullable(T)en la definición de la columna. ClickHouse no permite tiposNullableen rutas JSON dinámicas, por lo que se omiten las propiedades nulas sin indicación de tipo. - Los atributos
ClickHouseJsonPathyClickHouseJsonIgnorese ignoran en String mode (solo funcionan en Binary mode).
Otros tipos
Tipos de geometría
No se admiten para escritura
Manejo de tipos anidados
Los tipos anidados de ClickHouse (Nested(...)) se pueden leer y escribir mediante la semántica de los arrays.
Registro y diagnóstico
El client .NET de ClickHouse se integra con las abstracciones deMicrosoft.Extensions.Logging para ofrecer un registro ligero que se habilita solo si se solicita. Cuando está habilitado, el controlador emite mensajes estructurados sobre el ciclo de vida de la conexión, la ejecución de comandos, las operaciones de transporte y las inserciones masivas. El registro es totalmente opcional: las aplicaciones que no configuran un logger siguen funcionando sin sobrecarga adicional.
Inicio rápido
Uso de appsettings.json
Puede configurar los niveles de registro con la configuración estándar de .NET:Uso de la configuración en memoria
También puede configurar en el código el nivel de detalle de los logs por categoría:Categorías y emisores
El controlador utiliza categorías específicas que permiten ajustar el nivel de log de cada componente:Ejemplo: Diagnóstico de problemas de conexión
- La selección de la fábrica de clientes HTTP (pool de conexiones predeterminado o conexión única)
- La configuración del controlador HTTP (SocketsHttpHandler o HttpClientHandler)
- La configuración del pool de conexiones (MaxConnectionsPerServer, PooledConnectionLifetime, etc.)
- La configuración de los tiempos de espera (ConnectTimeout, Expect100ContinueTimeout, etc.)
- La configuración de SSL/TLS
- Los eventos de apertura y cierre de conexiones
- El seguimiento del ID de sesión
Modo de depuración: tracing de red y diagnóstico
Para ayudar a diagnosticar problemas de red, la biblioteca del controlador incluye un proceso auxiliar que permite realizar tracing de bajo nivel de los componentes internos de red de .NET. Para activarlo, debe pasar una LoggerFactory con el nivel configurado como Trace y establecer EnableDebugMode en true (o activarlo manualmente mediante la claseClickHouse.Driver.Diagnostic.TraceHelper). Los eventos se registrarán en la categoría ClickHouse.Driver.NetTrace. Advertencia: esto generará logs extremadamente detallados y afectará al rendimiento. No se recomienda activar el modo de depuración en producción.
OpenTelemetry
El controlador incluye compatibilidad con el trazado distribuido de OpenTelemetry a través de la API de .NETSystem.Diagnostics.Activity. Cuando se habilita, el controlador genera spans para las operaciones de base de datos que pueden exportarse a sistemas de observabilidad como Jaeger o el propio ClickHouse (mediante el OpenTelemetry Collector).
Habilitar el trazado
En las aplicaciones ASP.NET Core, agregue elActivitySource del controlador de ClickHouse a la configuración de OpenTelemetry:
Atributos de los spans
Cada span incluye atributos estándar de OpenTelemetry para bases de datos, además de estadísticas de las consultas específicas de ClickHouse que facilitan la depuración.Opciones de configuración
Controle el comportamiento del trazado medianteClickHouseDiagnosticsOptions:
Configuración de TLS
Al conectarse a ClickHouse por HTTPS, puede configurar TLS/SSL de varias formas.Validación personalizada de certificados
En entornos de producción que requieran una lógica de validación de certificados personalizada, proporcione su propia instancia deHttpClient con un controlador ServerCertificateCustomValidationCallback configurado:
Consideraciones importantes al proporcionar un HttpClient personalizado
- Descompresión automática: mantenga
AutomaticDecompressiondesactivado. El controlador descomprime las respuestas por su cuenta, por lo que no es necesario activarlo. Además, activarlo puede causar problemas al enviar solicitudes: en el momento del envío, el handler también añade todos los algoritmos de su máscara al encabezadoAccept-Encodingsaliente. Esto amplía los algoritmos anunciados por el controlador y permite que ClickHouse responda con un códec que no se había solicitado. Consulte Descompresión de respuestas. - Tiempo de espera de inactividad: configure
PooledConnectionIdleTimeoutcon un valor inferior alkeep_alive_timeoutdel servidor (10 segundos para ClickHouse Cloud) para evitar errores causados por conexiones semiabiertas.
Optimización del rendimiento
En esta sección se explica cómo usar el client para obtener un rendimiento óptimo y qué opciones puede ajustar para adaptarlo a las necesidades de su caso de uso.De un vistazo
| Si | Haga esto | |---|---|---| | Lee filas en objetos POCO | UseQueryAsync<T>, no MapTo<T> |
| Realiza inserciones masivas | Aumente InsertOptions.BatchSize |
| Ejecuta una aplicación de consola o un proceso de trabajo que realiza muchas inserciones | Active Server GC |
| Lee resultados voluminosos a través de la red | Mantenga activada la compresión de las respuestas (activada de forma predeterminada) |
| Inserta datos a través de una conexión rápida | Pruebe InsertOptions.Compressor = null |
| Inserta datos repetidamente en la misma tabla | Use UseSchemaCache o ColumnTypes |
| Lee resultados muy voluminosos | Aumente ReadBufferSize |
Lectura: elija la ruta de materialización
Hay tres formas de obtener una fila de un resultado, y no todas tienen el mismo costo. Algunas encapsulan los valores como objetos, lo que aumenta las asignaciones de memoria y reduce el rendimiento.
Para una lectura de 1,000,000 filas y 105 columnas del conjunto de datos hits:
Los ORM usan la ruta rápida cuando emplean métodos de acceso tipados. linq2db registra
GetInt64,
GetDouble y GetDateTime para cada columna, lo que le permite leer sin boxing. El código que lee mediante
GetValue (incluido un resultado dynamic de Dapper) aplica boxing a cada valor. Si una consulta de un ORM se ejecuta con frecuencia
y lee mediante GetValue, use QueryAsync<T> para esa consulta.Inserción: tamaño de lote y paralelismo
El tamaño de lote es el factor que más influye en el caudal de inserción.InsertOptions.BatchSize tiene un valor predeterminado de
100 000 filas.
Utilice lotes grandes. En una inserción de 1 000 000 de filas, aumentar el tamaño de cada lote de 10 000 a 100 000 filas
dio estos resultados:
Si no puede controlar el tamaño de lote (por ejemplo, porque muchos productores envían pequeñas cantidades de filas por separado), utilice la inserción asíncrona y deje que el servidor agrupe las filas en lotes.
Envíos paralelos.
InsertOptions.MaxDegreeOfParallelism tiene un valor predeterminado de 1. Auméntelo para enviar
varios lotes a la vez. Esto resulta especialmente útil si la compresión está activada, ya que cada lote se comprime
en su propio thread. Las sesiones no son compatibles con las inserciones paralelas: desactívelas o mantenga
MaxDegreeOfParallelism = 1.
Elimine la consulta de comprobación del esquema. Cada llamada a InsertBinaryAsync envía primero una consulta SELECT ... WHERE 1=0
para determinar los tipos de columna. Consulte Cómo omitir la consulta de comprobación del esquema para evitar ese
viaje de ida y vuelta mediante ColumnTypes o UseSchemaCache.
La ruta de inserción sin boxing se aplica al formato
RowBinary predeterminado. RowBinaryWithDefaults debe
examinar cada valor para encontrar el marcador DBDefault, por lo que sigue utilizando la ruta más lenta.Compresión: los resultados varían según la dirección
La compresión reduce la cantidad de bytes transferidos a cambio de consumir CPU. Que compense depende de la dirección de la transferencia, del ancho de banda de la conexión con el servidor de ClickHouse, de la eficacia del algoritmo de compresión elegido con sus datos y de si paga por cada byte transferido. Lecturas: mantenga activada la compresión, salvo que el servidor se ejecute en la misma máquina. Este es el comportamiento predeterminado. En comparación con la transferencia sin compresión,zstd en el nivel 1
dio estos resultados:
Inserciones: mida el efecto antes de activar la compresión. El ahorro quizá no sea suficiente para justificarla. Tenga en cuenta también que la descompresión aumenta la carga del servidor; ese aumento es moderado con Zstd y LZ4, pero puede ser considerable con otros algoritmos (por ejemplo, Brotli).
Para desactivar la compresión de inserciones:
Búferes
ReadBufferSize establece el tamaño del búfer utilizado para leer las respuestas HTTP. Su valor predeterminado es 64 KiB.
El controlador obtiene este búfer de un grupo compartido y lo devuelve cuando libera el lector, por lo que
no necesita asignar uno por cada consulta. Aumente su tamaño para reducir la frecuencia con la que debe rellenarse el búfer al procesar
resultados grandes. El controlador mantiene un búfer por cada lector abierto simultáneamente, por lo que el consumo de memoria
aumenta tanto con el tamaño del búfer como con el número de lectores concurrentes.
Entorno de ejecución y recolección de basura
Active Server GC en aplicaciones que realizan muchas inserciones. Con el mismo código y la misma cantidad de bytes asignados, las inserciones con Workstation GC fueron hasta un 97 % más lentas que con Server GC.Server GC es una configuración orientada al caudal, no a la latencia. En las mismas mediciones, Server GC acumuló
menos de la mitad del tiempo total de pausa, pero cada pausa fue más larga
(percentil 95: 114.6 ms frente a 61.9 ms). Si su servicio es sensible a la latencia de las solicitudes más lentas, mida
ambos modos antes de elegir uno.
Latencia: reutilizar conexiones
Establecer una nueva conexión TCP y completar el handshake TLS lleva bastante tiempo. Reutilizar las conexiones reduce considerablemente la latencia de las consultas.- No cree un cliente por cada solicitud. Cada cliente nuevo con su propio
HttpClientcrea un nuevo grupo de conexiones y requiere otro handshake. Utilice un únicoClickHouseClientdurante toda la vida útil de la aplicación. Es seguro para subprocesos y está diseñado para usarse como singleton. - Para ADO.NET y los ORM, utilice
ClickHouseDataSourcepara que todas las conexiones compartan el mismo grupo.
Mida el rendimiento usted mismo
En muchos casos, el rendimiento dependerá de la estructura de sus datos, de la velocidad de la conexión con el servidor, de si prefiere aumentar el uso de CPU del client para reducir el del servidor (o viceversa), de las limitaciones de su hardware, etc. Por eso, le recomendamos que mida el rendimiento con sus propios datos y en su entorno. Para conocer la parte del trabajo que realiza el servidor, establezcaQueryOptions.QueryId y consulte los contadores:
Compatibilidad con ORM
Los ORM requieren la API ADO.NET (ClickHouseConnection). Para gestionar correctamente la duración de las conexiones, créelas a partir de un ClickHouseDataSource:
Dapper
ClickHouse.Driver funciona con Dapper. El controlador convierte automáticamente la sintaxis @parameter de Dapper a la sintaxis nativa {parameter:Type} de ClickHouse e infiere los tipos a partir de los valores de .NET.
Utilice ClickHouseDataSource para gestionar correctamente la duración de la conexión:
Formas de pasar parámetros
Dapper admite todas las formas estándar de pasar parámetros: Objetos anónimos:DynamicParameters (a partir de un diccionario o un objeto anónimo):
Consultas que devuelven objetos POCO
Dapper relaciona las columnas con las propiedades por nombre (sin distinguir entre mayúsculas y minúsculas):Sintaxis de parámetros nativa de ClickHouse
Si necesita controlar explícitamente el tipo, use la sintaxis{param:Type} de ClickHouse directamente en SQL y proporcione los valores de los parámetros mediante un Dictionary<string, object>. No combine las sintaxis @param y {param:Type} para un mismo parámetro.
WHERE IN
La expansión nativa de IN en Dapper funciona:WHERE id IN (@Ids1, @Ids2, @Ids3), y el controlador convierte cada parámetro expandido.
La función has() de ClickHouse también funciona con un parámetro de tipo Array:
Handlers de tipos personalizados
Algunos tipos de ClickHouse, comoITuple, BigInteger y ClickHouseDecimal, requieren registrar handlers al inicio:
Dapper.Contrib
GetAll<T>() y Get<T>(id) funcionan. Insert<T>(), en cambio, no: genera sintaxis propia de SQL Server (SCOPE_IDENTITY, []). Se recomienda utilizar el método nativo InsertBinaryAsync de ClickHouseClient en su lugar.
Limitaciones
Linq2db
Este controlador es compatible con linq2db, un ORM ligero y un proveedor de LINQ para .NET. Encontrará documentación detallada en el sitio web del proyecto. Ejemplo de uso: Cree unaDataConnection con el proveedor de ClickHouse:
BulkCopyAsync para insertar grandes volúmenes de datos de forma eficiente.
Entity Framework Core
El proveedor oficial de Entity Framework Core para ClickHouse. Asocie clases de C# a tablas de ClickHouse, haga consultas con LINQ e inserte datos medianteSaveChanges, todo ello con los patrones habituales de EF Core.
- NuGet:
ClickHouse.EntityFrameworkCore - Código fuente: GitHub
Este proveedor está en desarrollo activo. La versión actual admite consultas LINQ (incluidos JOIN, subconsultas y operaciones de conjuntos),
INSERT mediante SaveChanges / BulkInsertAsync, migraciones con DDL completo (CREATE / ALTER / DROP) y la configuración de motores de tabla específicos de ClickHouse. UPDATE / DELETE no son compatibles.Instalación
Inicio rápido
Defina la entidad y elDbContext y, después, haga consultas con LINQ:
Tipos compatibles
Use
ClickHouseDecimal (de ClickHouse.Driver.Numerics) en lugar de decimal si necesita toda la precisión de las columnas Decimal128/Decimal256: el tipo decimal de .NET admite como máximo entre 28 y 29 dígitos significativos.
Operaciones LINQ compatibles
Consultas:Where, OrderBy, Take, Skip, Select, First, Single, Any, All, Count, Distinct, AsNoTracking
GROUP BY y agregaciones: GroupBy con Count, LongCount, Sum, Average, Min y Max; también admite HAVING (.Where() después de .GroupBy()), varias agregaciones en una misma proyección y OrderBy aplicado a resultados de agregaciones.
JOIN: Join (INNER) y patrones GroupJoin/SelectMany (LEFT y CROSS). LEFT JOIN devuelve null para las filas sin coincidencia (consulte Semántica de los valores nulos en LEFT JOIN más abajo).
Subconsultas: Contains / IN correlacionados, Any / EXISTS, All y subconsultas escalares en proyecciones.
Operaciones de conjuntos: Concat (→ UNION ALL), Union (→ UNION DISTINCT), Intersect, Except.
Colecciones locales en línea: las uniones y Contains con colecciones en memoria (int[], List<T>, etc.) se traducen en una serie de operaciones UNION.
Métodos de cadenas: Contains, StartsWith, EndsWith, IndexOf, Replace, Substring, Trim/TrimStart/TrimEnd, ToLower, ToUpper, Length, IsNullOrEmpty, Concat (y el operador +).
Funciones matemáticas: los métodos estándar de Math y MathF se traducen a las funciones equivalentes de ClickHouse: aritméticas, logarítmicas, trigonométricas y auxiliares.
El provider añade automáticamente set_join_use_nulls=1 a todas las rutas de conexión para que el comportamiento de JOIN coincida con el que espera Entity Framework.
Si el servidor de ClickHouse o el perfil impiden cambiar esta configuración (por ejemplo, un perfil con readonly=1), desactive esta opción con:
0 / "" en lugar de usar == null.
Inserción de datos
SaveChanges utiliza la API nativa InsertBinaryAsync del controlador, con codificación RowBinary y un cuerpo de solicitud comprimido, lo que resulta mucho más eficiente que el SQL parametrizado:
Added a Unchanged después de guardarse, como ocurre con cualquier otro provider de EF Core.
El tamaño del lote es configurable (valor predeterminado: 1000):
Inserción masiva
Para cargas de datos de alto caudal, useBulkInsertAsync en lugar de SaveChanges. Este método de extensión de DbContext prescinde por completo del seguimiento de cambios, la resolución de identidades y la gestión de estados de EF Core: llama directamente a InsertBinaryAsync del controlador con codificación RowBinary y un cuerpo de solicitud comprimido.
Por eso, resulta adecuado para cargar grandes conjuntos de datos si no necesita hacer un seguimiento de las entidades después de insertarlas:
IEnumerable<T>: las entidades se procesan una a una sin cargarlas todas en memoria. El valor devuelto indica cuántas filas se han insertado. Tras la inserción, las entidades no quedan asociadas al DbContext, por lo que no pasan del estado Added al estado Unchanged.
Enumeraciones
Las columnasEnum8/Enum16 de ClickHouse se pueden mapear a propiedades de tipo string o a tipos enum de C#. Al usar enumeraciones de C#, el provider convierte automáticamente entre la enumeración y su representación en cadena:
Conversiones de tipos personalizados
El sistemaValueConverter de EF Core permite convertir tipos personalizados en tipos que el provider ya admite. El provider nunca llega a trabajar con el tipo personalizado: EF Core lo convierte al pasar de una capa a otra.
Conversión por propiedad:
Anotaciones del tipo de columna
Para los tipos escalares comostring, int, DateTime, etc., el provider infiere automáticamente el tipo de ClickHouse. Para los tipos parametrizados y las envolturas, debe indicar el tipo de ClickHouse de forma explícita.
Uso de anotaciones de datos (atributos):
OnModelCreating:
Array(Nullable(Int32)) y LowCardinality(Nullable(String)): el provider elimina automáticamente las envolturas Nullable y LowCardinality en todos los niveles de anidamiento.
Columnas Variant y Dynamic
Las columnasVariant(T1, T2, ...) y Dynamic de ClickHouse se representan como object en .NET. Como object es demasiado genérico para inferir el tipo automáticamente, debe especificar el tipo de almacenamiento mediante .HasColumnType():
string, ulong, ulong[]).
Columnas JSON
El provider admite el tipo de columnaJson de ClickHouse y lo asigna a System.Text.Json.Nodes.JsonNode (opción principal) o a string (mediante un ValueConverter automático):
SaveChanges como con BulkInsertAsync:
string con un tipo de columna Json; el provider aplicará automáticamente un ValueConverter:
- No se traducen las rutas JSON —
entity.Data["name"]en LINQ no se convierte a la sintaxis SQLdata.namede ClickHouse. Filtre por columnas que no sean JSON y examine el contenido JSON en memoria. - Semántica de NULL — El tipo JSON de ClickHouse devuelve
{}(un objeto vacío) para los valores NULL, en lugar de SQL NULL. - Precisión de los enteros — ClickHouse almacena todos los enteros en JSON como
Int64. Al leerlos medianteJsonNode, useGetValue<long>()en lugar deGetValue<int>().
Motores de tabla
Configure los motores de tabla de ClickHouse y las cláusulas específicas de cada motor mediante la API fluidaToTable(name, t => ...). Si no se configura ningún motor, el provider usa MergeTree de forma predeterminada y determina ORDER BY a partir de la clave primaria de la entidad.
Cláusulas del motor:
WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings. Todas se añaden al constructor de motores que devuelve HasXxxEngine().
Funciones a nivel de columna: HasCodec, HasTtl, HasComment, HasDefault — todas se tienen en cuenta en las migraciones.
Índices de omisión de datos — mediante HasIndex(...).HasSkippingIndexType(...):
Migraciones
Flujo de trabajo estándar para las migraciones de EF Core:Limitaciones de las migraciones
Al margen de las migraciones, el provider tampoco admite todavía:
UPDATE/DELETE- Transacciones:
BeginTransactionno tiene efecto. ClickHouse no admite transacciones ACID. - Traducción de consultas con rutas JSON:
entity.Data["key"]en LINQ no se traduce a la sintaxis SQLdata.keyde ClickHouse. Filtre por columnas que no sean JSON y examine el JSON en memoria.
Limitaciones
Tuplas de 8 o más elementos con una tupla anidada en la última posición
Los tiposValueTuple de C# con más de 7 elementos utilizan un esquema de anidamiento generado por el compilador: el octavo argumento genérico (TRest) es un ValueTuple que contiene los elementos restantes. Por ejemplo, (int, int, int, int, int, int, int, string, string) se compila en ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>.
Esto genera una ambigüedad cuando una columna de ClickHouse contiene una tupla de 8 elementos y el último es, a su vez, una tupla; por ejemplo, Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Tuple(String, String)). El controlador no puede distinguir entre:
- Una tupla plana de 9 elementos (con el anidamiento de TRest generado por el compilador)
- Una tupla de 8 elementos cuyo último elemento es una
Tuple(String, String)anidada
ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>.
El controlador interpreta el octavo argumento como TRest (es decir, lo aplana), por lo que serializa incorrectamente la tupla de 8 elementos con una tupla anidada.
Esto afecta tanto a System.Tuple como a ValueTuple, ya que ambos utilizan el anidamiento de TRest cuando tienen más de 7 elementos. Las tuplas de 7 elementos o menos y aquellas cuyo último elemento no es una tupla no se ven afectadas.
Solución alternativa: Envuelva la tupla interna en un nivel adicional de anidamiento para que el controlador pueda distinguirla del anidamiento de TRest:
Columnas de tipo AggregateFunction
Las columnas de tipoAggregateFunction(...) no se pueden consultar ni insertar directamente.
Para insertar datos: