首页/后端问题/数据库连接池被打满但 CPU 占用低怎么办?慢查询排查指南
后端问题

数据库连接池被打满但 CPU 占用低怎么办?慢查询排查指南

数据库连接池耗尽但 CPU 占用不高的典型问题排查指南,包括索引失效检测、锁竞争分析、慢查询定位与优化方法。

发布时间:2026年4月6日 06:20阅读量:1

数据库连接池被打满但 CPU 占用低怎么办?慢查询排查指南

问题现象

应用突然变慢或超时,监控显示:

  • 应用日志:Connection is not available, request timed out after 30000ms
  • 数据库连接池:所有连接被占满,等待队列堆积
  • 数据库监控:CPU 占用率仅 20%,负载不高

这种"连接池干涸但 DB 不 busy"的不匹配现象,是典型的慢查询或锁竞争问题。


常见原因

| 原因 | 说明 | |------|------| | 索引失效 | 隐式类型转换、函数操作导致全表扫描 | | 锁竞争 | 长事务持有锁,阻塞其他查询 | | 大查询 | SELECT * 无 limit,返回百万级数据 | | 死锁 | 多个事务循环等待对方释放锁 | | 网络延迟 | 应用与数据库之间网络不稳定 |


排查步骤

步骤1:确认连接池状态

bash
# 查看 HikariCP 连接池状态(如有 actuator)
curl http://localhost:8080/actuator/metrics/jdbc.connections.active
curl http://localhost:8080/actuator/metrics/jdbc.connections.pending

# 或查看应用日志中的连接池监控

步骤2:查看数据库当前连接和正在执行的 SQL

sql
-- MySQL 查看当前连接
SHOW PROCESSLIST;

-- 或更详细的版本
SELECT 
    ID,
    USER,
    HOST,
    DB,
    COMMAND,
    TIME,
    STATE,
    LEFT(INFO, 100) as SQL_PREVIEW
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC;

关键字段说明:

  • TIME:执行时间(秒),重点关注大于 10 秒的
  • STATE:当前状态,如 Sending dataWaiting for table lock
  • INFO:正在执行的 SQL

步骤3:定位慢查询

sql
-- 查看慢查询日志是否开启
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';

-- 临时开启慢查询日志(当前会话)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过1秒记录

-- 直接查看执行中的慢查询
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST 
WHERE TIME > 10 AND COMMAND != 'Sleep';

步骤4:分析具体 SQL 的执行计划

sql
-- 对可疑 SQL 执行 EXPLAIN
EXPLAIN SELECT * FROM orders 
WHERE user_id = 123456 
AND created_at > '2024-01-01';

-- 关注以下字段:
-- type: ALL(全表扫描) | index(索引扫描) | range(范围扫描) | ref(索引引用)
-- rows: 扫描行数,越大越慢
-- Extra: Using filesort(需优化) | Using temporary(需优化) | Using index(好)

步骤5:检查索引使用情况

sql
-- 查看表索引
SHOW INDEX FROM orders;

-- 分析表统计信息
ANALYZE TABLE orders;

-- 查看索引选择性(cardinality 越高越好)
SHOW INDEX FROM orders WHERE Column_name = 'user_id';

步骤6:检查锁等待情况

sql
-- MySQL 8.0+ 查看锁等待
SELECT 
    r.object_schema,
    r.object_name,
    r.thread_id AS waiting_thread,
    r.processlist_info AS waiting_query,
    b.thread_id AS blocking_thread,
    b.processlist_info AS blocking_query
FROM performance_schema.data_lock_waits w
JOIN performance_schema.data_locks r ON w.requesting_lock_id = r.lock_id
JOIN performance_schema.data_locks b ON w.blocking_lock_id = b.lock_id;

-- MySQL 5.7 查看 InnoDB 锁状态
SHOW ENGINE INNODB STATUS;
-- 查看 LATEST DETECTED DEADLOCK 和 TRANSACTIONS 部分

常见原因及解决方法

原因1:隐式类型转换导致索引失效

sql
-- 表结构
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id VARCHAR(32),  -- 字符串类型
    created_at DATETIME,
    INDEX idx_user_id (user_id)
);

-- 错误写法:索引失效!
SELECT * FROM orders WHERE user_id = 123456;  -- 数字类型,触发隐式转换

-- 正确写法:
SELECT * FROM orders WHERE user_id = '123456';  -- 字符串类型,走索引

-- 验证:
EXPLAIN SELECT * FROM orders WHERE user_id = 123456;
-- type: ALL (全表扫描)

