MySQL之慢 SQL 定位、性能优化实践

MySQL之慢 SQL 定位、性能优化实践

IT大咖说 2023-07-05 20:00 发表于浙江

图片

在面试中被问到MySQL慢查询的概率还是非常高的。

说你没有经历过就是理由吗?显然不是。

一般来说一句SQL语句执行超过5s就能够算是慢SQL,需要进行优化了。

◆ 为何要对慢SQL进行治理

每一个SQL都需要消耗一定的I/O资源,SQL执行的快慢直接决定了资源被占用时间的长短。假设业务要求每秒需要完成100条SQL的执行,而其中10条SQL执行时间过长,从而导致每秒只能完成90条SQL,所有新的SQL将进入排队等待,直接影响业务,然后用户就各种投诉来了。

◆ 治理的优先级

  • master数据库->slave数据库 采用读写分离架构,读在从库slave上执行,写在主库master上执行。但由于从库的数据都是在主库复制过去的,主库如果等待较多的情况,会加大从库的复制延时

  • 执行SQL次数多的优先治理

  • 某张表被高并发集中访问的优先治理

 

◆ MySQL执行原理

为了更好的优化慢SQL,我们来简单了解下MySQL的执行原理

图片

绿色部分为SQL实际执行部分,主要分为两步:

  • 解析:词法解析->语法解析->逻辑计划->查询优化->物理执行计划,过程中会检查缓存是否可用,如果没有可用缓存则进入下一步mysql_execute_command执行

  • 执行:检查用户、表权限->表加上共享读锁->取数据到query_cache->取消共享读锁

 

◆ 如何发现慢查询SQL

-- 修改慢查询时间,只能当前会话有效;
set long_query_time=1; ­
-- 启用慢查询 ,加上global,不然会报错的;
set global slow_query_log='ON';­­
-- 是否开启慢查询;
show variables like "%slow%";­­
-- 查询慢查询SQL状况;
show status like "%slow%"; ­­
-- 慢查询时间(默认情况下MySQL认位10秒以上才是慢查询)
show variables like "long_query_time"; ­­

除了sql的方式,我们也可以在配置文件(my.ini)中修改,加入配置时必须要在[mysqld]后面加入

-- 开启日志;
slow_query_log = on
-- 记录日志的log文件(注意:window上必须写绝对路径)
slow_query_log_file = D:/mysql5.5.16/data/showslow.log
-- 最长查询的秒数;
long_query_time = 2
-- 表示记录没有使用索引的查询
log­queries­not­using­indexes ­­

特别注意:开启慢查询会带来CPU损耗与日志记录的IO开销,所以建议间断性的打开慢查询日志来观察MySQL运行状态

◆ 慢查询分析示例

假设我们有一条SQL

SELECT * FROM `emp` where ename like '%mQspyv%'; 

执行时间为1.163s,而我们设置的慢查询时间为1s,这时我们可以打开慢查询日志进行日志分析:

## Time: 150530 15:30:58 ­­ -- 该查询发生在2015­5­30 15:30:58
## User@Host: root[root] @ localhost [127.0.0.1] ­­ --是谁,在什么主机上发生的查询
## Query_time: 1.134065 Lock_time: 0.000000 Rows_sent: 8 Rows_examined: 4000000 Query_time: --查询总共用了多少时间,Lock_time: 在查询时锁定表的时间,Rows_sent: 返回多少rows数据,Rows_examined: 表扫描了400W行数据才得到的结果;

如果我们的慢SQL很多,人工分析肯定分析不过来,这时候我们就需要借助一些分析工具,MySQL自带了一个慢查询分析工具mysqldumpslow,以下是常见使用示例

mysqldumpslow ­s c ­t 10 /var/run/mysqld/mysqld­slow.log # 取出使用最多的10条慢查询
mysqldumpslow ­s t ­t 3 /var/run/mysqld/mysqld­slow.log # 取出查询时间最慢的3条慢查询
mysqldumpslow ­s t ­t 10 ­g “left join” /database/mysql/slow­log #得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow ­s r ­t 10 ­g 'left join' /var/run/mysqld/mysqldslow.log # 按照扫描行数最多的

