MySQL unterschiedliche Leistung

8

Wenn ich meiner Abfrage "deutlich" hinzufüge, erhöht sich die Abfragezeit von 0,015 auf über 6 Sekunden.

Ich möchte mehrere Tabellen verbinden, die über Fremdschlüssel verknüpft sind, und eine bestimmte Spalte daraus erhalten:

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

Die eindeutige Abfrage dauert 6 Sekunden, was mir verbesserungsfähig erscheint.

Mit Auswahl:

Dauer: 0,015 s / Abruf: 5,532 s (5,760,434 Zeilen)

Erklären:

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  

Mit eindeutiger Auswahl:

Dauer: 6.625s / Abruf: 0.000s (1000 Zeilen)

Erklären:

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  

Datenbank: Datenbank-Snippet

Code zum Testen / 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
quelle
4
Und so stellt man eine Frage
Strawberry
Ich lerne ...
Langer

Antworten:

2

Danke für das CREATE TABLEs; Ohne sie hätten Sie vielleicht nie eine Antwort bekommen.

  • Jeder Tisch sollte eine haben PRIMARY KEY. Wenn Sie eine Spalte (oder eine Kombination von Spalten) haben, die "natürlich" funktioniert, verwenden Sie diese. Andernfalls verwenden Sie eine AUTO_INCREMENT.
  • Stellen Sie bei Timing-Abfragen (1) sicher, dass der "Abfrage-Cache" nicht verwendet wird, und (2) führen Sie die Abfrage zweimal aus, um nach anderen Variationen im Timing zu suchen.
  • INDEX(fkey)wird INVISIBLEdaher nicht verwendet. Verschwenden Sie keine Lernzeit mit VISIBLE/ INVISIBLE, Sie werden sie möglicherweise nie in Ihrer Karriere brauchen.
  • Stellen Sie beim Experimentieren sicher, dass jede Tabelle mehr als ein paar Zeilen enthält und die Werte realistisch variieren. Andernfalls verwendet der Optimierer möglicherweise Verknüpfungen, die Ihre Lernerfahrung nur verwirren.
  • Und...

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

Beachten Sie, wie beide ungefähr 6 Sekunden sind. Es ist nur so, dass die Zeit anders aufgeteilt ist.

  • Mit 6 Millionen Zeilen und Nein DISTINCTkann die Abfrage die Daten sofort auspumpen, dauert jedoch aufgrund der Netzwerklatenz lange.
  • Mit dem DISTINCTkann die erste Zeile erst nach dem Vorformen der "Deduplizierung" herauskommen, die wahrscheinlich eine "temporäre" (siehe die EXPLAIN) und eine Sortierung beinhaltet. Jetzt ist die ganze Zeit mit dem Rechnen beschäftigt, bevor die Daten gesendet werden.
  • Die Verwirrung ist, dass Sie nur die "Dauer" betrachtet haben, nicht die Summe der beiden Male. Das heißt, die Gesamtzeit ist wichtig.
  • Die DISTINCTist etwas langsamer (Gesamtzeit), da zusätzliche Schritte zum Sammeln und Sortieren von 5,7 Millionen Zeilen erforderlich sind.
Rick James
quelle
'Die erste Reihe kann erst nach der Durchführung der "Deduplizierung" herauskommen.' - Um pedantisch zu sein, die zweite Reihe.
Philipxy
@philipxy - Das hängt vom Algorithmus ab. Ich denke, es wählt zwischen dem Sortieren der Daten und dem Erstellen eines Hash im RAM. Beweis: Manchmal DISTINCTist sortiert, manchmal nicht.
Rick James
Die zitierte Aussage handelt von der Ausgabe einer vagen Klasse von Implementierungen, die etwas tun, das dann "de-dupliziert" wird. Der letzte Schritt des Etwas oder der erste Schritt des "Duplizierens" könnte darin bestehen, eine beliebige Zeile auszugeben. Die Aussage ist also falsch. Darüber hinaus hängt es als wahr-oder-falsch-Aussage von nichts "ab". Ich bin sicher, wir würden uns darauf einigen, dass bestimmte andere Aussagen wahr sind und Ihr Kommentar für sie wahr ist, aber sie sind nicht das, was Sie geschrieben haben. (Mein ursprünglicher Punkt war nur, um auf einen
Randfall
1) Ich habe den Primkey weggelassen, weil es mehr Aufwand wäre, Daten einzugeben. Fragen Sie einfach nicht ... Aber es hat keinen Einfluss auf die Leistung hier. 2) Dieses Cache-Ding hat mein Benchmarking so oft beendet ... aber diesmal nicht. Wie können Sie die Verwendung des Caches vermeiden? 3) Invis-Index: Okay, hat aber nichts an der Leistung geändert. 4) Die Daten waren realistisch. Mehr oder weniger ... Ich weiß was du meinst. Rest) Ich kann 2 Ziffern hinzufügen und die Summe sehen, aber danke für diese Mathe-Lektion;) Ich habe mich gefragt, warum diese Unterscheidung so lange dauert und ich wollte dies beschleunigen. Ich werde prob. Netzwerkgeschwindigkeit nicht erhöhen.
Langer
1
@Langer - Der "Abfrage-Cache" kann bei Auswahl folgendermaßen vermieden werden : SELECT SQL_NO_CACHE ....
Rick James