Crear un índice de texto
tokenizer. El argumento tokenizer especifica el tokenizador:
splitByNonAlphadivide las cadenas por caracteres ASCII no alfanuméricos (consulte también la función splitByNonAlpha).splitByString(S)divide las cadenas usando determinadas cadenas separadorasSdefinidas por el usuario (consulte también la función splitByString). Los separadores pueden especificarse mediante un parámetro opcional; por ejemplo,tokenizer = splitByString([', ', '; ', '\n', '\\']). Tenga en cuenta que cada cadena puede constar de varios caracteres (', 'en el ejemplo). La lista predeterminada de separadores, si no se especifica explícitamente (por ejemplo,tokenizer = splitByString), es un único espacio en blanco[' '].ngrams(N)divide las cadenas enN-gramas del mismo tamaño (consulte también la función ngrams). La longitud del ngram puede especificarse mediante un parámetro entero opcional entre 2 y 8; por ejemplo,tokenizer = ngrams(3). El tamaño predeterminado del ngram, si no se especifica explícitamente (por ejemplo,tokenizer = ngrams), es 3.arrayno realiza tokenización; es decir, cada valor de fila es un token (consulte también la función array).sparseGrams(min_length, max_length, min_cutoff_length)— utiliza el mismo algoritmo que la función sparseGrams para dividir una cadena en todos los ngrams demin_lengthy varios ngrams de mayor tamaño hastamax_length, inclusive. Si se especificamin_cutoff_length, solo se guardan en el índice los N-gramas con una longitud mayor o igual quemin_cutoff_length. A diferencia dengrams(N), que genera únicamente N-gramas de longitud fija,sparseGramsproduce un conjunto de N-gramas de longitud variable dentro del rango especificado, lo que permite una representación más flexible del contexto del texto. Por ejemplo,tokenizer = sparseGrams(3, 5, 4)generará 3-, 4- y 5-gramas a partir de la cadena de entrada y guardará solo los 4- y 5-gramas en el índice.
El tokenizador
splitByString aplica los separadores de división de izquierda a derecha.
Esto puede crear ambigüedades.
Por ejemplo, las cadenas separadoras ['%21', '%'] harán que %21abc se tokenice como ['abc'], mientras que, si se invierte el orden de ambas cadenas separadoras a ['%', '%21'], el resultado será ['21abc'].
En la mayoría de los casos, querrá que la coincidencia dé prioridad a los separadores más largos.
En general, esto puede hacerse pasando las cadenas separadoras en orden descendente de longitud.
Si las cadenas separadoras forman un código prefijo, pueden pasarse en cualquier orden.preprocessor. El argumento opcional preprocessor es una expresión que transforma la cadena de entrada antes de la tokenización.
Los casos de uso típicos del argumento preprocessor incluyen:
- Convertir las cadenas de entrada a minúsculas (o mayúsculas) para permitir coincidencias sin distinción entre mayúsculas y minúsculas; por ejemplo, lower, lowerUTF8. Consulte el primer ejemplo a continuación.
- La normalización UTF-8; por ejemplo, normalizeUTF8NFC, normalizeUTF8NFD, normalizeUTF8NFKC, normalizeUTF8NFKD, toValidUTF8.
- Eliminar o transformar caracteres o subcadenas no deseados; por ejemplo, extractTextFromHTML, substring, idnaEncode.
preprocessor debe transformar un valor de entrada de tipo String o FixedString en un valor del mismo tipo.
Ejemplos:
INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(col))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = substringIndex(col, '\n', 1))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(extractTextFromHTML(col))
preprocessor solo debe hacer referencia a la columna sobre la que se define el índice de texto.
No se permite usar funciones no deterministas.
Las funciones hasToken, hasAllTokens y hasAnyTokens usan preprocessor para transformar primero el término de búsqueda antes de tokenizarlo.
Por ejemplo:
Parámetros avanzados opcionales
Parámetros avanzados opcionales
Los valores predeterminados de los siguientes parámetros avanzados funcionan bien en prácticamente todas las situaciones.
No recomendamos cambiarlos.El parámetro opcional
dictionary_block_size (predeterminado: 128) especifica el tamaño de los bloques del diccionario en filas.El parámetro opcional dictionary_block_frontcoding_compression (predeterminado: 1) especifica si los bloques del diccionario usan front coding para la compresión.El parámetro opcional max_cardinality_for_embedded_postings (predeterminado: 16) especifica el umbral de cardinalidad por debajo del cual las posting lists deben integrarse en los bloques del diccionario.El parámetro opcional bloom_filter_false_positive_rate (predeterminado: 0.1) especifica la tasa de falsos positivos del bloom filter del diccionario.Uso de un índice de texto
Funciones compatibles
WHERE de una consulta SELECT:
= y !=
= (equals) y != (notEquals ) coinciden con el término de búsqueda completo especificado.
Ejemplo:
= y !=, pero la búsqueda por igualdad y desigualdad solo tiene sentido con el tokenizador array (lo que hace que el índice almacene los valores completos de cada fila).
IN y NOT IN
IN (in) y NOT IN (notIn) son similares a las funciones equals y notEquals, pero permiten buscar todos (IN) o ninguno (NOT IN) de los términos de búsqueda.
Ejemplo:
= y !=; es decir, IN y NOT IN solo tienen sentido cuando se usan con el tokenizador array.
LIKE, NOT LIKE y match
Actualmente, estas funciones usan el índice de texto para filtrar solo si el tokenizador del índice es
splitByNonAlpha o ngrams.LIKE like, NOT LIKE (notLike) y la función match con índices de texto, ClickHouse debe poder extraer tokens completos del término de búsqueda.
Ejemplo:
support en el ejemplo podría coincidir con support, supports, supporting, etc.
Este tipo de consulta es una consulta de subcadena y no puede acelerarse mediante un índice de texto.
Para aprovechar un índice de texto en consultas LIKE, el patrón de LIKE debe reescribirse de la siguiente manera:
support garantizan que el término pueda extraerse como token.
startsWith y endsWith
LIKE, las funciones startsWith y endsWith solo pueden usar un índice de texto si es posible extraer tokens completos del término de búsqueda.
Ejemplo:
clickhouse se considera un token.
support no es un token porque puede coincidir con support, supports, supporting, etc.
Para encontrar todas las filas que empiezan por clickhouse supports, termine el patrón de búsqueda con un espacio al final:
endsWith debe utilizarse con un espacio inicial:
hasToken and hasTokenOrNull
hasToken y hasTokenOrNull son las más eficientes para usar con el índice text.
hasAnyTokens and hasAllTokens
has
mapContains
mapContainsKey) busca coincidencias con un único token en las claves de un mapa.
Ejemplo:
operator[]
operator[] se puede usar con el índice de texto para filtrar claves y valores.
Ejemplo:
Array(T) y Map(K, V) con el índice de texto.
Ejemplos de compatibilidad del índice de texto para Array y Map.
Indexación de Array(String)
clickhouse) requiere examinar todas las entradas:
keywords que crea una estructura optimizada para la búsqueda, la cual preprocesa todas las palabras clave y permite búsquedas al instante:
Importante: Después de añadir el índice de texto, debes reconstruirlo para los datos existentes:
Indexación de Map
- Encuentra todos los logs con limitación de tasa:
- Encuentra todos los logs de una IP específica:
Importante: Después de agregar el índice de texto, debe reconstruirlo para los datos existentes:
- Encuentre todas las solicitudes sujetas a limitación de tasa:
- Busca todos los logs procedentes de una IP específica:
Implementación
Diseño del índice
- un diccionario que asigna cada token a una posting list, y
- un conjunto de posting lists, cada una de las cuales representa un conjunto de números de fila.
dictionary_block_size).
Un archivo de bloques del diccionario (.dct) consta de todos los bloques de diccionario de todos los gránulos de índice de una parte.
Archivo de gránulos de índice (.idx)
El archivo de gránulos de índice contiene, para cada bloque de diccionario, el primer token del bloque, su desplazamiento relativo en el archivo de bloques del diccionario y un bloom filter para todos los tokens del bloque.
Esta estructura de índice disperso es similar al índice disperso de clave primaria).
El bloom filter permite omitir bloques de diccionario de forma temprana si el token buscado no está contenido en un bloque de diccionario.
Archivo de posting lists (.pst)
Las posting lists de todos los tokens se organizan secuencialmente en el archivo de posting lists.
Para ahorrar espacio y, al mismo tiempo, permitir operaciones rápidas de intersección y unión, las posting lists se almacenan como roaring bitmaps.
Si la cardinalidad de una posting list es menor que 16 (configurable mediante el parámetro max_cardinality_for_embedded_postings), se incrusta en el diccionario.
Lectura directa
- La opción query_plan_direct_read_from_text_index (valor predeterminado: 1), que especifica si la lectura directa está habilitada en general.
- La opción use_skip_indexes_on_data_read (valor predeterminado: 1), que es otro requisito previo para la lectura directa. Ten en cuenta que, en las bases de datos de ClickHouse con compatibility < 25.10,
use_skip_indexes_on_data_readestá deshabilitado, por lo que debes aumentar el valor de la opción compatibility o ejecutarSET use_skip_indexes_on_data_read = 1explícitamente.
ALTER TABLE ... MATERIALIZE INDEX).
Funciones compatibles
La optimización de lectura directa admite las funciones hasToken, hasAllTokens y hasAnyTokens.
Estas funciones también pueden combinarse mediante los operadores AND, OR y NOT.
La cláusula WHERE también puede contener filtros adicionales que no sean funciones de búsqueda de texto (para columnas de texto u otras columnas); en ese caso, la optimización de lectura directa seguirá utilizándose, pero será menos eficaz (solo se aplica a las funciones de búsqueda de texto compatibles).
Para comprobar que una consulta utiliza la lectura directa, ejecuta la consulta con EXPLAIN PLAN actions = 1.
Como ejemplo, una consulta con lectura directa deshabilitado
query_plan_direct_read_from_text_index = 1
__text_index_<index_name>_<function_name>_<id>.
Si esta columna aparece, se utiliza la lectura directa.
Ejemplo: conjunto de datos de Hacker News
hackernews:
ALTER TABLE y añadiremos un índice de texto a la columna comment; luego lo materializaremos:
hasToken, hasAnyTokens y hasAllTokens.
Los siguientes ejemplos mostrarán la gran diferencia de rendimiento entre un escaneo estándar del índice y la optimización de lectura directa.
1. Uso de hasToken
hasToken comprueba si el texto contiene un token específico.
Buscaremos el token que distingue entre mayúsculas y minúsculas ‘ClickHouse’.
Lectura directa desactivada (escaneo estándar)
De forma predeterminada, ClickHouse usa el skip index para filtrar gránulos y luego lee los datos de la columna de esos gránulos.
Podemos simular este comportamiento desactivando la lectura directa.
2. Uso de hasAnyTokens
hasAnyTokens comprueba si el texto contiene al menos uno de los tokens indicados.
Buscaremos comentarios que contengan ‘love’ o ‘ClickHouse’.
Lectura directa desactivada (escaneo estándar)
3. Uso de hasAllTokens
hasAllTokens comprueba si el texto contiene todos los tokens indicados.
Buscaremos comentarios que contengan tanto ‘love’ como ‘ClickHouse’.
Lectura directa deshabilitada (Escaneo estándar)
Incluso con la lectura directa deshabilitada, el skip index estándar sigue siendo eficaz.
Reduce las 28.7M filas a solo 147.46K, pero aun así tiene que leer 57.03 MB de la columna.
4. Búsqueda compuesta: OR, AND, NOT, …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) sería la sintaxis más adecuada y eficiente.