Dije que usara count (*) para contar el número de filas, y el entrevistador me pidió que volviera y esperara las noticias...

15365404:
  • prefacio

  • ¿Por qué 1 cuenta (*) tiene un bajo rendimiento?

  • 2 ¿Cómo optimizar el rendimiento del conteo (*)?

    • 2.1 Aumentar caché redis

    • 2.2 Agregar caché L2

    • 2.3 Ejecución multihilo

    • 2.4 Reducir las tablas de unión

    • 2.5 Cambiar a ClickHouse

  • Comparación de rendimiento de varios usos de 3 cuentas


prefacio

Recientemente, he optimizado el rendimiento de varias interfaces de consulta lenta en la empresa. He resumido algunas experiencias y las he compartido con ustedes. Espero que les sea útil.

La base de datos que usamos es Mysql8, el motor de almacenamiento que usamos es Innodb. Además de esta optimización 优化索引, se trata más de optimización count(*).

En circunstancias normales, la interfaz de paginación generalmente consulta la base de datos dos veces, la primera vez es para obtener datos específicos, la segunda vez es para obtener el número total de filas de registros y luego devolver el resultado después de integrar los resultados.

Consulta el sql de datos específicos, como este:`

select id,name from user limit 1,20;

No tiene problemas de rendimiento.

Pero otro sql que usa count (*) para consultar el número total de filas, por ejemplo:

select count(*) from user;

Pero hay un problema de bajo rendimiento.

¿Por qué pasó esto?

Sistema de gestión en segundo plano + subprograma de usuario basado en Spring Boot + MyBatis Plus + Vue & Element, admite permisos dinámicos RBAC, multiusuario, permisos de datos, flujo de trabajo, inicio de sesión de tres partes, pago, SMS, centro comercial y otras funciones

  • Dirección del proyecto: https://github.com/YunaiV/ruoyi-vue-pro

  • Videotutorial: https://doc.iocoder.cn/video/

¿Por qué 1 cuenta (*) tiene un bajo rendimiento?

En Mysql, count(*)la función es contar el número total de filas registradas en la tabla.

El rendimiento de count(*)los motores de almacenamiento está directamente relacionado con los motores de almacenamiento, no todos los motores de almacenamiento count(*)tienen un rendimiento deficiente.

Los motores de almacenamiento más utilizados en Mysql son: innodby myisam.

En myisam, el número total de filas se guardará en el disco. Al usar count (*), solo necesita devolver esos datos sin cálculos adicionales, por lo que la eficiencia de ejecución es muy alta.

InnoDB es diferente, porque admite transacciones, existe MVCC(es decir, la existencia de control de concurrencia de múltiples versiones), en diferentes transacciones en el mismo punto de tiempo, la cantidad de filas devueltas por la misma consulta SQL puede ser incierta.

Cuando innodb usa count (*), necesita leer los datos fila por fila del motor de almacenamiento y luego sumarlos, por lo que la eficiencia de ejecución es muy baja.

Está bien si la cantidad de datos en la tabla es pequeña, pero una vez que la cantidad de datos en la tabla sea grande, el rendimiento será bajo cuando el motor de almacenamiento de innodb use estadísticas de conteo (*).

Sistema de gestión de fondo + subprograma de usuario basado en Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element, admite permisos dinámicos RBAC, multiusuario, permisos de datos, flujo de trabajo, inicio de sesión de tres partes, pago, SMS, centro comercial y otras funciones

  • Dirección del proyecto: https://github.com/YunaiV/yudao-cloud

  • Videotutorial: https://doc.iocoder.cn/video/

2 ¿Cómo optimizar el rendimiento del conteo (*)?

De lo anterior, dado que count(*)hay problemas de rendimiento, ¿cómo podemos optimizarlo?

Podemos partir de los siguientes aspectos.

2.1 Aumentar caché redis

Para un conteo simple (*), como contar el número total de visitas o el número total de visitas, podemos almacenar en caché directamente la interfaz usando redis, y no hay necesidad de estadísticas en tiempo real.

Cuando el usuario abre la página especificada, se puede configurar para contar = contar + 1 cada vez en el caché.

Cuando el usuario visita la página por primera vez, el valor de conteo en redis se establece en 1. Cada vez que el usuario visite la página en el futuro, el conteo se incrementará en 1 y finalmente se restablecerá a redis.

De esta manera, donde se debe mostrar la cantidad, se puede encontrar el valor de conteo y devolverlo desde redis.

En este escenario, no hay necesidad de usar datos estadísticos en tiempo real de conteo (*) de la tabla de puntos enterrados de datos, y el rendimiento mejorará considerablemente.

Sin embargo, en el caso de alta simultaneidad, puede haber inconsistencias de datos entre el caché y la base de datos.

Sin embargo, para el escenario comercial de contar el número total de visitas o el número total de visitas, la precisión de los datos no es alta y se tolera la inconsistencia de los datos.

2.2 Agregar caché L2

Para algunos escenarios comerciales, hay muy pocos datos nuevos, la mayoría de ellos son operaciones estadísticas y hay muchas condiciones de consulta. En este momento, utilizando los datos estadísticos tradicionales de conteo (*) en tiempo real, el rendimiento definitivamente no será bueno.

Si la página puede usar id, nombre, estado, hora, fuente, etc., una o más condiciones para contar el número de marcas.

En este caso, el usuario tiene muchas condiciones de combinación, y es inútil agregar un índice conjunto. El usuario puede seleccionar una o más de las condiciones de consulta. A veces, el índice conjunto fallará y el índice solo se puede agregar para satisfacer la condiciones más utilizadas.

Es decir, algunas condiciones de combinación se pueden indexar y algunas condiciones de combinación no se pueden indexar ¿Cómo optimizar estos escenarios que no se pueden indexar?

Respuesta: uso 二级缓存.

El caché de segundo nivel es en realidad un caché de memoria.

Podemos usar caffineo guavaimplementar la función del caché de segundo nivel.

En la actualidad SpringBoot, se ha integrado la cafeína, que es muy cómoda de usar.

Simplemente use anotaciones en los métodos de consulta que necesitan aumentar el caché de segundo nivel @Cacheable.

 @Cacheable(value = "brand", , keyGenerator = "cacheKeyGenerator")
   public BrandModel getBrand(Condition condition) {
       return getBrandByCondition(condition);
   }

A continuación, personalice cacheKeyGenerator para especificar la clave de caché.

public class CacheKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getSimpleName() + UNDERLINE
                + method.getName() + ","
                + StringUtils.arrayToDelimitedString(params, ",");
    }
}

Esta clave se compone de varias condiciones.

De esta forma, después de consultar los datos de la marca a través de una combinación de condiciones, los resultados se almacenarán en la memoria caché y el tiempo de caducidad se establecerá en 5 minutos.

Más tarde, cuando el usuario vuelve a consultar los datos utilizando las mismas condiciones dentro de los 5 minutos, los datos se pueden recuperar directamente de la memoria caché de segundo nivel y devolverse directamente.

Esto puede impulsar en gran medida la eficiencia de consulta de count (*).

Sin embargo, si se usa el caché de segundo nivel, los datos pueden ser diferentes en diferentes servidores. Necesitamos elegir de acuerdo con el escenario comercial real, que no se puede aplicar a todos los escenarios comerciales.

2.3 Ejecución multihilo

No sé si ha hecho tal requisito: cuente cuántos pedidos son válidos y cuántos son pedidos no válidos.

En este caso, generalmente necesita escribir dos sqls, y los sqls para contar pedidos válidos son los siguientes:

select count(*) from order where status=1;

El SQL para contar pedidos no válidos es el siguiente:

select count(*) from order where status=0;

Pero si está en una interfaz, la eficiencia de ejecutar de forma sincrónica estos dos SQL será muy baja.

En este momento, se puede cambiar a un sql:

select count(*),status from order
group by status;

El uso de group byla agrupación de palabras clave para contar la cantidad del mismo estado solo generará dos registros, un registro es la cantidad de pedidos válidos y el otro registro es la cantidad de pedidos no válidos.

Pero hay un problema: el campo de estado solo tiene dos valores de 1 y 0, lo que tiene un alto grado de repetición y un grado de discriminación muy bajo, no se pueden usar índices y se escaneará toda la tabla, lo cual es no eficiente.

¿Hay otras soluciones?

Respuesta: Utilice subprocesos múltiples.

Podemos usar CompleteFuturedos 线程llamadas asincrónicas para contar el sql de pedidos válidos y el sql de contar pedidos no válidos, y finalmente resumir los datos, lo que puede mejorar el rendimiento de la interfaz de consulta.

2.4 Reducir las tablas de unión

En la mayoría de los casos, count (*) se usa para contar el número total en tiempo real.

Sin embargo, si la cantidad de datos en la tabla en sí no es grande, pero hay demasiadas tablas unidas, la eficiencia de count (*) también puede verse afectada.

Por ejemplo, al consultar la información del producto, es necesario consultar los datos en función del nombre del producto, la unidad, la marca, la clasificación y otra información.

En este momento, escriba un sql para averiguar los datos deseados, como los siguientes:

select count(*)
from product p
inner join unit u on p.unit_id = u.id
inner join brand b on p.brand_id = b.id
inner join category c on p.category_id = c.id
where p.name='测试商品' and u.id=123 and b.id=124 and c.id=125;

Utilice la tabla de productos para ir joina las tres tablas de unidad, marca y categoría.

De hecho, estas condiciones de consulta pueden consultar datos en la tabla de productos y no es necesario unir tablas adicionales.

Podemos cambiar el sql a esto:

select count(*)
from product
where name='测试商品' and unit_id=123 and brand_id=124 and category_id=125;

En count (*), solo se puede verificar la tabla única del producto y se elimina la unión de la tabla redundante, por lo que la eficiencia de la consulta se puede mejorar mucho.

2.5 Cambiar a ClickHouse

A veces, hay demasiadas tablas de unión y es imposible eliminar las uniones redundantes. ¿Qué debo hacer?

Por ejemplo, en el ejemplo anterior, al consultar la información del producto, es necesario consultar los datos según el nombre del producto, el nombre de la unidad, el nombre de la marca, el nombre de la categoría y otra información.

En este momento es imposible consultar los datos en base a la tabla única de producto, es necesario ir a joinlas tres tablas: unidad, marca y categoría ¿Cómo optimizar este tiempo?

R: Puede guardar los datos en ClickHouse.

ClickHouse se basa en 列存储una base de datos, no admite transacciones y tiene un rendimiento de consulta muy alto. Afirma consultar más de mil millones de datos y puede devolverlos en segundos.

Para evitar la incrustación de código comercial, se pueden usar registros Canalde escucha . Cuando se agregan nuevos datos a la tabla de productos, es necesario consultar los datos de la unidad, la marca y la clasificación al mismo tiempo, generar un nuevo conjunto de resultados y guardarlo en ClickHouse.Mysqlbinlog

Al consultar datos, consulte desde ClickHouse, de modo que la eficiencia de la consulta con el conteo (*) se pueda aumentar N veces.

Un recordatorio especial: cuando utilice ClickHouse, no agregue datos con demasiada frecuencia e intente insertar datos en lotes.

De hecho, si hay muchas condiciones de consulta, no es especialmente adecuado utilizar ClickHouse, en este momento se puede cambiar ElasticSearch, pero tiene los mismos problemas que Mysql 深分页.

Comparación de rendimiento de varios usos de 3 cuentas

Ahora que hablamos de contar (*), tenemos que hablar de otros miembros de la familia de cuentas, como: cuenta (1), cuenta (id), cuenta (columna de índice común), cuenta (columna no indexada).

Entonces, ¿cuál es la diferencia?

  • count(*) : obtendrá los datos de todas las filas sin ningún procesamiento y agregará 1 al número de filas.

  • count(1): Obtendrá los datos de todas las filas, cada fila tiene un valor fijo de 1, que también es el número de filas más 1.

  • count(id): id representa la clave principal, necesita analizar el campo de id de los datos de todas las filas, donde la id no debe ser NULL, y el número de filas aumenta en 1.

  • count (columna de índice ordinaria): necesita analizar la columna de índice ordinaria de los datos de todas las filas y luego juzgar si es NULL. Si no es NULL, el número de filas +1.

  • count (columnas no indexadas): escanea toda la tabla para obtener todos los datos, columnas no indexadas en el análisis, y luego juzga si es NULL.Si no es NULL, el número de filas +1.

Por lo tanto, el rendimiento de la última cuenta de mayor a menor es:

cuenta (*) ≈ cuenta (1) > cuenta (id) > cuenta (columna de índice normal) > cuenta (columna no indexada)

Entonces, en realidad count(*)el más rápido.

¿Es una sorpresa, es una sorpresa?

No te select * confundas con eso.

Supongo que te gusta

Origin blog.csdn.net/2301_77463738/article/details/131152130
Recomendado
Clasificación