CPU作为数据库的核心资源,是日常运维中重点关注的对象。CPU的过度使用将导致应用响应时间(RT)的增加、业务的卡顿,甚至更严重的情况可能导致数据库实例挂起(hang死)、发生高可用(HA)问题,从而严重影响现网任务。因此,正常情况下,CPU的监控应设定安全水位,一旦超出安全水位应及时进行处理,以避免引发不可预期的严重后果。
现网业务中的CPU使用率
随着业务的增长,数据库集群的规格可能已经不能满足业务流量的上涨需求。此时由于流量的不断增长,数据库集群的使用率逐渐提升,CPU使用率也逐渐升高。如果从性能曲线进行观察,必然存在某个指标(如QPS/IOPS)呈上涨趋势,与CPU使用率上涨趋势相似。
非预期内CPU使用率增长
导致CPU使用率非预期增长的情况比较复杂,本文主要针对比较常见的几个现象进行说明,包括慢查询、活跃线程高、内核配置不合理以及系统BUG。
慢查询
正常情况下,CPU使用率的增长,一般是由于SQL语句不合理,产生了慢查询,同时活跃线程堆积导致CPU使用率过高。但是一定要区分清楚,是由于慢查询导致的CPU使用率高,还是由于其他资源满载导致查询变慢导致的CPU使用率高。
您可以在PolarDB控制台的菜单中,查看慢查询情况。如果慢查询中有数据,就需要对这些数据进行分析。如果在慢日志明细页签中,扫描行远远大于返回行数,则说明是慢查询导致的CPU使用率过高。
此处主要针对TP类查询进行分析,需要排除掉count类查询,一些AP类查询的扫描行数确实很大。
TP类查询业务的读写数据量都非常小,如果某个查询的扫描数据量非常大,那么大概率是由于索引缺失导致。例如下述查询语句在慢查询列表中显示扫描数据量为1万+,但返回数据为1条,那么很明显在name列上有索引缺失的情况。
SELECT * FROM table1 WHERE name='testname';那么可以通过下述语句确认在name列上是否有索引。
SHOW index FROM table1;如果
name列上没有索引,可以通过下述语句添加索引列,消除此类大数据量扫描导致的慢查询。ALTER TABLE table1 ADD KEY ix_name (name);如果
name列上有索引,可以通过下述语句查看SQL语句的执行计划,确认是否使用了正确的索引。EXPLAIN SELECT * FROM table1 WHERE name='testname';如果发现
name列有索引,但没有被使用,有可能是出现了统计信息不准确导致生成了错误的执行计划。可以通过下述语句重新生成表上的统计信息用以纠正错误计划。ANALYZE TABLE table1;执行完成后,再通过下述语句确认是否使用了正确的索引。
EXPLAIN SELECT * FROM table1 WHERE name='testname';
FAQ:为什么相同SQL和扫描量下,不同PolarDB集群的CPU使用率差异较大?
CPU打满通常由慢请求导致。虽然两个集群的SQL文本和理论扫描量看似相同,但实际执行情况可能存在差异,建议从以下几个角度排查:
对比两个集群的实际慢日志明细:SQL文本相同并不代表执行效果一致。分别获取两个集群的慢日志,对比相同SQL的实际扫描行数、返回行数及执行时间,确认是否存在差异。若高负载集群的扫描行数明显更大,则可能存在执行计划差异(例如统计信息不准确导致索引选择不同)或数据分布不均等问题。
检查集群架构差异:确认高负载集群与低负载集群的只读节点数量是否一致。若高负载集群缺少只读节点,读请求将无法有效分流,压力全部落在主节点上,从而导致CPU使用率明显偏高。
优化慢请求、减少SQL实际扫描量:建议通过优化相关SQL语句(例如添加合适的索引、改写查询逻辑)来减少SQL执行时的实际扫描行数,而非仅关注SQL文本的一致性。只有降低实际扫描量,才能从根本上避免CPU打满。
活跃线程高
活跃线程高一定会带来CPU使用率的增长。抽象来说,MySQL实现中,每一个CPU只能在同一时间内处理一个请求。假设是16C规格的集群,最多只能同时处理16个请求。但是要注意,这里的请求指的是内核层面,而非应用的并发层面。您可以在PolarDB控制台的菜单中,查看会话情况。
如果排除掉慢查询导致的请求无法正常处理,活跃线程堆积一般都是由于现网业务流量增长造成的。通过查看性能曲线,如果整体流量以及请求趋势和活跃线程的堆积趋势一致,那么说明集群资源已经达到上限,此时需要通过对数据库集群增加只读节点或者扩容集群规格来解决。
需要注意的是,在活跃线程达到临界点时,可能在CPU层面开始产生争抢,内核中会产生大量的mutex排他锁,此时性能曲线表现特征为高CPU使用率、高活跃线程、低IO或低QPS。另外一种情况是突然的业务洪峰,建立连接速度非常快,也可能在CPU层面产生大量争抢,从而导致请求堆积。此类问题一般可以通过开启集群的Thread Pool特性进行流控缓解。如果活跃线程有所缓解,同时还要注意应用侧是否已经产生了业务堆积,如果CPU负载较高同时活跃线程依然高居不下,此时则同样要考虑是不是对集群进行扩容操作。
另有一种情况是前端连接风暴导致集群流量瞬间堆积,此时流量属于异常流量,一般出现在流量数据被爬虫拉取数据的场景下。此时可以通过SQL限流的方式进行请求拒绝,具体操作请参见会话管理。
内核配置不合理
自建MySQL的参数是通用场景下的标准配置,对于个别业务可能不太适用,需要进行微调。有些问题在业务上线初期数据量较少的情况下不会触发,但是随着时间的推移,业务数据量增大,在特定条件下有些问题就会暴露出来。
比较常见的问题会出现内存使用争抢。在MySQL体系中,内存主要作为数据缓存使用,这意味着数据需要不断地迭代,最常用是buffer pool和innodb_adaptive_hash_index内存区域。整个数据库系统的缓存区域,是数据交换最为频繁的位置,如果内存不足和内存页争抢,则会出现各种异常的堆积和慢查询。最典型的表现是数据库突然CPU上涨打满,并且出现慢查询。经过排查后发现该问题并非索引缺失,这个时候就有可能是内存系统发生了问题。
例如:在进行truncate table操作时,MySQL要遍历buffer pool将truncate表的数据页全部驱逐。此时大规格的集群中,如果innodb_buffer_pool_instances配置为1并且并发相对较高的情况下,就有可能出现争抢问题。这个问题在业务上线初期就可以发现,一般来说可以将innodb_buffer_pool_instances的取值与CPU核数对齐,将buffer pool进行分桶,就可以规避此类问题。
另外一种场景是innodb_adaptive_hash_index出现争抢,比较明显的表现是执行下述命令时会出现大量的hash0hash.cc等待。
SHOW ENGINE innodb STATUS;在AHI显示段中,会出现明显的数据倾斜。
insert 0, delete mark 0, delete 0
Hash table size 25499819, node heap has 20720 buffer(s)
Hash table size 25499819, node heap has 25111 buffer(s)
Hash table size 25499819, node heap has 23884 buffer(s)
Hash table size 25499819, node heap has 16835 buffer(s)
Hash table size 25499819, node heap has 23132 buffer(s)
Hash table size 25499819, node heap has 189284 buffer(s)
Hash table size 25499819, node heap has 38864 buffer(s)
Hash table size 25499819, node heap has 49094 buffer(s)
5469.32 hash searches/s, 5282.36 non-hash searches/s此类问题可以将innodb_adaptive_hash_index参数关闭,也就是直接弃用AHI特性,已有数据表明在混合读写的场景下AHI也有可能带来负面的性能影响,关闭后对整体业务的影响不是很大。
内存资源异常导致CPU高或主备切换
内存资源异常是导致CPU使用率非预期增长的重要原因之一。与慢查询不同,内存问题不仅会直接引发CPU升高,还可能导致实例OOM宕机或触发高可用(HA)主备切换。如果在排查慢查询、活跃线程等常见原因后仍无法定位问题,建议检查以下内存相关场景。
OOM(内存溢出)导致宕机
当集群存在大量空闲连接或Prepare语句未及时关闭时,内存将持续被占用,最终触发OOM宕机。建议采取以下措施:
业务侧及时回收空闲连接,避免空闲连接长时间占用内存资源。
对Prepare语句在使用后及时Close,并排查业务侧是否存在大量并发发起Prepare语句的情况。
在业务低峰期升级内核小版本,利用新版本的内存优化能力降低内存消耗。
尝试适当调低
innodb_buffer_pool_size参数,观察内存使用率是否改善。
Query Parser内存分配集中导致内存持续上涨或主备切换
若实例中存在长SQL或大型Trigger,Query Parser在解析时会消耗大量内存,导致内存使用率持续上涨甚至触发主备切换。排查此类问题时需注意以下两点:
即使某条特定SQL已经优化或已配置不走写库,实例中的其他长SQL或大型Trigger仍可能持续触发Parser内存暴涨,问题不会因此消失。
DML操作必须在主节点执行,与读写分离配置无关。仅将读请求路由到只读节点并不能规避该问题,只读节点不接受读以外的请求,内存暴涨仍会发生在主节点侧。
建议采取以下措施:
对过长的SQL进行拆分,将复杂逻辑分步执行,降低单次Parser解析的内存消耗。
优化或缩减Trigger的长度和复杂度,避免大型Trigger频繁触发Parser内存分配。
在业务侧控制SQL长度,避免生成超长SQL语句。
若内存使用率持续高位,可考虑升级实例规格,或根据实际情况调整
innodb_buffer_pool_size参数。
大扫描SQL导致内存占用率100%触发主备切换
包含复杂条件的查询(如大范围扫描、COUNT统计等大扫描SQL)在执行时会消耗大量内存,可能导致内存占用率达到100%并触发主备切换(HA)。此类问题的特殊性在于:大扫描SQL不一定会出现在诊断报告中,仅依靠诊断报告无法确认根因。
建议通过分析慢日志来定位消耗内存较多的大扫描SQL,再针对具体SQL进行索引优化或语句改写,降低实际扫描行数,从根本上减少内存占用。
排查内存相关问题时,建议结合内存使用率监控曲线与慢日志时间点进行关联分析,可更快速地定位内存100%的触发根因。常见排查关键词包括:OOM、宕机、主备切换、HA、内存100%、Prepare语句、长SQL、Trigger、Query Parser、内存暴涨。
系统BUG
系统BUG是相对少见的问题,例如早期出现的进程死锁以及因表上统计信息置为0而导致的全表扫描等。随着产品的快速迭代,因系统BUG引发的CPU问题相对较少。然而,在排查此类问题时,涉及很多的内核层面信息,您自行处理可能有一定的难度。建议联系我们以获取技术支持进行问题排查。