MySQL原理简介—11.优化案例介绍
大纲
1.禁止或改写SQL避免自动半连接优化
2.指定索引避免按聚簇索引全表扫描大表
3.按聚簇索引扫描小表减少回表次数
4.避免产生长事务长时间执行
1.禁止或改写SQL避免自动半连接优化
(1)业务场景介绍
(2)SQL性能问题分析
(3)SQL性能调优
(1)业务场景介绍
某互联网公司的用户量比较大,有百万级日活用户的一个量级。该公司的运营系统会专门通过各种条件筛选出大量用户发送推送消息,比如一些促销活动的消息、办会员卡的消息、特价商品的消息。在这个过程中,比较耗时的是筛选用户的过程。
因为该公司当时的用户情况是:日活百万级、注册用户是千万级,而且还没有进行分库分表,其数据库里的用户表可能就一张,单表里是上千万的用户数据。
现在对运营系统筛选用户的SQL做一个简化,这个SQL经过简化看起来可能是这样的:
SELECT id, name FROM users WHERE id IN
(SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx)
一般存储用户数据的表会分为两张表:users表用来存储用户的核心数据,如id、name、昵称、手机号等信息。users_extent_info表则存储用户的一些拓展信息,如最近登录时间等。
所以上面的SQL语句的意思就是:首先有个子查询向用户拓展信息表查询最近登录时间小于某时间的用户,然后在外层查询用in去查询id在子查询结果范围里的users表的所有数据,此时这个SQL往往会查出来很多数据,可能几千、几万、几十万。
所以一般运行这类SQL前,会先跑一个count聚合函数看有多少条数据,然后在内存里做一个小批量多批次读取数据的操作。
SELECT COUNT(id) FROM users WHERE id IN
(SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx)
比如判断如果在1000条以内,那么就一下子读取出来。如果超过1000条,可通过limit语句每次从该结果集里查1000条数据。查1000条就做一次批量push,然后再查下一批1000条。
这就是这个案例的一个完整的业务背景,那么它会产生的问题是:在千万级数据量的大表场景下,执行上面查询数量的SQL需耗时几十秒,所以这个SQL急需优化。
(2)SQL性能问题分析
通过如下语句可得这个复杂SQL的执行计划:
EXPLAIN SELECT COUNT(id) FROM users WHERE id IN
(SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx)
为了方便查看,下面对执行计划进行简化,只保留最关键的字段。另外,下面执行计划是在测试环境的单表5万条数据场景下跑出来的。即使是几万条数据,这个SQL都跑了十多秒,所以足够复现生产问题。
+----+-------------+-------------------+-------+----------------+------+----------+-------+
| id | select_type | table | type | key | rows | filtered | Extra |
+----+-------------+-------------------+-------+----------------+-----------------+-------+
| 1 | SIMPLE | <subquery2> | ALL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | users | ALL | NULL | 49651| 10.00 | Using where; Using join buffer(Block Nested Loop) |
| 2 | MATERIALIZED| users_extent_info | range | idx_login_time | 4561 | 100.00 | NULL |
+----+-------------+-------------------+-------+----------------+------+----------+-------+
从上面的执行计划由下往上看,可清晰看到这个SQL语句的执行过程。首先第一条执行计划是在第三行,针对的是子查询的执行计划。表示对users_extent_info使用idx_login_time索引做了range类型的查询,查出来4561条数据,没有做其他的额外筛选,所以filtered是100%。MATERIALIZED表明把子查询的结果集进行物化,物化成一个临时表。这个临时表物化,会把4561条数据临时存放到磁盘文件里,该过程较慢。
然后第二条执行计划的ALL类型表明,会针对users表进行全表扫描。会扫出来49651条数据,其中Extra显示了一个Using join buffer的信息。这个Using join buffer明确表示,此处在执行join操作。
最后第三条针对子查询的物化临时表,也就是做全表查询。把里面的数据都扫描一遍,那么为什么要对这个临时表进行全表扫描呢?原因就是让users表的每一条数据,跟物化临时表里的数据进行join。所以针对users表里的每一条数据,会去全表扫描一遍物化临时表,查找物化临时表里哪条数据是跟它匹配的,从而筛选出一条结果。
第二条执行计划的全表扫描结果表明一共扫到了49651条数据,但是全表扫描的过程中,因为去跟物化临时表执行了一个join操作,而物化临时表就4561条数据,所以第二条执行计划的filtered显示10%。也就是说,最终从users表里筛选出的也是4000多条数据。
以上的执行计划,不同MySQL版本可能不一样,甚至差别很大。但是对这个SQL语句的执行计划过程的分析过程基本是一样的。
(3)SQL性能调优
一.总结上述SQL的执行过程
EXPLAIN SELECT COUNT(id) FROM users WHERE id IN
(SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx)
+----+-------------+-------------------+-------+----------------+------+----------+-------+
| id | select_type | table | type | key | rows | filtered | Extra |
+----+-------------+-------------------+-------+----------------+-----------------+-------+
| 1 | SIMPLE | <subquery2> | ALL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | users | ALL | NULL | 49651| 10.00 | Using where; Using join buffer(Block Nested Loop) |
| 2 | MATERIALIZED| users_extent_info | range | idx_login_time | 4561 | 100.00 | NULL |
+----+-------------+-------------------+-------+----------------+------+----------+-------+
首先执行子查询查出4561条数据,然后将这些数据物化成一个临时表。接着对users主表进行全表扫描,本质就是和物化临时表进行join操作。全表扫描的过程会把users表的每条数据都放到物化临时表里全表匹配。
二.上述SQL为什么会这么慢
根据explain出来的执行过程,对users表的全表扫描很耗时。对users表的每一条数据再到物化临时表里做全表扫描,也很耗时。所以整个过程必然是非常慢的,几乎没怎么用到索引。
三.semi join是什么意思
那么为什么会出现上述的这么一个过程:即首先全表扫描users表,然后再和物化临时表进行join,接着join的时候还全表扫描物化临时表。
这里有一个技巧,就是在执行完SQL的explain命令,看到执行计划后,可以执行一下show warnings命令。这个show warnings命令此时显示出来的内容如下:
/* select#1 */
select count(`d2.`users`.`user_id``) AS `COUNT(users.user_id)`
from `d2`.`users` `users` semi join xxxxxx
...
下面省略一大段内容,因为可读性实在不高,重点关注的应该是里面的semi join这个关键字。这里就显而易见了,MySQL在生成执行计划时:自动把一个普通的in子句优化成基于semi join来进行in + 子查询的操作。
semi join的意思就是:对users表里每一条数据,去物化临时表进行全表扫描做semi join。此时不需要把users表里的数据真的跟物化临时表里的数据join上,而是只要users表里的一条数据在物化临时表里找到匹配的数据就返回。这就叫做semi join,它是用来筛选的,所以慢就慢在这里了。
四.如何优化semi join
那既然知道了是semi join和物化临时表导致的问题,那应该如何优化?
先执行SET optimizer_switch='semijoin=off',即关闭掉半连接优化。再执行EXPLAIN命令看一下执行计划,发现此时会恢复为正常的状态。有个SUBQUERY子查询,基于range方式扫描索引查询出4561条数据。有一个PRIMARY主查询,基于id这个PRIMARY聚簇索引去执行的搜索。最后再重新执行该SQL,发现性能一下提升了几十倍,变成100多毫秒。
因此到此为止,这个SQL的性能问题,就是MySQL自动执行的semi join半连接优化导致的。一旦禁止semi join自动优化,让MySQL基于索引执行,性能是可以的。
当然生产环境是不能更改这些设置的,所以要尝试修改SQL语句的写法。在不影响语义的情况下,尽可能的去改变SQL语句的结构和格式,最终被尝试出了一个写法如下所示:
SELECT COUNT(id)
FROM users
WHERE ( id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx) OR id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < -1)
)
上述写法WHERE语句的OR后面的第二个条件,根本是不可能成立的。因为没有数据的latest_login_time小于-1,所以不会影响SQL语义。但是可以发现改变了SQL的写法后,执行计划也随之改变。没有再进行semi join优化,而是正常用子查询,主查询也基于索引执行。上线了这个SQL语句后,性能从几十秒一下子就变成几百毫秒了。
2.指定索引避免按聚簇索引全表扫描大表
(1)业务场景引入
(2)SQL性能问题分析
(3)SQL性能调优分析
(4)案例总结
(1)业务场景引入
前面案例的主要问题在于MySQL内部自动使用了半连接优化,结果半连接时导致大量无索引的全表扫描,引发了性能的急剧下降。接下来的案例也类似,MySQL在选择索引时选了一个不太合适的索引。
先从当时线上的商品系统出现的一个慢查询告警开始讲起,某天晚上突然收到线上数据库的频繁报警。这个报警的意思是,数据库突然涌现出了大量慢查询。
因为大量的慢查询,导致每个数据库连接执行一个慢查询都要耗费很久。这样也必然导致突然过来的很多查询需要让数据库开辟出更多的连接。因此这时也发出了报警,显示数据库的连接突然也暴增了。于是连接池被打满,每个连接都要执行一个慢查询,慢查询还特别慢。
接着引发的问题,就是数据库的连接池全部被打满,没法开辟新的连接。但是还持续有新的查询发送过来,导致数据库没法处理新的查询。很多查询发到数据库直接阻塞然后超时,导致线上商品系统频繁报警,出现了大量数据库查询超时报错的异常。
这种情况基本意味着商品数据库以及商品系统濒临崩溃,大量慢查询耗尽了数据库的连接资源,最终导致数据库没法执行新查询。商品数据库没法执行查询,用户就没法正常使用商品系统。虽说商品数据会有多级缓存,但在下单等过程,还是会大量请求MySQL。也就是晚高峰时,商品系统本身TPS大致是在每秒几千;因此这时数据库的监控里显示,每分钟的慢查询超过了10w+,也就是商品系统大量的查询都变成了慢查询。
那么慢查询的都是一些什么语句呢?其实主要就是下面这条语句,这里做了一个简化:
select * from products
where category='xx' and sub_category='xx'
order by id desc limit xx,xx
这是一个很平常的SQL语句,就是根据商品的品类以及子类来浏览商品。这个语句执行的商品表里大致是1亿左右的数据量,这个量级已经稳定了很长时间,主要也就是这么多商品。但上面的语句居然一执行就需要几十秒,基本上数据库的连接池会全部被慢查询打满。一个连接要执行几十秒的SQL,然后才能执行下一个SQL,此时数据库基本就废了,没法执行什么查询了,所以商品系统大量进行报警说查询数据库超时异常了。
(2)SQL性能问题分析
上面那条让用户根据品类筛选商品的SQL语句,在一个亿级数据量的商品表里执行,需要耗时几十秒,结果导致数据库的连接池全部打满,商品系统无法运行,处于崩溃状态。
下面来分析一下,到底为什么会出现这样的一个情况。这个表当时肯定是对经常用到的查询字段都建立好了索引的。那么可以认为索引index_category(catetory, sub_category)肯定存在的,所以基本可以确认上面的SQL语句是可以用上索引的。
理论上一旦用上index_category索引,那么按品类和子类在索引里筛选。第一筛选很快速,第二筛出来的数据是不多的。所以按道理这个SQL语句应该执行的速度是很快的,即使表里有亿级数据,但是执行时间也最多不应超过1s。但是实际上这个SQL语句跑了几十秒,那么说明它肯定没用上建立的那个索引,所以才会这么慢。
那么它到底是怎么执行的呢,可以先来看一下它的执行计划:
explain select * from products
where category='xx' and sub_category='xx'
order by id desc limit xx,xx
从执行计划中的possible_keys里发现是有index_category的,结果实际用的key不是index_category索引,而是PRIMARY主键索引,而且Extra里清晰写了Using where。
到此为止,这个SQL语句为什么性能这么差,就真相大白了。它其实就是在主键的聚簇索引上进行扫描,一边扫描一边还用where条件里的两个字段去进行筛选。所以这么扫描的话,那必然会耗费几十秒了。
因此为了快速解决这个问题,就需要强制改变MySQL自动选择不合适的聚簇索引进行扫描的行为,也就是通过使用force index语法来强制改变,如下:
select * from products
force index(index_category)
where category='xx' and sub_category='xx'
order by id desc limit xx,xx
使用上述语法过后,强制让SQL语句使用指定的索引。此时再次执行会发现仅仅耗费100多毫秒而已,性能瞬间就提升上来了。因此当时在紧急关头中,一下子就把这个问题给解决了。
这也是如何去强制改变MySQL执行计划的实战技巧,也就是如果MySQL使用了错误的执行计划,可以使用force index语法。
(3)SQL性能调优分析
一.为什么案例中的MySQL会默认选择对主键的聚簇索引进行扫描
二.为什么案例中没有使用index_category这个二级索引进行扫描
三.即使使用了聚簇索引,为什么这个SQL以前没问题,现在突然有问题
一.为什么MySQL默认选择聚簇索引而不选择index_category二级索引
因为这个商品表是一个亿级数据量的大表,那么对于它来说,index_category这个二级索引也是比较大的。所以此时MySQL就会对判断要选择执行方式一还是选择执行方式二。
执行方式一:
首先从index_category二级索引里来查找到符合where条件的大量数据,接着需要做完order by id desc limit 0,10这个排序和分页的操作,然后回表回到聚簇索引里把所有字段的数据都查出来。
举个例子,比如首先从index_category二级索引里查找出几万条数据。接着因为二级索引里包含主键id值,所以会按order by id desc执行排序。排序时需要对这几万条数据基于临时磁盘文件进行filesort排序。排序完后再按照limit 0,10语法,把指定位置的几条数据拿出来。拿出数据后,再回到聚簇索引里根据id查找,把数据的完整字段查出来。
以上就是MySQL认为使用index_category索引时可能会发生的执行情况。MySQL可能会担心从index_category二级索引里查出来的数据太多了,这么多数据还需要在临时磁盘里排序,这样性能就会很差。因此MySQL就会认为使用index_category二级索引的方式不太好。
执行方式二:
MySQL可能会选择这一种方式,也就是直接扫描主键的聚簇索引。因为聚簇索引都是按id值有序的,所以扫描时可按order by id desc扫描。而且已经知道是limit 0,10,也就是仅仅只要拿到10条数据就行了。所以MySQL在按order by id desc扫描聚簇索引时,就会对每一条数据都采用Using where的方式,使用where category='xx' and sub_category='xx'的条件进行匹配。符合条件的就直接放入结果集里,最多只需要放10条数据就可以返回。
此时MySQL认为,按顺序扫描聚簇索引拿到10条符合where条件的数据,很可能比使用index_category二级索引那个方案更快,因此MySQL于是就采用了扫描聚簇索引的方式。
二.为什么这个SQL以前扫描聚簇索引没有问题,现在突然就有问题了
这个SQL语句以前在线上系统运行一直没什么问题,也就是之前即使采用扫描聚簇索引的方式,该SQL语句也没有运行很慢。为什么会在某一天晚上突然就大量报慢查询,耗时几十秒了呢?
原因如下:
where category='x' and sub_category='x'这个条件以前通常有返回值,也就是扫描聚簇索引时,通常都能很快找到符合条件的值然后进行返回,所以之前其实性能也没什么问题。但后来可能运营人员,在商品管理的时候加了几种商品分类和子类,而这几种分类和子类的组合其实还没有对应的商品。
恰好那天晚上很多用户使用这种分类和子类去筛选商品,而where category='新分类' and sub_category='新子类'是查不到数据的。所以底层在扫描聚簇索引时,扫来扫去都扫不到符合where条件的结果。一下子就把聚簇索引全部扫了一遍,等于是上亿数据全表扫描了一遍,都没找到where category='新分类' and sub_category='新子类'的数据。也正因如此才导致这个SQL语句频繁的出现几十秒的慢查询,进而导致MySQL连接资源打满,系统崩溃。
(4)案例总结
第一个案例,通过禁用MySQL半连接优化或改写SQL语句结构来避免自动半连接优化。
第二个案例,就得通过force index语法来强制某个SQL使用指定的索引。
3.按聚簇索引扫描小表减少回表次数
(1)业务背景介绍
(2)如何进行优化
(3)案例总结
(1)业务背景介绍
有个商品评论系统的数据量非常大,拥有多达十亿量级的评论数据,所以对这个评论数据库进行了分库分表。基本上分完库和表过后,单表的评论数据在百万级别。每一个商品的所有评论都是放在同一个库的同一张表里,这样确保分页查询一个商品的评论时,从一个库的一张表查询即可。
有一些热门商品,可能销量多达上百万,商品的评论可能多达几十万条。有一些用户可能就喜欢看商品评论,不停对某热门商品的评论进行分页。一页一页翻,有时候还会用上分页跳转功能,直接输入要跳到第几页去。所以这时就会涉及到一个问题,针对一个商品几十万评论的深分页问题。
先来看一个经过简化后的对评论表进行分页查询的SQL语句:
SELECT * FROM comments
WHERE product_id ='xx' and is_good_comment='1'
ORDER BY id desc LIMIT 100000,20
这个SQL语句的意思就是:比如用户选择了查看某个商品的评论,因此必须限定Product_id,同时还选了只看好评,所以is_good_commit也要限定一下。接着用户要看第5001页评论,那么此时limit的offset就是(5001 - 1) * 20。其中20是每一页的数量,起始offset是100000,所以limit后100000,20。
而这个评论表核心的索引就一个,即index_product_id。所以对上述SQL语句,正常情况下,肯定是会使用这个索引的。
步骤一:
通过index_product_id索引,根据product_id ='xx'条件,从表里先筛选出指定商品的评论数据。
步骤二:
按照is_good_comment='1'条件,筛选这个商品评论数据里的所有好评。但问题来了,这个index_product_id索引里并没有is_good_commet字段。所以此时只能进行回表,即对这个商品的每一条评论都要进行一次回表。回到聚簇索引,根据id找到那条数据,取出is_good_comment字段的值,接着对is_good_comment='1'条件进行匹配,筛选出符合条件的数据。如果这个商品的评论有几十万条,那么就要做几十万次回表操作了。虽然每次回表都是根据id在聚簇索引里快速查找,但每条数据都回表。
步骤三:
假设筛选完所有符合条件的数据有十多万条,那么就要按id倒序排序。此时还得基于临时磁盘文件进行倒序排序,又要耗时很久。
步骤四:
排序完毕后,才能基于limit 100000,20获取第5001页的20条数据返回。
整个过程因为有几十万次回表查询 + 有十多万条数据的磁盘文件排序。所以当时发现这条SQL语句基本要执行1秒~2秒。
(2)如何进行优化
第二个案例中会基于商品品类查商品表,尽量避免对聚簇索引进行扫描。因为有可能找不到指定的品类下的商品,出现聚簇索引全表扫描的问题。所以当时第二个案例里,选择强制使用一个联合索引,快速定位到数据。接着根据id在临时磁盘文件排序后找到10条分页数据,只需回表查10次。因此当时对第二个案例而言,因为不涉及到大量回表的问题。所以这么做基本是合适的,性能通常在1s以内。
但是这个案例里,就不是这么回事了,这个案例的优化思路反而和前面的第二个案例反过来了。因为在WHERE product_id ='xx' and is_good_comment='1'这个条件中,product_id和is_good_comment不是一个联合索引。所以这个案例中无论如何都会出现大量的回表操作,这个耗时是极高的。既然按二级索引还是按聚簇索引都要大量回表,还不如直接用聚簇索引。
由于第二个案例中如果指定使用联合索引,则不会出现大量的回表操作,所以第二个案例最好还是指定使用联合索引比较好。
由于这个案例中即便使用二级索引,也可能会出现大量的回表操作,所以还不如直接用聚簇索引,因此通常会采取如下方式改造分页查询语句:
SELECT * from comments a,
(SELECT id FROM comments WHERE product_id ='xx' and is_good_comment='1' ORDER BY id desc LIMIT 100000, 20) b
WHERE a.id=b.id
上面那个SQL语句的执行计划就会彻底改变MySQL的执行方式,即会先执行括号里的子查询,子查询通常会使用PRIMARY聚簇索引。也就是会按照聚簇索引的id值的倒序方向进行扫描,选出符合WHERE product_id ='xx' and is_good_comment='1'的数据。
比如这里筛选出十万多条的数据,并不需要把符合条件的数据都找到。理论上只要有100000+20条符合条件的数据,而且按照id有序的。此时就可以根据limit 100000,20提取出第5001页的这20条数据了,接着会看到执行计划里会针对这个子查询的结果集进行全表扫描。这个子查询的结果集就是一个只有20条数据的临时表,拿到20条数据后,就会接着对20条数据遍历,每一条数据都按照id去聚簇索引里查找完整的数据即可。
可见,出现临时表并非都不好,如果临时表的数量很少还是不影响的。这个案例就是通过少量数据的临时表替换大量数据的回表来提升性能。所以针对这个场景,反而是优化成这种方式来执行分页,会更加合理。分页深度越深扫描数据越多,分页深度越浅扫描数据就越少,然后再对筛选出的20条数据进行20次回表查询即可。当做了这个优化后,执行时间降低到了几百毫秒。
(3)案例总结
对于第二个案例来说:
按顺序扫描聚簇索引可能会因找不到数据导致亿级数据量的全表扫描,所以最好通过force index来强制指定根据联合索引去查找。
对于第三个案例来说:
因为前提是做了分库分表,评论表单表数据一般在一百万左右。首先即使一个商品没有评论,全表扫描也不会像扫描上亿数据表那么慢。其次如果根据product_id的二级索引查找,反而可能出现几十万次回表。所以按二级索引查找反而不适合,而按聚簇索引扫描回表更少更加适合。
简而言之,针对不同的场景,要具体情况具体分析。慢的原因在哪儿,为什么慢,然后再用针对性的方式去优化。
4.避免产生长事务长时间执行
(1)业务背景引入
(2)出现SQL慢查询的服务器原因
(3)出现慢SQL的排查方法总结
(4)使用profilling工具对SQL语句进行分析
(5)SQL语句性能调优
(1)业务背景引入
当时有运维删除了千万级的数据,结果导致了频繁的慢查询。接下来介绍这个案例整个排查、定位以及解决的一个过程。
这个案例一开始是从线上收到大量的慢查询告警,当收到大量慢查询告警后,就去检查慢查询SQL,结果发现是普通SQL。这些SQL语句主要都是针对一个表的,同时也比较简单,而且基本都是单行查询,看起来不应该会慢查询。
所以这时就感觉到特别奇怪的,因为SQL本身完全不应该有慢查询。按道理那种SQL语句,基本上会直接根据索引查找出来,性能是极高的。那么可能慢查询就不是SQL问题,而是MySQL生产服务器的问题。
(2)出现SQL慢查询的服务器原因
事实上在某些特定的情况下:MySQL出现慢查询并不是SQL语句的问题,而是它所在服务器负载太高,从而导致SQL语句执行很慢。
特定情况一:磁盘IO负载特别高
比如现在MySQL服务器的磁盘IO负载特别高,即每秒执行大量高负载的随机IO,但磁盘每秒能执行的随机IO是有限的。结果就导致正常的SQL语句去磁盘执行时,因为磁盘太繁忙而需要等待。从而导致本来很快的一个SQL,要等很久才能执行完毕,这时就可能导致正常SQL语句也会变成慢查询。
特定情况二:网络负载很高
同理,除了磁盘外还有的因素是网络。如果网络负载很高,那就可能会导致一个SQL语句发送到MySQL上,光是等待获取和连接都很久,或MySQL网络负载太高带宽打满了。这样即使一个SQL执行很快,但返回数据网络传输很慢,也是慢查询。
特定情况三:CPU负载很高
另外一个关键的因素就是CPU负载。如果CPU负载过高,也会导致CPU过于繁忙在执行别的任务,而没时间执行SQL语句,此时也有可能会导致SQL语句出现慢查询。
所以出现慢查询不一定就是SQL导致的,如果觉得SQL不应该慢查询,结果某个时间段跑这个SQL就是慢,此时应该排查一下当时MySQL服务器的负载。尤其看看磁盘、网络以及CPU的负载是否正常,如果发现那个时间段MySQL服务器的磁盘、网络或CPU负载特别高,那么可能就是服务器负载导致的。
举个例子,如果某个离线作业瞬间大批量把数据往MySQL里写入时,那么这一瞬间服务器磁盘、网络以及CPU的负载超高。此时一个正常SQL执行下去,短时间内一定会慢查询的。针对类似问题,优化手段更多的是控制导致MySQL负载过高的那些行为。比如写入大量数据时,最好在凌晨低峰期写入,不要影响线上系统运行。
(3)出现慢SQL的排查方法总结
一.检查SQL是否有问题,主要就看执行计划
二.检查MySQL服务器的负载
三.都不行再用profilling工具去细致的分析SQL语句的执行过程和耗时
(4)使用profilling工具对SQL语句进行分析
回到千万级数据删除导致的慢查询的案例中,针对某个表的大量简单的单行数据查询SQL变成慢查询问题,于是先排查了SQL执行计划以及MySQL服务器负载,发现都没有问题。
此时就必须用上一个SQL调优的利器了,也就是profiling工具。这个工具可以对SQL语句的执行耗时进行非常深入和细致的分析。使用这个工具的过程,大致如下所示:
步骤一:
首先要使用命令:set profiling=1,打开profiling。接着MySQL就会自动记录查询语句的profiling信息。
此时如果执行show profiles命令,会列出各种查询语句的profiling信息。这里很关键的一点,就是它会记录下来每个查询语句的query id。所以要针对需要分析的query找对它的query id,假设针对慢查询的那个SQL语句找到了query id。
步骤二:
然后就可以针对单个查询语句,看一下它的profiling具体信息。使用命令:show profile cpu, block io for query xx。这里的xx是数字,此时就可以看到这个SQL语句执行时的profile信息了。除了CPU以及Block IO以外,还可指定去看其他各项负载和耗时。
步骤三:
使用show profile展示出SQL语句执行时的各种耗时等profiling信息,如磁盘IO耗时、CPU等待耗时、发送数据耗时、拷贝数据临时表耗时等。
当仔细检查一下这个SQL语句的profiling信息时,发现一个问题,就是它的Sending Data的耗时是最高的。几乎使用了1s的时间,占据了SQL执行耗时的99%,这就很严重了。毕竟这种简单SQL执行速度真的很快,基本就是10ms级别的。结果跑成了1s,那肯定Sending Data就是罪魁祸首了。
这个Sending Data是在干什么呢?
MySQL的官方释义是:为一个SELECT语句读取和处理数据行,同时发送数据给客户端的过程。简单来说就是为你的SELECT语句把数据读出来,同时发送给客户端。
可是为什么这个过程会这么慢呢?
profiling确实能提供更多的线索了,但是似乎还是没法解决掉问题。但毕竟已捕获到了第一个比较异常的点,就是Sending Data的耗时很高。
步骤四:
接着使用命令:show engine innodb status,查看innodb存储引擎状态。此时发现一个奇怪的指标,就是history list length这个指标。这个指标它的值特别高,达到了上万这个级别。这个history list length与MVCC机制有关,MVCC与Read View机制有关,同时还与数据的undo多版本快照链有关。
当有大量事务执行时,就会构建这种undo多版本快照链条,此时history list length的值就会很高。然后在事务提交后,会有一个多版本快照链条的自动purge清理机制。只要有清理,那么这个history list length值就会降低。
一般来说,这个值是不应该过高的。而展示innodb存储引擎的状态表示,history list length值过高,这表明大量的undo多版本链条数据没被清理。所以推测可能有的事务长时间运行,导致其undo日志不能被purge清理,从而导致history list length的值过高。
至此,大量简单SQL语句变成慢查询,基本可以肯定的两点是:一.一些SQL因为Sending Data环节异常耗时过高。二.同时出现一些长事务长时间运行,导致大量undo日志无法purge清理。
(5)SQL语句性能调优
此时发现有大量的更新语句在活跃,而且是那种长期活跃的超长事务。结果一问系统负责人,发现他在后台跑了一个定时任务,定时清理数据,结果清理时一下子清理了上千万数据。
这个清理是怎么做的呢,就是居然开了一个事务。然后在一个事务里删除上千万数据,导致这个事务一直在运行,所以才看到这个案例出现的一些奇怪现象。
然后这种长事务的运行会导致一个问题:就是删除时只是对数据加了一个删除标记,事实上并没有彻底删除掉。
此时如果有跟长事务同时运行的其他事务,它们在查询时是可能会把那上千万被标记为删除的数据都扫一遍的。因为每次扫描到一批数据,都发现标记为删除了。接着就会再继续往下扫描,所以才导致一些查询语句会那么慢。
那么为什么启动一个事务,在事务里查询,凭什么要去扫描之前那个长事务标记为删除状态的垃圾数据呢?那些数据都被删除了,跟当前事务没关系了,应该可以不用扫描它们的。
这个问题的关键点就在于,那个删除千万级数据的事务是个长事务。当启动新事务查询时,那个删除千万级数据的长事务一直在运行活跃的。而启动一个新事务查询时,会生成一个Read View,这个Read View里包含了当前活跃事务的最大id、最小id和事务id。
然后它有一个判定规则:新事务查询时,会根据ReadView判断哪些数据是可见的,以及可见版本。因为一个数据有一个版本链条,有时可见的只是该数据的一个历史版本。
所以正是因为这个长事务一直在运行,还在删除大量的数据。而且这些数据仅仅是标记为删除,实际还没删除。所以此时新开启的事务,查询时会读到所有被标记为删除的数据。于是就会导致千万级的数据扫描,从而造成慢查询。
因此,永远不要在业务高峰期去运行那种删除大量数据的语句。所以解决方案很简单,直接kill掉那个正在删除千万级数据的长事务。
相关文章:
MySQL原理简介—11.优化案例介绍
大纲 1.禁止或改写SQL避免自动半连接优化 2.指定索引避免按聚簇索引全表扫描大表 3.按聚簇索引扫描小表减少回表次数 4.避免产生长事务长时间执行 1.禁止或改写SQL避免自动半连接优化 (1)业务场景介绍 (2)SQL性能问题分析 (3)SQL性能调优 (1)业务场景介绍 某互联网公司…...
Http 响应协议
HTTP的响应协议 响应数据格式 响应行 响应数据的第一行,包括协议、状态码、描述 响应头 从响应数据格式的第二行开始,也是以key:value的格式 响应体 和响应头之间有一个空行,是响应数据格式的最后一部分,用于存放响应的数据 常见响…...
实现 Browser 客户端下载 XML 文件功能
后端 使用 io.BytesIO 方法 创建一个字节缓冲区在不需要磁盘文件的情况下进行文件操作打包为 zip 压缩包(上图代码)in_memory_zip.seek(0) 数据写入ZIP后文件指针会停留在缓冲区的末尾将文件指针重置回开头make_response() 方法用于创建HTTP响应的函数.g…...
Matlab以一个图像分类例子总结分类学习的使用方法
目录 前言 导入数据 训练学习 导出训练模型 仿真测试 总结 前言 最近在尝试一些基于Simulink的边沿AI部署,通过这个案例总结Matlab 分类学习功能的使用。本案例通过输入3000张28*28的灰度图像,训练分类学习模型。并验证训练好的模型最后部署到MCU。 导入数据 如下图是…...
AI-agent矩阵营销:让品牌传播无处不在
矩阵营销是一种通过多平台联动构建品牌影响力的策略,而 AI-agent 技术让这一策略变得更加智能化。AI社媒引流王凭借其矩阵管理功能,帮助品牌在多个平台上实现深度覆盖与精准传播。 1. 矩阵营销的优势 品牌触达更广:多平台联动可以覆盖不同用…...
HDMI转VGA方案 LT8612UX(HDMI2.0) LT8612SX LT8511EX LT8522EX LT8612EX_e(HDMI1.4)
一、产品概述 LT8612UX是一款高性能的HDMI至HDMI&VGA转换器,由龙迅半导体公司推出。它能够将HDMI2.0数据流转换为HDMI2.0信号和模拟RGB信号,同时输出8通道I2S和SPDIF信号,实现高质量的7.1声道音频。该转换器采用最新的ClearEdge技术&…...
零基础3分钟快速掌握 ——Linux【终端操作】及【常用指令】Ubuntu
1.为啥使用Linux做嵌入式开发 能广泛支持硬件 内核比较高效稳定 原码开放、软件丰富 能够完善网络通信与文件管理机制 优秀的开发工具 2.什么是Ubuntu 是一个以桌面应用为主的Linux的操作系统, 内核是Linux操作系统, 具有Ubuntu特色的可视…...
腾讯云OCR车牌识别实践:从图片上传到车牌识别
在当今智能化和自动化的浪潮中,车牌识别(LPR)技术已经广泛应用于交通管理、智能停车、自动收费等多个场景。腾讯云OCR车牌识别服务凭借其高效、精准的识别能力,为开发者提供了强大的技术支持。本文将介绍如何利用腾讯云OCR车牌识别…...
第二十二课 Vue中的组件切换
Vue中的组件切换 :is 操作符可以用于组件的切换,配合component标签可以实现根据不同的组件名进行组件的切换效果 组件切换实例 1):is与component实现组件切换 <div id"app"><button click"checks()">点击切换组件&l…...
抖音短视频矩阵源代码部署搭建流程
抖音短视频矩阵源代码部署搭建流程 1. 硬件准备 需确保具备一台性能足够的服务器或云主机。这些硬件设施应当拥有充足的计算和存储能力,以便支持抖音短视频矩阵系统的稳定运行。 2. 操作系统安装 在选定的服务器或云主机上安装适合的操作系统是关键步骤之一。推…...
【Linux】线程同步与互斥
文章目录 1. 线程互斥1.1 进程线程间的互斥相关背景概念1.2 互斥量mutex1.3 相关操作1.4 互斥量实现原理1.5 互斥量的封装 2. 线程同步2.1 条件变量2.2 生产者消费者模型2.3 基于BlockingQueue的生产者消费者模型2.4 信号量2.5 基于环形队列的生产消费模型 3. 线程池3.1 日志3.…...
设计模式-原型模式
背景 克隆羊:有一只羊,有各种属性:姓名,年龄……,现在要克隆10只和这只羊一模一样的羊。 传统方法: 定义一个羊类,在客户端调取原型羊的信息,根据信息创建10个属性相同的羊。 问…...
异或操作解决一些问题
前提: 异或操作符合交换律,结合律(因为其根本上来抽象理解,就是查看所有项二进制数相同位是否有奇数个1,对运算结果二进制数而言,没有该位为0,有该位为1,与顺序无关)。 …...
Rust中Tracing 应用指南
欢迎来到这篇全面的Rust跟踪入门指南。Rust 的tracing是一个用于应用程序级别的诊断和调试的库。它提供了一种结构化的、异步感知的方式来记录日志和跟踪事件。与传统的日志记录相比,tracing能够更好地处理复杂的异步系统和分布式系统中的事件跟踪,帮助开…...
Java与C#
Java和C#(C Sharp)是两种流行的面向对象编程语言,它们在很多方面非常相似,因为它们都受到了类似的编程范式和语言设计理念的影响。然而,它们之间也存在一些重要的区别。 平台依赖性: Java:Java是…...
Docker 部署 MongoDB
🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot 🍃 vue-uniapp-template 🌺 仓库主页: GitCode💫 Gitee …...
【C语言】字符串左旋的三种解题方法详细分析
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 💯前言💯题目描述💯方法一:逐字符移动法💯方法二:使用辅助空间法💯方法三:三次反转法💯方法对…...
Android导出Excel
poi org.apache.poi:poi-ooxml:4.x: 不支持Android使用, 不支持原因:Android底层库不支持xml所需的bean类,使用即报错only supported starting with Android O (–min-api 26) org.apache.poi:poi-ooxml:5.2.0: 支持A…...
【学术讲座】视觉计算中的深度学习方法 AIGC图像视频生成模型的推理加速
视觉计算中的深度学习方法 发展历程 backbone 强化学习、LLM等:有监督 && 无监督的结合 目标检测 图像分割 网络结构搜索 搜索方法 1:强化学习 2:强化学习 3:梯度算法 结构选择的作用 1:开放环境感知网络…...
华为OD机试真题---智能驾驶
华为OD机试中的“智能驾驶”题目是一道涉及广度优先搜索(BFS)算法运用的题目。以下是对该题目的详细解析: 一、题目描述 有一辆汽车需要从m * n的地图的左上角(起点)开往地图的右下角(终点)&a…...
视频质量评价SimpleVQA
目录 一、研究意义例子 二、介绍三、文章解读3.1 论文动机3.2论文思路3.3方法3.3.1网络框架3.3.2公式解读3.3.3核心创新3.3.4理解 !!!作者对模型的改进本人算法框体视频抽帧美学特征提取网络:3.3.5实验细节: 四、代码复…...
浏览器插件基于nativeMessaging通信
上一篇文章介绍了基于nativeMessaging启动本地程序,使用官方demo是支持双向通信,demo启动的程序是python写的,现在基于Qt程序进行说明。 消息传递协议 Chrome 会在单独的进程中启动每个原生消息传递主机,并使用标准输入 (stdin)…...
解决 MySQL 8.x 身份验证问题的最佳实践20241126
MySQL 8.x 身份验证问题的深入解析与实践解决方案 🎯 引言 🖋️ MySQL 是全球最受欢迎的开源数据库之一,随着 MySQL 8.x 的发布,引入了更安全的身份验证插件 caching_sha2_password,显著提升了数据库的安全性和性能。…...
对于GC方面,在使用Elasticsearch时要注意什么?
大家好,我是锋哥。今天分享关于【对于GC方面,在使用Elasticsearch时要注意什么?】面试题。希望对大家有帮助; 对于GC方面,在使用Elasticsearch时要注意什么? 1000道 互联网大厂Java工程师 精选面试题-Java…...
各种排序算法
前置知识 排序: 按照递增或者递减的顺序把数据排列好 稳定性: 值相等的元素在排序之后前后顺序是否发生了改变 内部排序: 数据放在内存上 外部排序: 数据放在磁盘上 内部排序 基于比较的排序 几大排序算法 1. 堆排序 特点: 思想: 1. 创建大根堆,把所有元素放在大根堆里…...
前端-Git
一.基本概念 Git版本控制系统时一个分布式系统,是用来保存工程源代码历史状态的命令行工具 简单来说Git的作用就是版本管理工具。 Git的应用场景:多人开发管理代码;异地开发,版本管理,版本回滚。 Git 的三个区域&a…...
用nextjs开发时遇到的问题
这几天已经基本把node后端的接口全部写完了,在前端开发时考虑时博客视频类型,考虑了ssr,于是选用了nextJs,用的是nextUi,tailwincss,目前碰到两个比较难受的事情。 1.nextUI个别组件无法在服务器段渲染 目前简单的解决方法&…...
Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64
yum install 报错: Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64 CentOS7的SCL源在2024年6月30日停止维护了。 当scl源里面默认使用了centos官方的地址,无法连接,需要替换为阿里云。 cd /etc/yum.repos.d/ 找到 CentOS-SCLo-scl.repo 和…...
HCIA笔记3--TCP-UDP-交换机工作原理
1. tcp协议 可靠的连接 1.1 报文格式 1.2 三次握手 1.3 四次挥手 为什么TIME_WAIT需要2MSL的等待时间? (a) 为了实现可靠的关闭 (b)为了让过期的报文在网络上消失 对于(a), 假设host发给server的last ack丢了。 ser…...
RabbitMQ原理架构解析:消息传递的核心机制
文章目录 一、RabbitMQ简介1.1、概述1.2、特性 二、RabbitMQ原理架构三、RabbitMQ应用场景3.1、简单模式3.2、工作模式3.3、发布订阅3.4、路由模式3.5 主题订阅模式 四、同类中间件对比五、RabbitMQ部署5.1、单机部署5.2、集群部署(镜像模式)5.3、K8s部署…...
C语言指针作业
//8-29 第八章作业 //3.输入10个整数,将其中最小的数第一个数对换,把最大的数与最后一个数对换 //写3个函数:①输人10个数;②进行处理;③输出10个数。 //①输人10个数, //方法一:数组索引 void main() {int arr[10];int *p arr;//printf("请输入…...
区块链:比特币-Binance
写在前面:相对于Tran和BNB而言,Binance不支持智能合约;大约每 10分钟 生成一个新区块 一、认识Binance 官方网站:Blockchain Explorer - Bitcoin Tracker & More | Blockchain.com 官方文档:Authentication – I…...
数据工程流程
** 数据工程流程图** #mermaid-svg-ArT55xCISSfZImy3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ArT55xCISSfZImy3 .error-icon{fill:#552222;}#mermaid-svg-ArT55xCISSfZImy3 .error-text{fill:#552222;stroke…...
使用Python和Pybind11调用C++程序(CMake编译)
目录 一、前言二、安装 pybind11三、编写C示例代码四、结合Pybind11和CMake编译C工程五、Python调用动态库六、参考 一、前言 跨语言调用能对不同计算机语言进行互补,本博客主要介绍如何实现Python调用C语言编写的函数。 实验环境: Linux gnuPython3.10…...
基于springboot的县市级土地使用监控系统的设计与实现
文末获取本系统(程序源码数据库调试部署开发环境)文末可获取,系统界面在最后面。 摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的…...
Hot100 - 最大子数组和
Hot100 - 最大子数组和 最佳思路:动态规划 时间复杂度:O(n) 代码: class Solution {public int maxSubArray(int[] nums) {int sum 0;int ans Integer.MIN_VALUE;for (int i 0; i < nums.length; i) {// 如果 sum > 0,…...
Matlab 深度学习 PINN测试与学习
PINN 与传统神经网络的区别 与传统神经网络的不同之处在于,PINN 能够以微分方程形式纳入有关问题的先验专业知识。这些附加信息使 PINN 能够在给定的测量数据之外作出更准确的预测。此外,额外的物理知识还能在存在含噪测量数据的情况下对预测解进行正则…...
[STM32]从零开始的STM32 FreeRTOS移植教程
一、前言 如果能看到这个教程的话,说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗?是的,我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS,再到最…...
软件团队的共担责任
问责制被认为是个人与其社会系统之间的纽带,它创造了一种将个人与其行为和绩效联系起来的身份关系。在入门系列的第一篇文章《超越工具和流程:成功软件开发团队的策略》中,我们介绍了问责制的概念,并提出了以下定义: …...
代码美学:MATLAB制作渐变色
输入颜色个数n,颜色类型: n 2; % 输入颜色个数 colors {[1, 0, 0], [0, 0, 1]}; createGradientHeatmap(n, colors); 调用函数: function createGradientHeatmap(n, colors)% 输入检查if length(colors) ~ nerror(输入的颜色数量与n不一…...
Perforce SAST专家详解:自动驾驶汽车的安全与技术挑战,Klocwork、Helix QAC等静态代码分析成必备合规性工具
自动驾驶汽车安全吗?现代汽车的软件包含1亿多行代码,支持许多不同的功能,如巡航控制、速度辅助和泊车摄像头。而且,这些嵌入式系统中的代码只会越来越复杂。 随着未来汽车的互联程度越来越高,这一趋势还将继续。汽车越…...
大模型中常见的微调方法有哪些?
我整理了1000道算法面试题: 获取 这里说的微调主要是指参数微调,参数微调的方法主要有以下几种: - Adapter 在预训练模型每一层(或某些层)中添加Adapter模块(如上图左侧结构所示),微调时冻结预训练模型主体,由Ada…...
MATLAB支持的距离度量
距离度量是用于量化两个点或样本之间差异的一种方法。在不同的领域和应用场景中,距离度量的选择可能会有所不同。 欧几里得距离(Euclidean Distance):这是最直观的距离定义,适用于n维空间中的两点。对于二维空间中的点…...
c++编程玩转物联网:使用芯片控制8个LED实现流水灯技术分享
在嵌入式系统中,有限的GPIO引脚往往限制了硬件扩展能力。74HC595N芯片是一种常用的移位寄存器,通过串行输入和并行输出扩展GPIO数量。本项目利用树莓派Pico开发板与74HC595N芯片,驱动8个LED实现流水灯效果。本文详细解析项目硬件连接、代码实…...
LSA详情与特殊区域
LSA是构成LSDB的重要原材料,在OSPF中发挥很大作用。 报文 通用头部 LS age:LSA寿命,0-3600s Options:可选项 LS type:LSA类型,三要素之一 Link State ID:LSAID 三要素之一 Advertising Ro…...
Leecode刷题C语言之交替组①
执行结果:通过 执行用时和内存消耗如下: 代码如下: int numberOfAlternatingGroups(int* colors, int colorsSize) {int res 0;for (size_t i 0; i < colorsSize; i) {if (colors[i] ! colors[(i - 1 colorsSize) % colorsSize] && col…...
深入解析 Django 中数据删除的最佳实践:以动态管理镜像版本为例
文章目录 引言场景与模型设计场景描述 删除操作详解1. 删除单个 Tag2. 批量删除 Tags3. 删除前确认4. 日志记录 高阶优化与问题分析1. 外键约束与误删保护2. 并发删除的冲突处理3. 使用软删除 结合 Django Admin 的实现总结与实践思考 引言 在现代应用开发中,服务和…...
4457数字示波器 2Gpts的深度存储
4457数字示波器 2Gpts的深度存储 256级灰度等级及四种波形色彩显示 4457M系列数字示波器,带宽从1GHz到4GHz,采样率10GSa/s、20GSa/s,垂直分辨率12bit,存储深度1Gpts,最快波形捕获率70万个波形/秒,独创的…...
【笔记】轻型民用无人驾驶航空器安全操控
《轻型民用无人驾驶航空器安全操控》 理论考试培训材料 法规部分 【民用无人驾驶航空器的分类】 1、如何定义微型、轻型无人驾驶航空器? 微型无人驾驶航空器,是指空机重量小于0.25千克,最大平飞速度不超过40千米/小时,无线电发…...
【leetcode】动态规划
31. 873. 最长的斐波那契子序列的长度 题目: 如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的: n > 3对于所有 i 2 < n,都有 X_i X_{i1} X_{i2} 给定一个严格递增的正整数数组形成序列 arr ࿰…...