◆ SQL语句常见优化

只要简单了解过MySQL内部优化机制,就很容易写出高性能的SQL

不使用子查询

SELECT * FROM t1 WHERE id (SELECT id FROM t2 WHERE name='hechunyang');

在MySQL5.5版本中,内部执行计划器是先查外表再匹配内表,如果外表数据量很大,查询速度会非常慢

再MySQL5.6中,有对内查询做了优化,优化后SQL如下

SELECT t1.* FROM t1 JOIN t2 ON t1.id = t2.id;

但也仅针对select语句有效,update、delete子查询无效,所以生成环境不建议使用子查询

避免函数索引

SELECT * FROM t WHERE YEAR(d) >= 2016;

即使d字段有索引,也会全盘扫描,应该优化为:

SELECT * FROM t WHERE d >= '2016-01-01';

使用IN替换OR

SELECT * FROM t WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30;

非聚簇索引走了3次,使用IN之后只走一次:

SELECT * FROM t WHERE LOC_IN IN (10,20,30);

LIKE双百分号无法使用到索引

SELECT * FROM t WHERE name LIKE '%de%';

应优化为右模糊

SELECT * FROM t WHERE name LIKE 'de%';

增加LIMIT M,N 限制读取的条数

避免数据类型不一致

SELECT * FROM t WHERE id = '19';

应优化为

SELECT * FROM t WHERE id = 19;

分组统计时可以禁止排序

SELECT goods_id,count(*) FROM t GROUP BY goods_id;

默认情况下MySQL会对所有GROUP BY co1,col2 …的字段进行排序,我们可以对其使用

ORDER BY NULL

禁止排序,避免排序消耗资源

SELECT goods_id,count(*) FROM t GROUP BY goods_id ORDER BY NULL;

去除不必要的ORDER BY语句

◆ Mysql 慢查询优化实践

◆ 目标:

提高mysql运行效率,增加并发,提高响应速度

◆ 方案:

通过阿里云给的慢查询日志excel,对耗时长,开销大的sql语句进行优化,提升访问速度服务器运行效率

◆ 实践:

分析 阿里云给的数据库单日报表有以下字段

  • Create Time DBName

  • MySQL Total Execution Counts

  • MySQL Total Execution Times

  • Max Execution Time

  • Max Lock Time

  • Parse Total Row Counts

  • Parse Max Row Count

  • Return Total Row Counts

  • Return Max Row Count

  • SQL Text

  • hard ware- latencies

分别是

  • 创建时间

  • 数据库名

  • mysql总执行数目

  • muysql总执行耗时

  • 最大执行耗时

  • 最大锁耗时

  • 解析总行数统计

  • 解析最大行数

  • 返回总计行数

  • 返回最大行数

  • sql语句

  • 硬件延迟

根据阿里云提供的慢查询记录,本次采用的优化策略如下:

查询次数超过100次/日的高频需求,按照最大查询/总查询用时最大,依次优化取得的优化收益最高.

◆ 第一条语句:

执行次数: 1114 最大耗时: 7 解析最大行数: 348325 返回最大行数 4 #执行次数: 1114 最大耗时: 7 解析最大行数: 348325 返回最大行数 4

csharp复制代码select id from appname_m_members where yiku_id = :1

可以看出,这个简单的sql不应该有这么大的解析行数,甚至最高要七秒钟.
初步判断没有在yiku_id这个字段加索引的可能性最大.现在我们需要寻求各种办法来验证下我们的猜测

◆ 分析

explain select id from appname_m_members where yiku_id = 1;

 

图片

可以看到的确是没有给yiku_id增加索引.

索引的特点
对于查询操作能迅速缩小查询范围,减少row的数量,指数级提高查询速度点
对于写操作,因为需要维护索引的变更,有一定开销.如果遇到大量并发写入,会有负面影响.
在这个表用来记录我们微信用户和应用id的关系,所以读的操作较之写操作更多,所以能够增加索引.

markdown复制代码#增加索引

ALTER TABLE `appname_m_members`
ADD INDEX `yiku_id` (`yiku_id`) ;

