Rendimiento distinto de MySQL

8

Cuando agrego 'distintivo' a mi consulta, el tiempo de consulta aumenta de 0.015 a más de 6 segundos.

Quiero unir varias tablas, que están vinculadas a través de claves externas y obtener una columna distinta de ella:

select distinct table3.idtable3 from 
    table1
    join table2 on table1.idtable1 = table2.fkey
    join table3 on table2.idtable2 = table3.fkey
    where table1.idtable1 = 1 

La consulta distinta lleva 6 segundos, lo que me parece mejorable.

Con seleccionar:

duración: 0.015s / búsqueda: 5.532s (5.760.434 filas)

Explique:

id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1   SIMPLE  table1      index   asd asd 137     10  10.00   Using where; Using index
1   SIMPLE  table2      ALL idtable2                200 25.00   Using where; Using join buffer (Block Nested Loop)
1   SIMPLE  table3      ref fkey_table2_table_3_idx fkey_table2_table_3_idx 138 mydb.table2.idtable2    66641   100.00  

Con selección distinta:

duración: 6.625s / búsqueda: 0.000s (1000 filas)

Explique:

id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1   SIMPLE  table1      index   asd asd 137     10  10.00   Using where; Using index; Using temporary
1   SIMPLE  table2      ALL idtable2                200 25.00   Using where; Using join buffer (Block Nested Loop)
1   SIMPLE  table3      ref fkey_table2_table_3_idx fkey_table2_table_3_idx 138 mydb.table2.idtable2    66641   100.00  

Base de datos: fragmento de base de datos

Código para pruebas / MCRE:

import mysql.connector
import time
import numpy as np




""" 
-- MySQL Script generated by MySQL Workbench
-- Fri Jan 17 12:19:26 2020
-- Model: New Model    Version: 1.0
-- MySQL Workbench Forward Engineering

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

-- -----------------------------------------------------
-- Schema mydb
-- -----------------------------------------------------

-- -----------------------------------------------------
-- Schema mydb
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 ;
USE `mydb` ;

-- -----------------------------------------------------
-- Table `mydb`.`table1`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`table1` (
  `idtable1` VARCHAR(45) NOT NULL,
  INDEX `asd` (`idtable1` ASC) VISIBLE)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`table2`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`table2` (
  `idtable2` VARCHAR(45) NOT NULL,
  `fkey` VARCHAR(45) NULL,
  INDEX `link_table1_table2_idx` (`fkey` ASC) INVISIBLE,
  INDEX `idtable2` (`idtable2` ASC) VISIBLE,
  CONSTRAINT `link_table1_table2`
    FOREIGN KEY (`fkey`)
    REFERENCES `mydb`.`table1` (`idtable1`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`table3`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`table3` (
  `idtable3` VARCHAR(45) NOT NULL,
  `fkey` VARCHAR(45) NULL,
  INDEX `fkey_table2_table_3_idx` (`fkey` ASC) VISIBLE,
  CONSTRAINT `fkey_table2_table_3`
    FOREIGN KEY (`fkey`)
    REFERENCES `mydb`.`table2` (`idtable2`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;


"""


def insertData():
    for i in range(2):
        num_distinct_table1_values = 5
        num_distinct_table2_values = 10
        num_distinct_table3_values = 1000

        num_entries_table1 = int(num_distinct_table1_values)
        num_entries_table2 = int(num_distinct_table2_values * 10)
        num_entries_table3 = int(num_distinct_table3_values * 300)

        random_numbers_table1_id = range(num_distinct_table1_values)

        random_numbers_table2_id = np.random.randint(num_distinct_table2_values, size=int(num_entries_table2))
        random_numbers_table2_fkey = np.random.randint(num_distinct_table1_values, size=int(num_entries_table2))

        random_numbers_table3_id = np.random.randint(num_distinct_table3_values, size=int(num_entries_table3))
        random_numbers_table3_fkey = np.random.randint(num_distinct_table2_values, size=int(num_entries_table3))

        value_string_table1 = ','.join([f"('{i_name}')" for i_name in random_numbers_table1_id])
        value_string_table2=""
        for i in range(num_entries_table2):
            value_string_table2 = value_string_table2+','.join(
                ["('{id}','{fkey}'),".format(id=random_numbers_table2_id[i], fkey=random_numbers_table2_fkey[i])])

        value_string_table3=""
        for i in range(num_entries_table3):
            value_string_table3 = value_string_table3+','.join(
                ["('{id}','{fkey}'),".format(id=random_numbers_table3_id[i], fkey=random_numbers_table3_fkey[i])])

        # fill table 1
        mySql_insert_query = f"INSERT INTO table1 (idtable1) VALUES {value_string_table1}"
        cursor.execute(mySql_insert_query)
        conn.commit()
        print("Done table 1")
        # fill table 2
        mySql_insert_query = f"INSERT INTO table2 (idtable2, fkey) VALUES {value_string_table2}"
        mySql_insert_query=mySql_insert_query[0:-1]
        cursor.execute(mySql_insert_query)
        print("Done table 2")
        # fill table 3
        mySql_insert_query = f"INSERT INTO table3 (idtable3, fkey) VALUES {value_string_table3}"
        mySql_insert_query = mySql_insert_query[0:- 1]
        cursor.execute(mySql_insert_query)
        print("Done table 3")

        conn.commit()

