MySQL独自のパフォーマンス


8

クエリに「明確」を追加すると、クエリ時間が0.015秒から6秒以上に増加します。

外部キーを介してリンクされている複数のテーブルを結合し、そこから個別の列を取得したいと思います。

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

明確なクエリは6秒かかりますが、これは改善できるようです。

選択あり:

期間:0.015秒/フェッチ:5.532秒(5.760.434行)

説明:

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  

明確な選択:

時間:6.625秒/フェッチ:0.000秒(1000行)

説明:

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  

データベース: データベーススニペット

テスト用コード/ 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()

4
そして、それが質問の仕方です
イチゴ

私は学んでいます...
ランガー

回答:


2

ありがとうCREATE TABLEs; あなたはそれらなしで答えを得たことはないかもしれません。

  • すべてのテーブルにが必要PRIMARY KEYです。「自然に」機能する列(または列の組み合わせ)がある場合は、それを使用します。それ以外の場合はを使用しAUTO_INCREMENTます。
  • クエリのタイミングをとるときは、(1)「クエリキャッシュ」が使用されていないことを確認し、(2)クエリを2回実行して、タイミングの他のバリエーションを確認します。
  • INDEX(fkey)されINVISIBLE、したがって、使用されていません。VISIBLE/の学習時間を無駄にしないINVISIBLEでください。キャリアでそれらを必要としない場合があります。
  • 実験するときは、各テーブルに数行以上あることと、それらの値が現実的な方法で変化することを確認してください。さもなければ、オプティマイザはあなたの学習経験を混乱させるだけのショートカットを取るかもしれません。
  • そして...

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

どちらも約6秒です。時間が違うように分かれているだけです。

  • 6M行あり、なしのDISTINCT場合、クエリはデータをすぐに汲み出すことができますが、ネットワークのレイテンシのために時間がかかります。
  • を使用するDISTINCTと、おそらく「一時的な」(を参照EXPLAIN)とソートが含まれる「重複除外」を実行するまで、最初の行は表示されません。したがって、今はデータを送信する前に常に計算関与しています。
  • 混乱は、2つの時間の合計ではなく、「期間」のみを見たということです。つまり、合計時間は注意すべき重要な時間です。
  • DISTINCT一つは、わずかに遅いため、収集及び5.7Mの行をソートする余分なステップの(総時間)です。

「最初の行は、「重複除外」を実行するまでは出てこない」-徹底的に言うと、2番目の行。
philipxy

@philipxy-それはアルゴリズムに依存します。データのソートとRAMでのハッシュの構築のどちらを選択するかと思います。証拠:DISTINCT並べ替えられる場合とそうでない場合があります。
リックジェームズ

引用されたステートメントは、何かを「重複解除」するなんらかの実装のあいまいなクラスの出力に関するものです。何かの最後のステップまたは「複製解除」の最初のステップは、任意の行を出力することです。したがって、ステートメントは偽です。さらに、真偽のステートメントとしては、何にも「依存」しません。他の特定の声明が真実であり、あなたのコメントが真実であることに同意するに違いありませんが、あなたが書いたものではありません。(私の本来の目的は、エッジケースを指摘することだけ
でした

1)データを入力するのはもっと手間がかかるので、私はprimkeyを省略しました。質問しないでください...しかし、ここではパフォーマンスに影響しません。2)このキャッシュ機能は、私のベンチマークを頻繁に無効にしましたが、今回はそうではありません。キャッシュの使用をどのように回避できますか?3)Invisインデックス:わかりましたが、パフォーマンスに変化はありませんでした。4)データは現実的でした。多かれ少なかれ....私はあなたの意味を知っています。残り)私は2つの数値を追加して合計を確認できますが、この数学のレッスンに感謝します;)この区別に時間がかかる理由を考えていたので、これを高速化したいと思いました。私は精査します。ネットワーク速度を向上させません。
ランガー

1
@Langer-「クエリキャッシュ」は、selectで回避できるため、次のようになります SELECT SQL_NO_CACHE ...
リック・ジェームズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.