Desempenho distinto do MySQL

8

Quando adiciono 'distinto' à minha consulta, o tempo da consulta aumenta de 0,015 para mais de 6 segundos.

Quero juntar várias tabelas, que são vinculadas por chaves estrangeiras e obtêm uma coluna distinta:

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

A consulta distinta leva 6 segundos, o que me parece improvável.

Com selecione:

duração: 0.015s / busca: 5.532s (5.760.434 linhas)

Explicar:

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  

Com seleção distinta:

duração: 6.625s / busca: 0.000s (1000 linhas)

Explicar:

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  

Banco de dados: snippet do banco de dados

Código para teste / 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
fonte
4
E é assim que se faz uma pergunta
Strawberry
Estou aprendendo ....
Langer

Respostas:

2

Obrigado pelo CREATE TABLEs; você pode nunca ter recebido uma resposta sem eles.

  • Toda tabela deve ter a PRIMARY KEY. Se você possui uma coluna (ou combinação de colunas) que 'naturalmente' funciona, use-a. Caso contrário, use um AUTO_INCREMENT.
  • Quando consultas por tempo, (1) verifique se o "cache de consulta" não está sendo usado e (2) execute a consulta duas vezes para verificar outras variações no tempo.
  • INDEX(fkey)é INVISIBLE, portanto, não usado. Não perca tempo aprendendo VISIBLE/ INVISIBLE, você pode nunca precisar deles em sua carreira.
  • Ao experimentar, certifique-se de ter mais do que algumas linhas em cada tabela e seus valores variam de maneira realista. Caso contrário, o Otimizador poderá usar atalhos que apenas confundirão sua experiência de aprendizado.
  • E...

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

Observe como ambos são cerca de 6 segundos. Só que o tempo é dividido de maneira diferente.

  • Com 6 milhões de linhas e não DISTINCT, a consulta pode bombear os dados imediatamente, mas leva muito tempo devido à latência da rede.
  • Com o DISTINCT, a primeira linha não pode sair até depois da pré-formatação da "desduplicação", que provavelmente envolve um "temporário" (consulte o EXPLAIN) e uma classificação. Então, agora o tempo todo está envolvido na computação antes de enviar os dados.
  • A confusão é que você olhou apenas para a "duração" e não para a soma das duas vezes. Ou seja, o tempo total é o mais importante a ser observado.
  • Essa DISTINCTé um pouco mais lenta (tempo total) devido à etapa extra de coletar e classificar 5,7 milhões de linhas.
Rick James
fonte
'a primeira linha não pode sair até depois de pré-formar a "deduplicação"' - Para ser pedante, a segunda linha.
philipxy 17/01
@ philipxy - isso depende do algoritmo. Eu acho que escolhe entre classificar os dados e construir um hash na RAM. Evidência: Às vezes DISTINCTé classificada, às vezes não.
Rick James
A declaração citada é sobre a saída de alguma classe vaga de implementações que fazem algo que "desduplica". O último passo do algo ou o primeiro passo do "esgotar" pode ser a saída de qualquer linha. Portanto, a afirmação é falsa. Além disso, como uma afirmação verdadeira ou falsa, não "depende" de nada. Estou certo de que concordaríamos que certas afirmações fossem verdadeiras e que seu comentário fosse verdade sobre elas, mas elas não foram o que você escreveu. (Meu argumento original era apenas para apontar um caso extremo).
philipxy 17/01
1) Omiti a primkey porque seria mais difícil inserir dados. Apenas não pergunte ... Mas isso não afeta o desempenho aqui. 2) Essa coisa de cache matou meus testes comparativos com tanta frequência ... mas não desta vez. Como você pode evitar o uso do cache? 3) Índice Invis: Ok, mas não mudou nada no desempenho. 4) Os dados foram realistas. Mais ou menos .... Eu sei o que você quer dizer. Resto) Posso adicionar 2 números e ver a soma, mas obrigado por esta lição de matemática;) Fiquei me perguntando por que essa distinção leva tanto tempo e queria acelerar isso. Eu vou prob. não aumenta a velocidade da rede.
Langer
1
@Langer - O "cache de consultas" pode ser evitado em seleciona assim: SELECT SQL_NO_CACHE ....
Rick James