尝试增加索引之后,再次分析语句的执行

图片

◆ 结果:

匹配范围 rows 从32w 降低到1

可以看到type从all的全表扫描变成ref的单个行的索引访问,rows从全表32w降为1,说明添加索引对这条语句产生了巨大效果.

◆ 第二条语句:

执行次数: 482 最大耗时: 15 解析最大行数: 764383 返回最大行数: 482

javascript复制代码#执行次数: 482 最大耗时: 15 解析最大行数: 764383 返回最大行数: 482
select fullname as username , linkphone as userphone ,
`userimage` , `nickname` , `hospitalname` , `partmentname` ,
`doctortitle` , `iscertification` , `fullname`
from `users`
where `useruuid` = '597_f66e1cb79341cedf6f24aaf01fde8611' limit 1;

◆ 分析:

图片

◆ 对其增加索引:

markdown复制代码#增加索引
ALTER TABLE `users`
ADD INDEX `useruuid` (`useruuid`);

 

图片

直接将扫描范围(rows)从72w降到了1,提升明显

◆ 结果:

匹配范围 rows 从72w 降低到1

◆ 第三条语句:

执行次数: 820 最大耗时: 10 解析最大行数: 167214 返回最大行数 1

csharp复制代码#执行次数: 820 最大耗时: 10 解析最大行数: 167214  返回最大行数 1
select count ( postingid ) as postnum from mediposting
where isaudit != :1
and isgoodcase = :2
and postsection = :3

◆ 分析:

图片

改变sql语句的顺序,按照最左原则修改如下

csharp复制代码select count(postingid) as postnum from mediposting 
where postsection = 1
and isgoodcase = 1
and isaudit != 1

◆ 结果:

主要使用的是 postsection 作为索引来统计总数,这部分无需优化.

shell复制代码##### 第四条语句:
执行次数: 482 最大耗时: 15 解析最大行数: 764383 返回最大行数: 482
##执行次数: 410 最大耗时: 10 解析最大行数:348325 返回最大行数 1
........

◆ 结果: 语句过长(2017个字符),嵌套了逻辑,暂不优化

◆ 第五条语句:

执行次数: 659 最大耗时: 6 解析最大行数:215115 返回最大行数 659

perl复制代码## 执行次数: 659 最大耗时: 6 解析最大行数:215115   返回最大行数 659
select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
`medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
`medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
( initalscore+effectevaladd ) as `effectval`
from `medigooddoc`
where ( ( initalscore+effectevaladd ) > 80 )
order by rand ( ) limit 1 ;

◆ 分析:

rand()函数放在order by后面会被执行多次,优化方式: 求出随机id后,取得对应记录

javascript复制代码select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
`medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
`medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
( initalscore+effectevaladd ) as `effectval`
from `medigooddoc`
where (initalscore+effectevaladd) > 80
and docid > (
RAND() * (
(SELECT MAX(docid) FROM `medigooddoc`)
-
(SELECT MIN(docid) FROM `medigooddoc`)
)
+
(SELECT MIN(docid) FROM `medigooddoc`)
)
order by `docid` limit 1;

优化前语句:

图片

可以看到扫描范围很大(rows) 120 770行.

图片

可以看到

  • 查询范围最小(rows) 1

  • 获取最大值/最小值是直接从mysql查询优化器返回数据(extra).mysql文档中有以下解释:

The query contained only aggregate functions (MIN(), MAX()) that were all resolved using an index, or COUNT(*) for MyISAM, and no GROUP BY clause. The optimizer determined that only one row should be returned.

测试执行效率:

  • 执行10次 a: 2 941 ms b: 168 ms

  • 执行50次 a: 14 441 ms b: 828 ms

  • 执行100次a: 29 126 ms b: 1 645 ms

可以看到每百次运行时间已经从30s缩短到不到2秒,大大提高查询mysql响应速度. 但是还有个问题,总共100 000的id,原来的语句查询出的结果比较平衡,有过万也有几千,但是用这个语句后,总是出现小于一万的id,结果在我们预期之外.

修正概率偏差

◆ 方案1:

