3. Análisis del principio MergeTree de ClickHouse

Seis, análisis del principio MergeTree

6.1 Método de creación MergeTree

Cuando MergeTree escribe datos, los datos siempre se escribirán en el disco en forma de fragmentos de datos y los fragmentos de datos no se pueden modificar. Para evitar demasiados fragmentos, clickhouse fusiona periódicamente estos fragmentos de datos a través de un proceso en segundo plano, y los fragmentos de datos que pertenecen a la misma partición se sintetizarán en un nuevo fragmento.

MergeTree admite el índice de clave principal, la partición de datos, la copia de datos y la adopción de datos, y admite la operación ALTER.

创建方式

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]

(

name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],

name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],

...

INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,

INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2

) ENGINE = MergeTree()

ORDER BY expr

[PARTITION BY expr]

[PRIMARY KEY expr]

[SAMPLE BY expr]

[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]

[SETTINGS name=value, ...]


PARTITION BY 			
			分区键:表示表数据会以何种标准进行分区;默认all分区。
    		分区方式:单列、元组形式使用多列或者使用列表达式。
    		合理使用数据分区,可以有效减少查询时数据文件的扫描范围。

ORDER BY	
			排序键:用于指定在一个数据片段内,数据以何种标准排序;默认情况和主键相同。
			排序方式:单列、元组形式使用多列。ORDER BY (counterID,EventDate)为例,在单个数据片段中,数据首先以counterID排序,相同的counterID,在按照EventDate排序。

PAIMARY KEY
			主键:会按照主键字段生成一级索引,用于加速表查询;默认情况下,主键个ORDER BY相同。

SAMPLE BY
			抽样表达式:用于声明数据以何种标砖进行采样。

SETTINGS:index_granularity 
			index_granularity对于MergeTree表示索引粒度,默认值8192.(每隔8192行数据生成一条索引)

SETTINGS:index_granularity_bytes
			19.11前:clickhouse只支持固定大小的索引间隔,由index_granularity控制,默认8192。
			在新版本:自适应间隔大小。根据每一批次写入数据体量大小,动态划分间隔大小。数据体量由index_granularity_bytes控制,默认10M(10*1024*1024),设置为0不启动自适应功能。

SETTINGS:enable_mixed_granularity_parts
			是否开启自适应索引间隔,默认开启

SETTINGS:merge_with_ttl_timeout			数据TTL功能
			
