广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

新手必看:查询优化慢查询治理 | 11分钟学会

我见过无数人因为慢查询死磕到凌晨,结果发现是索引没建对。一次凌晨三点的线上故障,就是因为一个简单的字段没有加索引,导致全表扫描,CPU直接飙到100%。慢查询治理不是加索引这么简单,得从查询结构、执行计划、数据分布、连接方式等多个维度去看。别想着一次性搞定所有问题,得一个一个捞出来。比如,我见过有人用LEFT JOIN代替INNER JOIN,结果因为左表数

新手必看:查询优化慢查询治理 | 11分钟学会
配图来源于网络和AI生成,仅供参考。
我见过无数人因为慢查询死磕到凌晨,结果发现是索引没建对。一次凌晨三点的线上故障,就是因为一个简单的字段没有加索引,导致全表扫描,CPU直接飙到100%。慢查询治理不是加索引这么简单,得从查询结构、执行计划、数据分布、连接方式等多个维度去看。别想着一次性搞定所有问题,得一个一个捞出来。比如,我见过有人用LEFT JOIN代替INNER JOIN,结果因为左表数据量大,导致性能严重下降。也有人在WHERE条件里用了函数操作,结果索引失效,查询时间直接翻倍。慢查询不是你写得慢,而是系统在执行的时候慢,要从系统视角分析。别光看查询语句,得看它在执行计划里走的是什么路径。

如果你是在用MySQL,EXPLAIN是你的第一个武器。不过很多人用它的时候只看type字段,以为type是index就万事大吉了,其实它还看possible_keys、key、key_len这些。我之前在某个生产库发现,虽然某个字段有索引,但因为使用了函数,导致possible_keys为空,根本没用上。这时候就得改写SQL,把函数移到右边。比如,把SELECT FROM users WHERE YEAR(create_time) = 2023改成SELECT FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'。这种改写能确保索引生效,同时也避免了MySQL对函数处理的低效。有些时候,优化器会因为某些逻辑导致索引失效,这时候得用FORCE INDEX或者用子查询来绕过。

慢查询治理不只是优化单条语句,还要看整个数据库的负载情况。比如,某个表频繁被全表扫描,那可能是该表的数据量特别大,而且没有合适索引。这时候就得看这个表的热点字段。或者某个查询在高峰期执行时间长,但平时很快,那可能是数据分布不均,比如某些分区的数据量特别多,导致扫描时间过长。这种场景下,可以用EXPLAIN来分析执行计划,结合SHOW PROFILE查看实际执行时间,再结合慢查询日志找出具体问题。有时候,索引虽然存在,但优化器没选对,这个时候可能需要手动指定使用哪个索引。

如果你在用PostgreSQL,可以看看pg_stat_statements这个扩展,它能帮你统计所有查询的执行时间和次数。我之前用这个模块发现,一个小查询被执行了上万次,但每次耗时都很长,后来才发现是由于没有使用合适的索引,或者查询条件写得有问题。用这个模块可以快速定位到最耗资源的查询,避免你盲目地优化。同样,在MySQL里,可以通过slow query log来抓取慢查询,但默认的阈值可能不太合理,得自己配置long_query_time,比如设成1秒。同时,开启log_queries_not_using_indexes可以抓到那些没有用索引的查询,这对排查索引失效问题非常有用。

有些时候,慢查询的根源并不是SQL本身,而是数据库的配置。比如,MySQL的innodb_buffer_pool_size如果太小,会导致频繁IO,影响性能。或者,连接池配置不合理,导致频繁建立连接,增加延迟。我之前见到一个项目,数据库连接池最大连接数设置成50,结果在高并发时阻塞严重,一下子卡死。这种情况下,需要根据系统负载调整连接池大小,或者检查是否有高并发的查询被阻塞。还有,数据库的缓存配置、查询缓存、预读策略这些参数,也会影响查询性能。比如,把query_cache_type关掉,可能会减少不必要的缓存开销,反而提升性能。

慢查询治理还涉及到数据库的分库分表策略。如果你的数据量太大,单表查询性能差,那可能需要横向拆分。比如,按时间分表,或者按业务分表。但分库分表之后,查询的复杂度会提升,这时候需要保证查询条件能支持路由到正确分片。否则,查询会变成跨分片的全表扫描,反而更慢。我之前见过一个项目,数据量达到几十亿,结果不敢分库分表,导致每次查询都得扫全表,速度慢得离谱。分库分表的关键在于合理设计分片键,以及实现分片后的查询优化。比如,使用一致性哈希分片,或者按时间范围分片,能有效降低查询复杂度。

在慢查询治理中,有时候问题出在业务逻辑层面。比如,某些业务会频繁更新热点数据,导致索引碎片化严重。这种情况下,索引失效的概率非常高,查询性能也会变差。我见过有人在处理订单状态更新时,直接更新整个订单表,导致索引无法维护,查询变慢。这时候应该考虑使用状态机或者在中间层做缓存,减少对数据库的直接操作。或者,使用批量更新代替单条更新,提升效率。此外,某些业务逻辑会把查询条件写得特别复杂,比如多个条件拼接,或者使用OR连接,这时候优化器可能无法有效选择索引路径。这种情况下,可以考虑使用临时表或者拆分查询,让优化器有更多选择。