增加一次对数据库消耗不大的表查询

python复制代码# php
$round = select max(docid) as max,min(docid) as min from medigooddoc;
$rand = rand($round['min'],$round['max']);
perl复制代码# sql
select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
`medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
`medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
( initalscore+effectevaladd ) as `effectval`
from `medigooddoc`
where (initalscore+effectevaladd) > 80
and docid > $rand
order by `docid` limit 1;

这样的问题是:会多产生一个sql交互,数据库

◆ 方案2:

使用内连接 join 优化

perl复制代码#可用一
select `docid` ,`docname`,
`doctitle` , `docimgurl` ,
`docdep` , `dochospital` ,
( initalscore+effectevaladd ) as `effectval`
from `medigooddoc` as t1
join (
select rand() * (select max(docid) from `medigooddoc`)
as rand
) as t2
where (t1.initalscore+t1.effectevaladd) > 80
and `t1`.`docid` >= t2.rand
order by `docid` limit 1;

但是这样有一个问题:并不是完全平均落到每条记录上,因为记录并不是连续的

修正概率 rand * 数量范围,这样概率平均到整张表存在的记录中.

javascript复制代码select `docid` ,`docname`,
`doctitle` , `docimgurl` ,
`docdep` , `dochospital` ,
( initalscore+effectevaladd ) as `effectval`
from `medigooddoc` as t1
join (
select rand() *
(
(select max(docid) from `medigooddoc`)
-
(select min(docid) from `medigooddoc`)
)
+
(select min(docid) from `medigooddoc`)
as rand
) as t2
where (t1.initalscore+t1.effectevaladd) > 80
and `t1`.`docid` >= t2.rand
order by `docid` limit 1;

综合来说,因为方案1 产生了更多的数据库交互,因为我们的数据库是另一台服务器,网络连接开销是比较大的,额外的查询也会在高并发的时刻对数据库产生更大压力.

而方案2采用内连接的方式,仅需要一次数据库交互就能完成,最大最小值也是直接由mysql查询器返回,减少了种种数据库性能开销.故采用为最佳方案..

图片

◆ 结果:

使用mysql保存的表结构信息替代了order rand()的低效率查询.

◆ 深入理解:

图片

◆ 第六条语句:

执行次数: 729 最大耗时: 4秒 解析最大行数:130898 返回最大行数 2

go复制代码select `medigooddoc`.`docid` , `medigooddoc`.`yikuid` 
from `medigooddoc`
where ( yikuid = 597725 or yikuid = -597725 );

◆ 分析:

图片

◆ 优化方案:

字段yikuid加索引

go复制代码ALTER TABLE `medigooddoc`
ADD INDEX `YiKuID` (`YiKuID`);

再次执行explain分析

图片

◆ 结果:

匹配范围 rows 从8.3w 降低到1

◆ 第七条语句

执行次数: 474 最大耗时: 5秒 解析最大行数:261797 返回最大行数 1

select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
`medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl`
from `medigooddoc` order by rand ( ) limit 1;

◆ 分析

图片

◆ 方案

将获取一条随机记录 由order by rand() limit 1 改为 内连接方式

select `docid`, `docname`,
`doctitle` , `docimgurl`
from `medigooddoc` as t1
inner join
(
select rand() *
(
(select MAX(docid) from `medigooddoc`)
-
(select MIN(docid) from `medigooddoc`)
)
+
(select MIN(docid) from `medigooddoc`)
as rand
) as t2
on t1.docid >= t2.rand
order by docid limit 1;

再次执行explain分析

图片

◆ 结果

用mysql存储的表信息替代了效率低下的order by rand()

◆ 第八条语句

执行次数: 136 最大耗时: 7秒 解析最大行数:301880 返回最大行数 1

go复制代码select `searchrecords`.`searchid` , `searchrecords`.`searchnum` 
from `searchrecords`
where ( searchtype = 0 ) and ( userid = 14 )
and ( searchmsg = '碳酸钙D3' );

◆ 方案

索引的目的是为了缩小查询范围,通过文字内容的前三个字区分,通过userid进行区分,可以得到范围更精确的语句执行