SETTINGS:storage_policy					多路径存储策略
```



CREATE TABLE test20(ID String,Price Int32,Val Float64,EventTime Date) engine = MergeTree() PARTITION BY toYYYYMM(EventTime) ORDER BY ID

create table test (id UInt8,name String,age UInt8,shijian Date) engine = MergeTree() partition by toYYYYMM(shijian) order by id

6.2 Estructura de almacenamiento MergeTree

Los datos en el motor de la tabla MergeTree tienen almacenamiento físico y los datos se guardarán en el disco en forma de directorio de partición.

Inserte la descripción de la imagen aquí

[root@postgresql test 08:51:37]# tree test20

test20

├── 202005_1_3_1				分区目录

│   ├── checksums.txt			校验文件,保存余下各类文件的size大小及size的哈希值,校验数据完整性

│   ├── columns.txt				列信息文件。明文格式存储列字段名称和数据类型。

│   ├── count.txt				计数文件。明文记录当前数据分区目录下的数据总行数

│   ├── EventTime.bin			

│   ├── EventTime.mrk2

│   ├── ID.bin					数据文件。使用压缩格式存储(默认LZ4),存储某一列数据

│   ├── ID.mrk2

│   ├── minmax_EventTime.idx	分区键的索引文件,记录当前分区下分区字段对应原始数据的最小和最大值

│   ├── partition.dat			分区键(使用了PARTITION BY),保存前分区下分区表达式最终生成的值

│   ├── Price.bin				

│   ├── Price.mrk2				使用了自适应大小索引间隔的列标记文件,二进制存储,保存.bin文件中数据的偏移量信息

│   ├── primary.idx				一级索引文件,二进制格式存储。一张MergeTree()表只能声明一次一级索引(primary key或者order  by)

│   ├── Val.bin				

│   └── Val.mrk2

├── detached

└── format_version.txt

2 directories, 15 files

6.3 Partición de datos

Partición de datos: para datos locales, una segmentación vertical de datos

División de datos: para los clústeres de CK, los datos se cortan horizontalmente.

6.3.1 Reglas de particionamiento de datos

Las reglas de la partición de datos MergeTree están determinadas por ID, y la ID correspondiente a cada partición de datos está determinada por el valor de la clave de partición.

分区ID生成逻辑四种规则:

1.不指定分区键			分区ID默认为all

2.使用整型				 直接按照该整形的字符形式输出作为分区ID的取值

3.使用日期类型			按照YYYYMMDD格式化后字符形式输出作为分区ID取值

4.使用其他类型			分区键取值不属于整型,也不属于日期,例如String、Float则会通过128位Hash算法取其Hash值作为分区ID

Ejemplos de PartitionID bajo diferentes reglas

Tipos de Data de muestra Expresión de partición ID de partición
Sin clave de partición no todo
Entero 18,19,20 PARTICIÓN POR EDAD Partición 1:18; Partición 2:19; Partición 3:20
Entero 'A0', 'A1', 'A3' PARTICIÓN POR longitud (Código) Partición 1: 2
fecha 2019-02-01,019-06-11 PARTICIÓN POR EventTime Partición 1: 20190201; Partición 2: 20190611
fecha 2019-05-01,2019-06-11 PARTICIÓN POR toYYYYMM (EventTime) Partición 1: 201905; Partición 2: 201906
otro 'www.oldba.cn' PARTICIÓN POR URL Partición 1: 15r515rs15gr15615wg5e5h5548h3045h

6.3.2 Reglas de nomenclatura de directorios de particiones de datos

举例说明:

202005_1_3_1					此目录直观来看,采用时间年月作为分区ID,分三次插入到同一分区,并且三次插入完成之后的某个时刻进行了一次数据合并。

202005		PartitionID			分区目录ID

1			MinBlockNum			最小数据块编号 (默认和MaxBlockNum从1开始)

3			MaxBlockNum			最大数据块编号 (发生合并时取合并时的最大数据块编号)

1			Level				合并的层级,某个分区被合并过的次数或者这个分区的年龄。(每次合并自增+1)

6.3.3 Proceso de fusión de la partición de datos

Creación del directorio de la partición MergeTree: se crea durante la escritura de datos; después de la creación, el directorio también cambiará cuando se escriban o combinen datos.

En otras palabras: si una tabla no tiene datos, no habrá directorios de partición.

Proceso de fusión del directorio de particiones MergeTree:

Con cada escritura de datos (inserción), MergeTree generará un lote de nuevos directorios de partición (incluso si los datos escritos en diferentes lotes pertenecen a la misma partición, se generarán diferentes directorios de partición). En algún momento después de escribir, ClickHouse fusionará varios directorios que pertenecen a la misma partición en un nuevo directorio a través de una tarea en segundo plano. La partición antigua existente no se eliminará de inmediato, sino que se eliminará mediante una tarea en segundo plano en un momento posterior (8 minutos de forma predeterminada).

Las reglas de fusión del nuevo nombre de directorio:

MinBlockNum: toma el valor MinBlockNum más pequeño de todos los directorios de la misma partición.

MaxBlockNum: toma el valor MaxBlockNum más grande en todos los directorios de la misma partición.

Nivel: tome el valor máximo de Nivel en la misma partición y agregue 1.

create table test(id UInt32,name String,age UInt8,shijian DateTime) engine = MergeTree() PARTITION BY toYYYYMM(shijian) ORDER BY id

insert into test values (1,'张三',18,'2020-12-08')					t1时刻

insert into test values (2,'李四',19,'2020-12-08')					t2时刻
	
insert into test values (3,'王五',22,'2021-01-03')					t3时刻

insert into test values (2,'李四',19,now())							t4时刻

SELECT now()

┌───────────────now()─┐
│ 2020-12-08 11:36:42 │
└─────────────────────┘

		
按照上述规则未合并时的目录:
PARTITIONID 		202012
MinBlockNum			1
MaxBlockNmu			1
							对于新建分区,它们的值一样(来源表内全局自增的BlockNum),初始值为1,每次新建目录累计加1level				0				
    
    	202012_1_1_0												t1时刻的目录

		202012_2_2_0												t2时刻的目录

		202101_3_3_0												t3时刻的目录

		202012_4_4_0												t4时刻的目录

按照上述规则合并时的目录:

假设在t2~t3时刻之间发生了合并,那么此时只有一个目录:202012_1_2_1

假设在t3~t4时刻之间发生了合并,那么此时肯有两个目录:202012_1_2_1,202101_3_3_0

假设在t4时刻之后发生了合并,那么此时也肯定有两个目录:202012_1_4_2,202101_3_3_0

注意:
在创建完成之后的某个时刻进行合并,必须是相同分区才会合并,生成新的分区,同时将旧分区目录状态设置为非激活,然后在默认8分钟之后,删除非激活状态的分区目录。

6.4 Índice primario

MergerTree especifica el método de clave principal:

1. CLAVE PRINCIPAL MergerTree generará un índice primario para la tabla de datos de acuerdo con el intervalo index_granularity (por defecto 8192 filas) y lo guardará en el archivo primary.idx, ordenado por la clave primaria

2.Los archivos ORDENAR POR .bin se ordenan de acuerdo con las mismas reglas de CLAVE PRIMARIA

6.4.1 Índice disperso

El índice principal en el archivo primary.idx se implementa mediante un índice disperso

Índice denso: cada fila de marca de índice corresponde a una fila de registros de datos específicos

Índice disperso: cada fila de la marca de índice corresponde a un registro de datos específico

Comparación de los dos:

A El espacio de almacenamiento del índice ocupado por el índice disperso es relativamente pequeño, pero el tiempo de búsqueda es más largo; para escenarios con un gran volumen de datos, use la memoria residente de los datos del índice en primary.idx

		  b  稠密索引查找时间较短,索引存储空间较大。			  		  			数据量小场景

Inserte la descripción de la imagen aquí

6.4.2 Granularidad del índice

Los datos se marcan en varios espacios pequeños con la granularidad de index_granularity (la granularidad de índice fija predeterminada es 8192), y cada espacio tiene un máximo de 8192 filas de datos. El intervalo específico de este espacio es MarkRange, y el intervalo específico se expresa por inicio y final.

Inserte la descripción de la imagen aquí

6.4.3 Reglas de generación de datos de índice

Debido a que es un índice escaso, MergeTree necesita intervalos de datos de fila index_granularity para generar un registro de índice, y su valor de índice se obtendrá de acuerdo con el campo de clave principal declarado. La Figura 6-8 muestra los datos reales en la tabla de prueba hits_v1 después de la visualización. hits_v1 usa la partición de año y mes (PARTITION BYtoYYYYMM (EventDate)), por lo que los datos de marzo de 2014 eventualmente se dividirán en el mismo directorio de partición. Si usa CounterID como clave principal (ORDER BY CounterID), el valor de CounterID se tomará como el valor de índice cada 8192 filas de datos, y los datos de índice eventualmente se escribirán en el archivo primary.idx para su almacenamiento.

avance

Por ejemplo, el valor de CounterID de la fila 0 (8192 0) es 57, el valor de CounterID de la fila 8192 (8192 1) es 1635 y el valor de CounterID de la fila 16384 (8192 * 2) es 3266, el índice final los datos serán 5716353266.

 从图中也能够看出,MergeTree对于稀疏索引的存储是非常紧凑的,索引值前后相连,按照主键字段顺序紧密地排列在一起。不仅此处,ClickHouse中很多数据结构都被设计得非常紧凑,比如其使用位读取替代专门的标志位或状态码,可以不浪费哪怕一个字节的空间。以小见大,这也是ClickHouse为何性能如此出众的深层原因之一。

Si se utilizan varias claves primarias, como ORDER BY (CounterID, EventDate), los valores de las columnas CounterID y EventDate se pueden tomar como el valor de índice al mismo tiempo para 8192 filas en cada intervalo, como se muestra en la figura.

avance

6.4.4 Proceso de consulta de índice

MergeTree divide un segmento de datos completo en varios segmentos de datos de intervalo pequeño según la granularidad de intervalo de index_granularity. Un segmento de número específico es MarkRange.

MarkRange corresponde al número de índice, usando inicio y final para indicar un rango específico.

Tomando el valor del número de índice correspondiente al inicio y al final, se puede obtener el rango numérico correspondiente.

La consulta de índice es en realidad el juicio de la intersección de dos intervalos numéricos:

1. Un intervalo es un intervalo condicional convertido a partir de una condición de consulta basada en la clave primaria;

2. Un intervalo es el intervalo numérico correspondiente a MarkRange.

Proceso de consulta de índice:

1.生成查询条件区间:将查询条件转换为条件区间

	where ID = 'A003'			['A003','A003']

	where ID > 'A000'			('A000','+inf')

	where ID LIKE 'A006%'		['A006','A007')

2.递归交集判断:以递归的形式,依次对MarkRange的数值区间与条件区间做交集判断。

	如果不存在交集,则直接通过剪枝算法优化此整段MarkRange

	如果存在交集,且MarkRange步长大于8(end-start),则将此区间进一步拆分成8个子区间(由merge_tree_coarse_index_granularity指定,默认为8),并重复此规则,继续做递归交集判断

	如果存在交集,且MarkRange不可再分割(步长小于8),则记录MarkRange并返回

3.合并MarkRange区间:将最终匹配的MarkRange聚在一起,合并它们的范围

Inserte la descripción de la imagen aquí

Diagrama del proceso completo de consulta de índice

6.4.5 Índice secundario (índice de recuento de saltos)

Construido a partir de información agregada de datos. Los diferentes tipos de índices tienen diferente contenido de información agregada.

MergeTree admite tipos de índice de conteo de saltos: minmax, set, ngrambf_v1 y tokenbf_v1. Una tabla admite la declaración de índice de múltiples saltos al mismo tiempo.

El índice de recuento de saltos está cerrado de forma predeterminada, debe establecer set allow_experimental_data_skipping_indiced = 1

Para el índice de salto, index_granularity define la granularidad de los datos y la granularidad define la granularidad del resumen de información agregada.

La granularidad define cuántos intervalos index_granularity de datos se pueden omitir en una fila de índice de conteo de saltos.

Para explicar el papel de la granularidad, debemos comenzar con las reglas de generación de datos del índice de salto. Las reglas son aproximadamente las siguientes: Primero, divida los datos en n segmentos de acuerdo con el intervalo de granularidad index_granularity, y hay [0, n- 1] en total. Intervalo (n = total_rows / index_granularity, redondeado hacia arriba). Luego, de acuerdo con la expresión declarada cuando se define el índice, a partir del intervalo 0, la información agregada se obtiene de los datos de acuerdo con la granularidad index_granularity a su vez, avanzando 1 paso (n + 1) cada vez, y el la información agregada se acumula gradualmente. Finalmente, cuando se mueve el subintervalo de granularidad, se resume y genera una fila de datos del índice de conteo de saltos.

Tomemos como ejemplo el índice minmax, cuya información agregada son los valores extremos mínimo y máximo de los datos en un intervalo index_granularity. La siguiente figura es un ejemplo. Suponiendo index_granularity = 8192 y granularity = 3, los datos se dividirán en n partes iguales de acuerdo con index_granularity. MergeTree comienza desde el segmento 0 y obtiene información agregada a su vez. Cuando se obtiene la tercera partición (granularidad = 3), el índice minmax de la primera fila se resume y genera (el valor del valor extremo minmax de los primeros 3 segmentos se resume como [1, 9]), como se muestra en la figura.

avance

6.4.6 Almacenamiento de datos

Almacenamiento independiente para cada columna

En MergeTree, los datos se almacenan en columnas. Específico para cada campo de columna, cada campo de columna tiene un archivo de datos .bin correspondiente (almacenamiento físico).

El archivo .bin solo guardará esta parte de los datos en el segmento de partición actual.

En primer lugar, los datos están comprimidos (actualmente admite: algoritmos LZ4, ZSTD, Multiple y Delta);

En segundo lugar, los datos se ordenarán de acuerdo con la instrucción ORDER BY de antemano;

Finalmente, los datos se organizan en forma de múltiples bloques de datos comprimidos y se escriben en el archivo .bin.

Bloque de datos comprimidos

Un bloque de datos comprimidos consta de dos partes: información de encabezado y datos comprimidos. La información del encabezado siempre está representada por bytes de 9 bits, compuestos específicamente por 1 entero UInt8 (1 byte) y 2 enteros UInt32 (4 bytes), que representan el tipo de algoritmo de compresión utilizado, el tamaño de los datos comprimidos y la compresión. Los datos anteriores Talla.

avance

Como puede verse en la figura, el archivo comprimido .bin se compone de múltiples bloques de datos comprimidos, y la información del encabezado de cada bloque de datos comprimidos se genera en base a la fórmula CompressionMethod_CompressedSize_UncompressedSize.

Durante el proceso específico de escritura de datos, MergeTree obtendrá y procesará los datos en lotes de acuerdo con la granularidad del índice. Como se muestra abajo:

avance

Muchos a uno 1. Un solo lote de datos TAMAÑO <64 KB; si un solo lote de datos tiene menos de 64 KB, continúe obteniendo el siguiente lote de datos, hasta que la acumulación de TAMAÑO> = 64 KB, genere el siguiente comprimido bloque de datos;

Uno a uno 2. Un solo lote de datos 64 KB <= TAMAÑO <= 1 MB: si el tamaño de un solo lote de datos está entre 64 KB y 1 MB, el siguiente bloque de datos comprimidos se genera directamente

Uno a muchos 3. Un solo lote de datos TAMAÑO> 1 MB; si un solo lote de datos supera directamente 1 MB, primero se truncará de acuerdo con el tamaño de 1 MB y generará el siguiente bloque de datos. Los datos restantes continúan ejecutándose según el tamaño.

Resumen: un archivo .bin se compone de uno o más bloques de datos comprimidos y el tamaño de cada bloque comprimido está entre 64 KB y 1 MB. Entre varios bloques comprimidos, se escriben de un extremo a otro en secuencia.

avance

El propósito de introducir bloques comprimidos en archivos .bin:

1. Una vez comprimidos los datos, el tamaño de los datos se puede reducir de manera efectiva, el espacio de almacenamiento se reduce y la eficiencia de transmisión de datos se acelera, pero la eficiencia de compresión y descompresión también afectará el rendimiento.

2. Al leer una determinada columna de datos (archivo .bin), primero debe cargar los datos comprimidos en la memoria para descomprimirlos y leerlos. Es decir, al comprimir el bloque (64 KB ~ 1 MB), la granularidad de lectura se puede reducir al nivel de bloque comprimido sin leer todo el archivo .bin.

6.5 Marcado de datos

6.5.1 Reglas de generación de etiquetas de datos

índice primario primary.idx

archivo de datos .bin

.mrk establece una asociación entre el índice principal y el archivo de datos. Se registran dos piezas principales de información:

1. Información del número de página correspondiente al índice principal;

2. La posición inicial de un párrafo de texto en una página.

Inserte la descripción de la imagen aquí

Características de marcado de datos: 1. El archivo de marcado de datos y el intervalo de índice están alineados. Todos se dividen según el intervalo de granularidad de index_granularity.

2. También existe una correspondencia uno a uno entre los archivos de marcas de datos y los archivos .bin. Cada archivo de campo de columna [columna] .bin tiene un archivo de marca de datos [columna] .mrk correspondiente, que se utiliza para registrar la información de compensación de los datos en el archivo .bin.

Una línea de datos marcados está representada por una tupla, que contiene la información de desplazamiento de dos datos enteros (el desplazamiento en el archivo comprimido, el desplazamiento en el bloque descomprimido)

Cada línea de datos marcados representa la información de la posición de lectura de un dato (el valor predeterminado es 8192 líneas) en el archivo comprimido .bin

Los datos marcados son diferentes del índice de primer nivel. No pueden residir en la memoria. En su lugar, utiliza la estrategia de almacenamiento en caché LRU (menos recientemente utilizada) para acelerar su recuperación.

6.5.2 Cómo funciona el etiquetado de datos

Cuando MergeTree lee datos, debe encontrar los datos requeridos a través de la información de ubicación de los datos marcados.

El proceso de búsqueda se divide aproximadamente en dos pasos: leer el bloque de datos comprimido y leer los datos.

Inserte la descripción de la imagen aquí

El tipo de datos del campo JavaEnable es UInt8, por lo que cada fila de datos ocupa 1 byte.

La granularidad index_granularity de la tabla de datos es 8192, por lo que el tamaño de cada fragmento de índice es exactamente 8192B.

De acuerdo con la regla del bloque de compresión de datos, 8192B <64KB, cuando es igual a 64KB, se comprimirá como el siguiente bloque de datos. (64KB / 8192B = 8, es decir, 8 filas de datos son un bloque de compresión de datos)

¿Cómo MergeTree localiza el bloque de datos comprimidos y lee los datos?

1. Leer bloque de datos comprimidos: MergeTree no necesita cargar todo el archivo .bin de una sola vez al consultar una determinada columna de datos. Cargue el bloque de compresión de datos especificado tomando prestado el desplazamiento del archivo comprimido en el archivo marcado.

2. Leer datos: después de descomprimir los datos, MergeTree no necesita escanear todo el segmento de los datos descomprimidos a la vez, tomando prestado el desplazamiento en el bloque de datos guardado en el archivo de marca para cargar un segmento pequeño específico con la granularidad de index_granularity

6.6 Resumen colaborativo de particionamiento, indexación, marcado y datos comprimidos

6.6.1 Proceso de escritura

1. Genere un directorio de partición (con cada operación de inserción, se genera un nuevo directorio de partición);

2. Posteriormente, combine los directorios de la misma partición;

3. De acuerdo con la granularidad del índice index_granularity, genere respectivamente el archivo de índice primary.idx, el índice secundario, la marca de datos .mrk de cada campo de columna y el archivo de datos comprimido .bin.

El índice corresponde al intervalo marcado. El intervalo marcado es diferente del intervalo de bloque comprimido, y se generan tres relaciones de uno a uno, uno a muchos y muchos a uno.

avance

Según el directorio de la partición: 201403_1_34_3:

Las N filas de datos de esta partición se escriben en lotes 34 veces y se combinan 3 veces.

6.6.2 Proceso de consulta

1.minmax.idx (índice de partición)

2.primary.idx (índice principal)

3.skp_idx.idx (índice secundario)

4… mrk (archivo marcado)

5… bin (archivo de datos comprimidos)

avance

No hay ninguna condición donde en la declaración de consulta. Los pasos 1, 2 y 3 no van; primero escanee todos los directorios de partición y el rango máximo de segmentos de índice en el directorio. MergeTree toma prestadas etiquetas de datos y lee múltiples bloques comprimidos en forma de multihilo.

6.6.3 Correspondencia entre la marca de datos y el bloque de datos comprimido

La división de bloques comprimidos:

El tamaño de index_granularity y las tres reglas del bloque de compresión determinan el tamaño del bloque de datos en 64KB ~ 1MB.

Y los datos de un intervalo de índice producen una fila de marcas de datos.

Muchos a uno: varias etiquetas de datos corresponden a un bloque de compresión de datos. Un TAMAÑO sin comprimir de index_granularity <64KB

Suponiendo que el tipo de datos del campo JavaEnable es UInt8, cada fila de datos ocupa 1 byte. La granularidad index_granularity de la tabla de datos es 8192, por lo que el tamaño de cada fragmento de índice es exactamente 8192B. De acuerdo con la regla del bloque de compresión de datos, 8192B <64KB, cuando es igual a 64KB, se comprime como el siguiente bloque de datos. (64KB / 8192B = 8, es decir, 8 filas de datos son un bloque de compresión de datos)

avance

Uno a uno: una etiqueta de datos corresponde a un bloque de compresión de datos. 64 KB sin comprimir de index_granularity <= SIZE <= 1MB

Suponiendo que el tipo de datos del campo URLHash es UInt64 y el tamaño es 8B, el tamaño de datos de un intervalo predeterminado es 8 * 8192 = 65536B, que es exactamente 64 KB. En este momento, los datos marcados y los datos comprimidos están en una relación uno a uno.

avance

Uno a varios: una etiqueta de datos corresponde a varios bloques de compresión de datos. Un TAMAÑO sin comprimir de index_granularity> 1 MB

Suponiendo que el tipo de campo de URL es String y el contenido es exactamente 4.8MB, entonces un archivo marcado con datos corresponde a 5 bloques de compresión de datos.

avance
Para obtener más contenido interesante, siga la cuenta pública de WeChat para obtener

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_45320660/article/details/112761790
Recomendado
Clasificación