MySQL 不同寻常的表现
MySQL distinct performance
当我将 'distinct' 添加到我的查询时,查询时间从 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 秒,这在我看来是可以改进的。
与select:
持续时间:0.015 秒/fetch: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
不同 select:
持续时间:6.625 秒/fetch: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
数据库:
Database snippet
测试代码/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()
感谢CREATE TABLEs
;没有他们,你可能永远得不到答案。
- 每个 table 应该有一个
PRIMARY KEY
。如果您有 'naturally' 有效的列(或列组合),请使用它。否则使用 AUTO_INCREMENT
.
- 计时查询时,(1) 确保未使用 "Query cache",(2) 运行 查询两次以检查计时的其他变化。
INDEX(fkey)
是 INVISIBLE
,因此未使用。不要把学习时间浪费在VISIBLE
/INVISIBLE
上,你的职业生涯可能永远不需要它们。
- 进行试验时,请确保每行 table 中有几行以上,并且它们的值以实际方式变化。否则,优化器可能会采取只会混淆您的学习体验的捷径。
还有...
duration : 0.015s / fetch:5.532s (5.760.434 rows)
duration : 6.625s / fetch:0.000s (1000 rows)
请注意两者都是 6 秒左右。只是时间分配不同而已。
- 有 6M 行且没有
DISTINCT
,查询可以立即抽出数据,但由于网络延迟需要很长时间。
- 对于
DISTINCT
,第一行只有在执行"de-duplication"之后才能出来,这可能涉及一个"temporary"(见EXPLAIN
)和一个排序.所以,现在所有的时间都在计算 before 发送数据。
- 令人困惑的是您只查看了 "duration" 而不是两次的总和。也就是总的时间是要注意的重点。
DISTINCT
稍微慢一点(总时间),因为收集和排序 5.7M 行的额外步骤。
当我将 'distinct' 添加到我的查询时,查询时间从 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 秒,这在我看来是可以改进的。
与select:
持续时间:0.015 秒/fetch: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
不同 select:
持续时间:6.625 秒/fetch: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
数据库: Database snippet
测试代码/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()
感谢CREATE TABLEs
;没有他们,你可能永远得不到答案。
- 每个 table 应该有一个
PRIMARY KEY
。如果您有 'naturally' 有效的列(或列组合),请使用它。否则使用AUTO_INCREMENT
. - 计时查询时,(1) 确保未使用 "Query cache",(2) 运行 查询两次以检查计时的其他变化。
INDEX(fkey)
是INVISIBLE
,因此未使用。不要把学习时间浪费在VISIBLE
/INVISIBLE
上,你的职业生涯可能永远不需要它们。- 进行试验时,请确保每行 table 中有几行以上,并且它们的值以实际方式变化。否则,优化器可能会采取只会混淆您的学习体验的捷径。
还有...
duration : 0.015s / fetch:5.532s (5.760.434 rows) duration : 6.625s / fetch:0.000s (1000 rows)
请注意两者都是 6 秒左右。只是时间分配不同而已。
- 有 6M 行且没有
DISTINCT
,查询可以立即抽出数据,但由于网络延迟需要很长时间。 - 对于
DISTINCT
,第一行只有在执行"de-duplication"之后才能出来,这可能涉及一个"temporary"(见EXPLAIN
)和一个排序.所以,现在所有的时间都在计算 before 发送数据。 - 令人困惑的是您只查看了 "duration" 而不是两次的总和。也就是总的时间是要注意的重点。
DISTINCT
稍微慢一点(总时间),因为收集和排序 5.7M 行的额外步骤。