sql复制代码ALTER TABLE searchrecords ADD INDEX searchmsg (searchmsg(5));
ALTER TABLE searchrecords ADD INDEX userid (userid);

通过文本前5个字建立索引来区分范围后,范围缩小到28个记录

图片

再通过用户ID建立索引,进一步缩小范围,仅需要查找1条记录

图片

分析索引对写入的影响 表主要用来记录用户搜索的高频词,主要的写操作时更新统计字段,这两个新增索引的字段并不会频繁更新,故索引开销不大.

◆ 结果

匹配范围从 29w 缩小到 1

◆ 第九条语句

less复制代码select `projects`.`id` , `projects`.`guid` , 
`projects`.`getittime` , `projects`.`keywords` ,
`projects`.`barcode` as `num` , `projects`.`goodcasedep` ,
`projects`.`bingshi` , `pictures`.*
from `projects`
inner join `pictures` on projects.guid = pictures.projectid
and pictures.filetype = :1
where ( islock != :2 ) and ( isgoodcase = :3 )
and ( ( goodcasedep like :4 or goodcasedep like :5
or goodcasedep like :6 or goodcasedep like :7
or goodcasedep like :8 or goodcasedep like :9
or goodcasedep like :10 or goodcasedep like :11
or goodcasedep like :12 or goodcasedep like :13
or goodcasedep like :14 or goodcasedep like :15
or goodcasedep like :16 or goodcasedep like :17
or goodcasedep like :18 or goodcasedep like :19
or goodcasedep like :20 or goodcasedep like :21
or goodcasedep like :22 or goodcasedep like :23
or goodcasedep like :24 or goodcasedep like :25
or goodcasedep like :26 or goodcasedep like :27
or goodcasedep like :28 or goodcasedep like :29
or goodcasedep like :30 or goodcasedep like :31
or goodcasedep like :32 or goodcasedep like :33
or goodcasedep like :34 or goodcasedep like :35
or goodcasedep like :36 or goodcasedep like :37
or goodcasedep like :38 or goodcasedep like :39
or goodcasedep like :40 or goodcasedep like :41 ) )
order by rand ( ) limit :42

◆ 结果:

暂不修改:超过字节限制

◆ 第十条语句

执行次数: 145 最大耗时: 2秒 解析最大行数:130898 返回最大行数 1

go复制代码select `medigooddoc`.`isfollow` , `medigooddoc`.`isconsult` ,
`medigooddoc`.`isphone` , `medigooddoc`.`isprivate`
from `medigooddoc` where ( yikuid = 694 );

◆ 分析:

图片

◆ 方案

增加索引

sql复制代码ALTER TABLE `medigooddoc` ADD INDEX YiKuID(`YiKuID`);

再次执行explain分析

图片

◆ 结果

匹配范围从12w缩小到1

◆ 第十一条

执行次数: 148 最大耗时: 3秒 解析最大行数:74616 返回最大行数 30

go复制代码select `magazinearticle`.`articleid` , 
`magazinearticle`.`articletitle` ,
`magazinearticle`.`article_publishtime` ,
`magazinearticle`.`articlepicpath` ,
`magazinearticle`.`articleurl` ,
`magazinearticle`.`articlenum` ,
`magazinearticle`.`perid` ,
`magazinearticle`.`article_originallink` ,
`magazinearticle`.`islink` from `magazinearticle`
where ( logicdel = 0 ) and ( perid != 60 )
order by `article_publishtime` desc limit 1,30;

◆ 分析:

图片

◆ 方案:

由于是读多写少的文章表,增加索引适用这类场景,提高查询响应速度.

sql复制代码ALTER TABLE `magazinearticle` ADD INDEX 
article_publishtime(`article_publishtime`);

再次执行explain分析

图片

◆ 结果:

匹配范围 rows 从2w缩小到59

◆ 深入理解:

explain type的不同种类

类型

含义

类型

含义

system

表只有一行

const

表最多只有一行匹配,通用用于主键或者唯一索引比较时

eq_ref

每次与之前的表合并行都只在该表读取一行,这是除了system,const之外最好的一种,特点是使用=,而且索引的所有部分都参与join且索引是主键或非空唯一键的索引