慢查询治理还涉及到数据库引擎的选择。比如,MySQL的InnoDB和MyISAM在处理慢查询时的表现完全不同。InnoDB支持事务和行级锁,适合写多读少的场景,但有时候会因为锁等待导致查询变慢。而MyISAM虽然读性能好,但写性能差,而且不支持事务,容易出问题。我之前在处理一个报表查询时,用了MyISAM,结果因为锁冲突,导致查询阻塞。后来换成InnoDB后,虽然写操作变慢,但读取性能反而提升。所以,引擎选择不是万能的,得根据业务场景来调整。比如,写多读少的业务用InnoDB,读多写少的业务用MyISAM,或者使用只读副本。

还有一些实用的工具能帮你解决慢查询问题。比如,使用pt-query-digest分析慢查询日志,可以快速找出哪些查询最耗资源。我之前用它发现,某个INSERT语句执行时间特别长,后来才发现因为事务太大,导致日志过大,事务提交变慢。这时候可以把事务拆分成多个小事务,或者调整innodb_log_file_size参数,提升日志写入速度。另外,在MySQL中,可以用SHOW ENGINE INNODB STATUS查看事务状态,或者用SHOW PROCESSLIST查看当前执行的查询。如果某个查询卡在某个阶段,比如Sorting,那可能需要调整sort_buffer_size,或者优化查询结构,避免不必要的排序。

慢查询治理中也要注意数据库的索引策略。比如,有时候一个字段有多个索引,但优化器选择了最不合适的那个。这时候可以通过FORCE INDEX来指定使用哪个索引,但要小心,如果索引不存在,或者数据分布不均,反而会导致性能下降。我之前遇到一个情况,某个查询用到了两个索引,但优化器选了字段长度更长的索引,导致查询效率降低。后来通过改写SQL,让查询条件更明确,优化器就自动选对了索引。索引的建立和维护也要注意成本,比如一个复合索引如果字段使用率低,反而会增加维护开销,得评估清楚。

在数据量特别大的情况下,可以使用分区表来提升查询效率。比如,按时间分区,或者按业务分区,能减少扫描的数据量。我之前处理一个用户行为日志表,数据量达到TB级别,每次查询都要扫全表,后来按日期分区,每次查询只扫描对应分区,时间直接减半。但分区也不是万能的,比如如果查询条件里用了非分区字段,那分区表也没用。分区表的维护成本也很高,比如要合并或拆分分区,得考虑清楚。另外,某些场景下,例如OLAP查询,分区表反而会引入额外的复杂度,这时候可能更适合用列式存储或者数据仓库方案。

慢查询的优化也得看具体场景。比如,一个报表查询可能需要读取大量数据,这时候优化不是靠单条查询的优化,而是靠数据预处理。我之前优化一个库存统计报表,发现每次都需要从多个表JOIN,导致性能很差。后来把数据预处理到一个中间表,用定时任务每天更新一次,结果查询时间从半小时降到几秒。这种情况下,预处理比数据库优化更有效。但预处理也有代价,比如需要额外的存储空间和维护成本。得权衡清楚,到底该在数据库层面优化,还是在应用层做数据处理。

还有些时候,查询语句的结构会直接影响性能。比如,避免使用SELECT ,只查询需要的字段,减少网络传输和内存占用。我见过一个系统,每次查询都带,导致返回的数据量特别大,尤其是在数据量大的时候,网络延迟变得很严重。这时候可以改写查询,把字段列出来,或者使用字段别名,让优化器更明确地选择索引。此外,避免在WHERE条件里使用OR,除非两边的字段都有索引,否则优化器可能无法有效使用索引路径。这时候可以考虑用UNION来替代OR,让优化器能分别走索引路径。

如果数据库是只读的,那可以用缓存来减轻压力。比如,使用Redis或者Memcached缓存热点数据,减少直接查询数据库的次数。我之前用Redis缓存用户基本信息,结果查询时间从200ms降到5ms。但缓存也有局限,比如缓存穿透、缓存击穿、缓存雪崩的问题,得设计好过期策略和布隆过滤器。另外,缓存的数据需要和数据库保持一致,所以得考虑数据更新的同步问题。缓存虽然能提升性能,但不能替代数据库,只能作为辅助手段。

在分布式数据库场景下,比如MySQL集群或者TiDB,慢查询治理就更要复杂。因为数据分布在多个节点,查询可能需要跨节点执行,这时候得看分布式查询的执行方式。我之前在处理TiDB的查询时,发现因为某些查询条件无法路由到正确节点,导致全量扫描。后来调整了查询条件,让分片键能被有效利用,查询效率大大提升。在分布式场景下,还要注意网络延迟,避免频繁跨节点查询,尽量把查询路由到本地节点。

最后说一个我踩过的坑,就是在一个分布式系统里使用了分库分表,但查询条件里包含了分片键以外的字段,导致查询变成了全表扫描。后来发现,分片键是用户ID,但查询条件里用了订单号,这时候数据库不知道该去哪个分片,只能走全库扫描。这时候应该把订单号作为查询条件,而同时确保分片键能被有效利用,或者在中间层做路由。总之,慢查询治理是一个系统工程,要从查询、索引、数据库配置、工具使用、业务逻辑等多个方面入手,优化才能落地。