EXPLAIN SELECT * FROM orders WHERE user_id = '123456';
-- type: ref (走索引)

原因2:对索引字段使用函数

sql
-- 错误写法:索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01';

-- 正确写法:范围查询走索引
SELECT * FROM orders 
WHERE created_at >= '2024-01-01 00:00:00' 
AND created_at < '2024-01-02 00:00:00';

原因3:大查询无 limit

sql
-- 错误写法:可能返回百万级数据
SELECT * FROM orders WHERE status = 'pending';

-- 正确写法:分页查询
SELECT * FROM orders 
WHERE status = 'pending' 
ORDER BY id 
LIMIT 1000;

-- 或只取需要的字段
SELECT id, user_id, amount FROM orders WHERE status = 'pending';

原因4:长事务持有锁

sql
-- 查看长事务
SELECT 
    trx_id,
    trx_mysql_thread_id,
    trx_state,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) as trx_seconds,
    LEFT(trx_query, 100) as query_preview
FROM INFORMATION_SCHEMA.INNODB_TRX
ORDER BY trx_started;

-- 处理:
-- 1. 优化业务逻辑,缩短事务
-- 2. 将大事务拆分为小批次
-- 3. 非必要不放在事务中(如查询操作)

原因5:缺少合适的索引

sql
-- 分析查询条件,创建复合索引
-- 原查询:
SELECT * FROM orders 
WHERE user_id = '123' 
AND status = 'paid' 
AND created_at > '2024-01-01';

-- 创建复合索引(遵循最左前缀原则)
CREATE INDEX idx_user_status_created 
ON orders(user_id, status, created_at);

-- 验证
EXPLAIN SELECT * FROM orders 
WHERE user_id = '123' 
AND status = 'paid' 
AND created_at > '2024-01-01';
-- type: range, key: idx_user_status_created

预防措施

1. 配置慢查询监控

ini
# my.cnf 配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5  # 超过500ms记录
log_queries_not_using_indexes = 1  # 记录未使用索引的查询

2. 应用层配置查询超时

yaml
# Spring Boot 配置
spring:
  datasource:
    hikari:
      connection-timeout: 30000      # 获取连接超时30秒
      leak-detection-threshold: 60000 # 连接泄漏检测
  jpa:
    properties:
      hibernate:
        connection:
          provider_disables_autocommit: true

3. 代码规范

java
// 1. 所有查询必须加 limit
@Query("SELECT o FROM Order o WHERE o.status = ?1 ORDER BY o.id LIMIT ?2")
List<Order> findByStatus(String status, int limit);

// 2. 大事务拆分
@Transactional
public void processOrders() {
    // 错误:一次性处理所有
    // List<Order> all = orderRepo.findAll();
    
    // 正确:分批处理
    Pageable pageable = PageRequest.of(0, 1000);
    Page<Order> page;
    do {
        page = orderRepo.findByStatus("pending", pageable);
        // 处理...
        pageable = page.nextPageable();
    } while (page.hasNext());
}

// 3. 查询超时设置
@QueryHints(@QueryHint(name = "javax.persistence.query.timeout", value = "5000"))
@Query("SELECT o FROM Order o WHERE ...")
List<Order> findWithTimeout();

验证修复

sql
-- 1. 修复后重新执行 EXPLAIN,确认走了索引
EXPLAIN SELECT * FROM orders WHERE user_id = '123456';

-- 2. 查看慢查询日志是否还有该 SQL
-- tail -f /var/log/mysql/slow.log

-- 3. 监控连接池状态,确认连接数下降

-- 4. 压力测试验证
-- 使用 JMeter 或类似工具模拟并发

总结

| 症状 | 可能原因 | 快速检查 | |------|---------|---------| | 连接池满 + CPU 低 | 慢查询/锁等待 | SHOW PROCESSLIST | | type=ALL | 索引失效 | EXPLAIN | | STATE=Sending data | 大查询/全表扫描 | 检查 rows 字段 | | Waiting for table lock | 锁竞争 | SHOW ENGINE INNODB STATUS |

一句话总结:连接池干涸但 DB CPU 低 = 查询被卡住,重点排查索引失效和长事务。

问题求助

没能解决你的问题?直接问我

如果你遇到任何技术问题无法解决,可以在这里提交求助。我会尽快查看并回复你。

支持作者

如果这篇文章帮到了你,可以支持我

扫码打赏,支持我持续更新原创排障文章。

打赏二维码
数据库连接池慢查询排查指南 | 技爪网