ref

如果每次只匹配少数行,那就是比较好的一种,使用=或<=>,可以是左覆盖索引或非主键或非唯一键

fulltext

全文搜索

ref_or_null

与ref类似,但包括NULL

index_merge

表示出现了索引合并优化(包括交集,并集以及交集之间的并集),但不包括跨表和全文索引。
这个比较复杂,目前的理解是合并单表的范围索引扫描(如果成本估算比普通的range要更优的话)

unique_subquery

在in子查询中,就是value in (select...)把形如“select unique_key_column”的子查询替换。
PS:所以不一定in子句中使用子查询就是低效的!

index_subquery

同上,但把形如”select non_unique_key_column“的子查询替换

range

常数值的范围

index

a.当查询是索引覆盖的,即所有数据均可从索引树获取的时候(Extra中有Using Index)
b.以索引顺序从索引中查找数据行的全表扫描(无 Using Index);
c.如果Extra中Using Index与Using Where同时出现的话,则是利用索引查找键值的意思;
d.如单独出现,则是用读索引来代替读行,但不用于查找

all

全表扫描

◆ 第十二条

执行次数: 135 最大耗时: 3秒 解析最大行数:78395 返回最大行数 0

sql复制代码select distinct userid from weekhosnominate 
where userid = 351211 and datatype = 4

◆ 分析

图片

◆ 方案

sql复制代码ALTER TABLE `weekhosnominate` ADD INDEX UserID(`UserID`);

再次执行explain分析

图片

◆ 结果

匹配范围 rows 从1w缩小到288

◆ 第十三条

执行次数: 110 最大耗时: 2秒 解析最大行数:87693 返回最大行数 1

ini复制代码select `inspectioninfo`.`itemmsg` from `inspectioninfo` 
where ( itemid in ( 30 ,31 ) and itemtype = 0
and inspectionid = 109 ) limit 1 ;

◆ 分析

图片

◆ 方案:

增加索引

sql复制代码ALTER TABLE `inspectioninfo` ADD INDEX 
InspectionID(`InspectionID`);

再次执行explain分析

图片

◆ 结果

匹配范围 rows 从 5w 缩小到 13

◆ 第十四条语句

执行次数: 103 最大耗时: 2秒 解析最大行数:78395 返回最大行数 0

select `weekhosnominate`.`id` from `weekhosnominate` 
where ( userid = 351211 );

◆ 分析:

图片

◆ 方案:

通过给字段 userid 建立索引来区分,缩小范围

sql复制代码ALTER TABLE `weekhosnominate` ADD INDEX UserID(UserID) ;

再次执行explain分析可以发现, 通过索引 userid 将范围由全表扫描的近万到索引指向的数十条记录.

图片

◆ 结果:

匹配范围 rows 从 9k 缩小到 288

◆ 深入理解:

图片

◆ mysql索引原理

索引目的
索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者ze开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?

索引原理
除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的目录等。它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。

数据库也是一样,但显然要复杂许多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。

但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。

磁盘IO与预读
前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。

那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考:

图片

硬件处理延

◆ 总结

总的来说,我们知道曼查询的SQL后,优化方案可以做如下尝试:

  • SQL语句优化,尽量精简,去除非必要语句

  • 索引优化,让所有SQL都能够走索引

  • 如果是表的瓶颈问题,则分表,单表数据量维持在2000W(理论上)以内

  • 如果是单库瓶颈问题,则分库,读写分离

  • 如果是物理机器性能问题,则分多个数据库节点

     

 

来源:https://www.toutiao.com/article/7234018475412193829/?log_from=fe9bc67bd470a_1688518700843

“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com

 

 

图片

 

 

来都来了,走啥走,留个言呗~

 

 

 

 IT大咖说  |  关于版权 

由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!

感谢您对IT大咖说的热心支持!

 

相关推荐

 

推荐文章

阅读原文
阅读 1251
IT大咖说
278篇原创内容
 
 
posted @ 2023-07-10 09:17  往事已成昨天  阅读(332)  评论(0编辑  收藏  举报