conn = mysql.connector.connect(user='root', password='admin', host='127.0.0.1',
                               database='mydb', raise_on_warnings=True, autocommit=False)
cursor = conn.cursor()


insertData()


conn.close()
Langer
fuente
44
Y así es como hacer una pregunta
Fresa
Estoy aprendiendo ....
Langer

Respuestas:

2

Gracias por el CREATE TABLEs; Es posible que nunca haya recibido una respuesta sin ellos.

  • Cada mesa debe tener un PRIMARY KEY. Si tiene una columna (o combinación de columnas) que funciona "naturalmente", úsela. De lo contrario, use un AUTO_INCREMENT.
  • Al cronometrar consultas, (1) asegúrese de que no se esté utilizando el "Caché de consultas" y (2) ejecute la consulta dos veces para verificar otras variaciones en el cronometraje.
  • INDEX(fkey)es INVISIBLE, por lo tanto, no se utiliza. No pierdas el tiempo de aprendizaje en VISIBLE/ INVISIBLE, es posible que nunca los necesites en tu carrera.
  • Al experimentar, asegúrese de tener más de unas pocas filas en cada tabla y que sus valores varíen de manera realista. De lo contrario, el Optimizer puede tomar atajos que solo confundirán su experiencia de aprendizaje.
  • Y...

    duration : 0.015s / fetch:5.532s (5.760.434 rows)
    duration : 6.625s / fetch:0.000s (1000 rows)

Observe cómo ambos son unos 6 segundos. Es solo que el tiempo se divide de manera diferente.

  • Con 6 millones de filas y no DISTINCT, la consulta puede bombear los datos inmediatamente, pero lleva mucho tiempo debido a la latencia de la red.
  • Con el DISTINCT, la primera fila no puede salir hasta después de preformar la "desduplicación", que probablemente implica un "temporal" (ver el EXPLAIN) y una ordenación. Entonces, ahora todo el tiempo está involucrado en la computación antes de enviar los datos.
  • La confusión es que solo miraste la "duración", no la suma de las dos veces. Es decir, el tiempo total es importante para tener en cuenta.
  • El DISTINCTuno es un poco más lento (tiempo total) debido al paso adicional de recopilar y ordenar 5.7M filas.
Rick James
fuente
'la primera fila no puede salir hasta después de preformar la "desduplicación"' - Para ser pedante, la segunda fila.
philipxy
@philipxy: eso depende del algoritmo. Creo que elige entre ordenar los datos o construir un hash en RAM. Evidencia: a veces DISTINCTse ordena, a veces no.
Rick James
La declaración citada trata sobre la salida de alguna clase de implementaciones vagas que hacen algo y luego "desduplican". El último paso del algo o el primer paso de la "eliminación de duplicados" podría ser generar cualquier fila. Entonces la afirmación es falsa. Además, como una declaración de verdadero o falso no "depende de" nada. Estoy seguro de que estaríamos de acuerdo en que ciertas otras declaraciones sean ciertas y que su comentario sea cierto respecto de ellas, pero no son lo que usted escribió. (Mi punto original era sólo señalar un caso extremo.)
philipxy
1) Omití la clave primaria porque sería más difícil poner datos. Simplemente no pregunte ... Pero aquí no afecta el rendimiento. 2) Esta cosa de caché mató mi evaluación comparativa tan a menudo ... pero no esta vez. ¿Cómo puedes evitar usar el caché? 3) Índice Invis: Está bien, pero no cambió nada en el rendimiento. 4) Los datos fueron realistas. Más o menos ... Sé lo que quieres decir. Descanso) Puedo sumar 2 numerbs y ver la suma, pero gracias por esta lección de matemáticas;) Me preguntaba por qué esto toma tanto tiempo y quería acelerar esto. Voy a probar No aumenta la velocidad de la red.
Langer
1
@Langer - El "caché de consultas" se puede evitar en selecciona así: SELECT SQL_NO_CACHE ....
Rick James