MySql复杂查询优化分析(附测试数据表生成代码)

MySql自连接查询分析(附测试数据表生成代码)

在MySql中,对于同样的查询结果(目的),使用不同的查询语句所需要得时间开销是不同的

场景1 查找新用户

1.1 场景描述

有这样一个数据表,记录用户的每一次使用信息(表中数据可通过文章后面附录提供的python程序添加)

在这里插入图片描述

现在需要在这个表中找到在某一天(2020-06-01)新增的用户列表,即当且仅当他最早的logTime处于2020-06-01 00:00:002020-06-02 00:00:00之间

下面有两种查找思路

1.2 先查找出所有用户的第一次使用时间,再筛选出第一次使用日期在这一天的用户

查找所有用户的第一次使用时间

SELECT uid,MIN(logTime) FROM t1 GROUP BY uid

在这里插入图片描述

再从中筛选出第一次使用时间在这一天的

SELECT a.uid,a.mt FROM 
(SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a 
WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

1.3 先查找出当天产生了使用信息的所有用户列表,再从中筛选出新用户

查找当天产生了使用信息的所有用户:

SELECT uid, logTime FROM t1 WHERE logTime BETWEEN "2020-06-01" AND "2020-06-02" GROUP BY uid

在这里插入图片描述

再从中筛选出新用户

SELECT a.uid,a.mt FROM
((SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a inner JOIN 
(SELECT uid FROM t1 WHERE logTime BETWEEN "2020-06-01" AND "2020-06-02" GROUP BY uid) AS b ON 
a.=b.uid) WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

1.4 初步分析与实验结果

从逻辑上看,第一种查找思路首先需要查找出所有用户的第一次使用时间,因此需要扫描所有的行(131183行)

而第二种首先通过时间条件筛选掉了所有没有在这一天产生使用记录的用户,因此只需要扫描这151行记录

那么应该是第一种查找思路的效率要更高一些,但实验结果却是第一种查找思路要更快

在这里插入图片描述

经过多次实验,第一种思路查找所耗费的平均时间在0.36s左右,第二种在0.42s左右

1.5 原因分析与论证

先论证以下两种解释

①数据库服务端自动对查询过程进行了优化
②第二种查找思路的查询语句较为复杂,数据库解析查询语句的时间要比执行查询过程的时间要长得多

先尝试对②进行论证,通过逐步增加表中的数据量,对比两种查找思路所耗费的时间,得下表

总记录行数2020.6.1当天使用用户数结果行数思路1平均耗费时间(s)思路2平均耗费时间(s)
131183151460.360.42
191127208270.450.54
2438863511310.590.70
4429534611311.061.30

可以看到随着表格规模的增大,差距仍在增大,解释②不成立

下面将使用explain语句进行优化分析

1.6 explain语句

explain语句可以对查询语句进行预分析

explain SELECT uid,col1 FROM t1 WHERE col1<10

在这里插入图片描述
可以显示语句预计要查找的表格和要扫描的行数等信息,试着对两种思路的语句进行explain

explain
SELECT a.uid,a.mt FROM (SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

explain
SELECT a.uid,a.mt FROM
((SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a inner JOIN 
(SELECT uid FROM t1 WHERE logTime BETWEEN "2020-06-01" AND "2020-06-02" GROUP BY uid) AS b ON 
a.uid=b.uid) WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

可以看到第二种思路并没有起到缩小查找范围的目的,相比于第一种查询方式还多了一个需要全局扫描的子查询,注意到所有的查询类型(type字段)都是ALL,接下来考虑加入索引来对查询进行优化

1.7 利用索引进行查询优化

sql中的索引可以简单理解为提前将查找过程中的筛选条件按照一定的规则排列好,或者说根据筛选条件中特定的列将原始表做一个数据字典,查找的时候就可以按照该列的索引快速定位到要找的列,这里的规则一般有B-树、Hash映射和R-树,先看一个简单的列子

有一个表格t2如下:

SELECT * FROM t2

在这里插入图片描述
在没有创建索引前查找score小于80的记录,并用explain分析该语句

explain SELECT * FROM t2 WHERE score<80

在这里插入图片描述

这里的筛选条件是score,那就对score建立一个简单的B-树索引,再进行分析

CREATE INDEX score_idx ON t2(score)
explain SELECT * FROM t2 WHERE score<80

在这里插入图片描述
发现除了possible_keys有变化其它没什么改变,这是因为select * 查找了所有的列,将索引删除重新建一个包含所有列的新索引

drop INDEX score_idx ON t2
CREATE INDEX score_idx ON t2(score,uid,sex)
explain SELECT * FROM t2 WHERE score<80

在这里插入图片描述

预计扫描的行变成了3行,type也变成了range,利用索引的确达到了优化特定查询的目的,更多关于索引的细节可参考这篇博客

接下来对之前的查询建立索引来优化查询

CREATE INDEX time_uid ON t1(logTime,uid)
explain SELECT a.uid,a.mt FROM (SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

explain
SELECT a.uid,a.mt FROM
((SELECT uid,MIN(logTime) AS mt FROM t1 GROUP BY uid) AS a inner JOIN 
(SELECT uid FROM t1 WHERE logTime BETWEEN "2020-06-01" AND "2020-06-02" GROUP BY uid) AS b ON 
a.uid=b.uid) WHERE a.mt BETWEEN "2020-06-01" AND "2020-06-02"

在这里插入图片描述

加入索引后第一种查询思路基本没有什么变化,但第二种查询思路只需要进行一次全表扫描再加上三次小范围的扫描,有了很明显的优化,执行时间也缩短了

加入索引前的时间加入索引后的时间
第一种1.061.09
第二种1.301.09

虽然还是没有通过复杂的查询语句实现加速查询的效果,但至少可以跟简单查询持平了,深层次的原因以后有时间再做研究

附录1 python随机生成表格记录代码

首先需要在数据库中新建相应的表格

import pymysql
import random
import calendar
import datetime

mysqlHost="*.*.*.*"
mysqlPort=3306
mysqlTable="t1"
mysqlDB="***"
userName="****"
userPwd="*****"

#随机生成时间字符串
def creatTimeStrByChance():
    yearNum=random.randint(2017,2020)
    monthNum=random.randint(1,12)
    dayNum=random.randint(1,calendar.monthrange(yearNum,monthNum)[1])
    hNum=random.randint(0,23)
    mNum=random.randint(0,59)
    sNum=random.randint(0,59)
    mydate=datetime.datetime(yearNum,monthNum,dayNum,hNum,mNum,sNum)
    return mydate.strftime("%Y-%m-%d %H:%M:%S")

#随机生成id
def creatUidByChance():
    uid="A"
    for i in range(3):
        uid+=("%s"%random.randint(0,9))
    return uid

conn1=pymysql.connect(mysqlHost,user=userName,passwd=userPwd,db=mysqlDB,charset='utf8',port=mysqlPort)
cur1=conn1.cursor()#游标

#插入100000条数据
for i in range(100000):
    # print(creatTimeStrByChance(),creatUidByChance())
    myqury="INSERT INTO analyse.t1(uid,logTime,col1) VALUES(\"%s\",\"%s\",%s)"%(creatUidByChance(),creatTimeStrByChance(),random.randint(0,100))
    cur1.execute(myqury)
    conn1.commit()

conn1.close()
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值