当前位置:首页 > 问答 > 正文

数据库性能卡住了?聊聊那些不容易发现的瓶颈和怎么让系统跑得更稳更快

前几天和一个在电商公司做运维的朋友聊天,他愁眉苦脸地说,公司搞了大促,服务器和带宽都加了,但数据库的CPU就是时不时飙到100%,交易页面卡得要命,查了半天慢日志,也没找到特别明显的“元凶”,这让我想起很多团队都会遇到的困境:明明感觉该优化的都优化了,为什么数据库还是像老牛拉车一样慢?问题往往就出在那些不容易被第一时间发现的“隐形瓶颈”上。

第一个不容易发现的瓶颈,其实不在数据库本身,而在应用程序里,这就是我们常说的“N+1查询问题”,想象一个场景:你要在网页上展示一个订单列表,每笔订单下面要显示对应的商品信息,一种常见的写法是,先发一条SQL查询获取10笔订单(这就是1),然后程序循环这10笔订单,为每一笔订单再发一条SQL去查询它的商品详情(这就是N,10条),看起来每条SQL单独执行都很快,但加起来就是11次数据库往返,如果页面一页显示100笔订单呢?那就是101次查询!数据库大部分时间都花在建立连接、解析SQL这些琐碎的事情上了,而不是真正去处理数据,这种问题在监控上可能表现为QPS(每秒查询次数)异常高,但每条查询的耗时却不高,很容易被忽略,解决办法是,让程序员养成“批量思维”,用一条JOIN联表查询或者通过IN语句一次性把数据都取出来。(来源:常见的ORM框架使用不当引发的性能问题讨论)

第二个隐藏的瓶颈是“热点争用”,这听起来有点专业,但举个例子就明白了,比如一个论坛的表,每个新帖子都需要获取一个唯一的ID作为主键,如果这个ID是用数据库的自增字段生成的,那么所有插入新帖子的请求都会去竞争那个“自增计数器”的最新值,在高并发插入的时候,这个计数器就会成为一个热点,所有线程都得排队等着它更新,就像很多人在一个小门口挤着要进去一样,虽然每个插入操作本身很快,但等待的时间会急剧上升,监控上可能会看到数据库的CPU和IO都不高,但系统吞吐量就是上不去,事务延迟很高,解决这种问题,可以考虑使用分布式的ID生成算法(比如雪花算法),让不同的应用服务器自己生成不会重复的ID,从根本上避免去数据库里争抢。(来源:高并发场景下的数据库主键设计实践)

第三个容易被忽视的点是“统计信息的过时”,数据库里有个叫做“查询优化器”的智能家伙,它的工作是决定怎么执行SQL最快,比如是先查A表还是先查B表,用哪个索引,它做决策的依据,就是关于表里数据特征的“统计信息”,比如表有多大、每个字段的值是怎么分布的,如果这个统计信息很久没更新,比如一个表原来只有1万条数据,现在通过数据导入变成了1千万条,但统计信息还显示它是1万条的小表,那么优化器就可能选择一个针对小表有效的索引,结果在大数据量下性能一落千丈,这就好比用一张十年前的老地图在今天导航,肯定会走错路,定期更新数据库的统计信息(通常是自动的,但需要关注是否正常),或者在对表数据做大量增删后手动更新一下,是非常必要的。(来源:数据库查询计划分析与优化器原理相关文档)

怎么让系统跑得更稳更快呢?除了解决上述这些隐藏问题,还有一些通用的“保健”原则。

加缓存是性价比最高的良药,把那些频繁读取但又很少变动的数据,比如商品分类、用户基本信息、热点文章等,放到Redis或Memcached这样的缓存里,这样绝大部分读请求就不用去麻烦数据库了,数据库的压力自然就降下来了,这相当于在数据库前面加了一个高速服务区。

做好读写分离,如果读的压力特别大,可以设置一个主数据库负责写入(写),然后挂多个从数据库负责读取(读),应用程序写数据时找主库,读数据时随机找一个从库,这样就把压力分散开了,这会带来数据同步延迟的问题,需要业务能接受短暂的读写不一致。

监控一定要到位,而且要会看监控,不能只盯着CPU、内存这些大指标,更要关注一些细化的指标,慢查询的数量和具体内容、实时的活跃连接数、锁等待的情况、网络流量等,一旦发现异常,这些细化的指标就是定位问题的“侦探线索”。

数据库优化是一个持续的过程,没有一劳永逸的银弹,很多时候,性能瓶颈就像水管里的锈,不在最显眼的地方,而是藏在拐角处,需要我们既有整体的架构视野,又能沉下心来仔细分析每一个可疑的细节,才能让系统真正地跑得又稳又快。

数据库性能卡住了?聊聊那些不容易发现的瓶颈和怎么让系统跑得更稳更快