数据库连接池性能优化实战

发布于:2025-08-01 ⋅ 阅读:(13) ⋅ 点赞:(0)

 背景

我们公司正在处于某个项目的维护阶段,领导对资源告警比较重视,服务器资源告警的就不说了,运维同学每隔一小时都会检测线上环境的应用服务信息,例如:网关日志响应时间告警/nginx日志接口响应时间告警/日志关键字异常日志告警等,每周都需要对告警信息的排查分析~

都是通过邮件发送给干系人~

 例如,这个是邮件信息截图~

 排查思路

日志--【检索】->traceId--【skywaling】->完整的调用链信息

1.从日志中查到TraceId。

2.通过TraceId获取调用链

通过traceId在skywalking中的可视化界面上可以看出获取数据库链接就花费了7百多毫秒

大家无需纠结接口花费了6s多,而本次却在重点说获取链接池的情况。其他的可以从代码或者sql进行入手优化~~只是获取数据库链接耗时较多,所以着重进行了分析。

3.查看该应用的数据库链接池配置

    druid:
      # 初始化时建立物理连接的个数
      initial-size: 20
      # 最大连接池数量
      max-active: 100
      # 最小连接池数量
      min-idle: 20
      # 获取连接时最大等待时间
      max-wait: 60000
      # 是否缓存preparedStatement,也就是PSCache。
      pool-prepared-statements: false
      # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
      max-pool-prepared-statement-per-connection-size: -1
      # 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
      validation-query: SELECT 'x'
      # 单位:秒,检测连接是否有效的超时时间。
      validation-query-timeout: 1
      # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-borrow: false
      # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-return: false
      # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
      time-between-eviction-runs-millis: 6000
      # 连接保持空闲而不被驱逐的最小时间
      min-evictable-idle-time-millis: 1200000
      max-evictable-idle-time-millis: 1800000
      # 监控页面相关配置
      stat-view-servlet:
        url-pattern: /druid/*
        allow: ""
        enabled: true

4.获取该应用数据库链接池的监控(需要对应的时间段)

从图中可以看出PoolingCount在这个时刻突然下降,突然下降可能表示有突发流量

详细解释(poolingCount )

在数据库连接池监控中,poolingCount 是一个重要的监控指标,它表示当前连接池中空闲的、可用的数据库连接数量

含义

  • poolingCount 表示连接池中当前处于空闲状态、可以被应用程序直接获取使用的连接数量

  • 这些连接已经创建并与数据库建立了连接,但暂时没有被应用程序使用

  • 它是连接池中"准备好的"连接的数量

相关指标对比

在常见的连接池实现中,通常会有以下相关指标:

指标名称 含义
poolingCount 当前空闲连接数
activeCount 当前活跃连接数(正在被使用的连接)
maxActive 连接池最大允许的连接数
minIdle 连接池保持的最小空闲连接数
maxIdle 连接池允许的最大空闲连接数

监控意义

  1. 性能调优依据

    • 如果 poolingCount 经常为0,可能表明连接池配置过小

    • 如果 poolingCount 长期很高,可能表明配置过大

  2. 问题诊断

    • poolingCount 突然下降可能表示有突发流量

    • poolingCount 长期低位可能表示连接泄漏

  3. 容量规划

    • 结合 activeCount 和 poolingCount 可以评估连接池使用情况

    • 帮助确定合适的 maxActive 和 minIdle 参数

✅ 图表汇总分析(监控时间:近6小时)

1. IdleConnection(空闲连接)

  • 大部分时间维持在 20 左右,直到接近 14:00 时突然拉升。

  • 说明min-idle: 20 已经生效,Druid 正在努力维持至少 20 个空闲连接。


2. PoolingCount(连接池中的连接总数)

  • 长期稳定在 5~10 左右,你说的问题验证了。

  • 但注意这个指标来源标注为 TotalConnection(...),它并非严格意义的“空闲连接数”,可能是连接池当前存在的连接总数(和 Idle + Active 的关系要确认 exporter 定义)。


3. ActiveConnection(活跃连接数)

  • 绝大多数时间活跃连接在 0~2 之间

  • 即使接近 14:00 的高峰期,也只有少量连接(<10)在活跃使用;

  • 说明系统当前数据库并发负载是低的


4. ThreadsAwaitingConnection(等待获取连接的线程数)

  • 全程为 0;

  • ✅ 说明连接池没有连接耗尽问题,应用线程从不需等待连接。


5. MaxConnection(连接池历史最大并发连接数)

  • 常态是 4,只有在快到 14:00 时突增至 200;

  • 猜测是某个服务(或某次压测)在那个时段拉高了连接数。


✅ 结论

项目 结论
min-idle 是否生效? ✅ 是的,空闲连接维持在 20
是否存在连接不足 / 堵塞? ❌ 没有。activeCountwaitThreadCount 都非常低
是否需要增加 min-idle? 目前不需要,现有配置已满足需求
是否要扩大连接池? ❌ 不需要,max-active: 100 远超实际并发需求

当前链接数在22左右,超过了预热的的链接数20,所以需要与mysql中建立链接。

思考:

但是为什么与mysql建立链接这么长时间?

1.mysql的监控

Threads Cached = 1 意味着什么?

这表示:

  • 当前 MySQL 有 1 个空闲线程 在等待下一个连接复用;

  • 如果有新连接请求到来,MySQL 可以立刻用这 1 个线程处理,而 无需调用 pthread_create() 创建新线程

重点观察图表分析

1. 线程缓存 (MySQL Thread Cache)

  • Threads Created 几乎为 0,说明线程缓存有效;

  • 但 Threads Cached 明显有下降段(21:45 前后出现断崖);

  •  表示短时间内大量连接请求导致缓存线程被用完,需新建线程。

这会造成连接响应变慢,尤其是连接池新建连接时要与数据库创建新线程,代价高。


2. 临时表 (MySQL Temporary Objects)

  • 每秒创建临时表数在 4~5 个,且偶尔触发 Created Tmp Disk Tables(磁盘临时表);

  • 说明存在大量使用 GROUP BYORDER BY、子查询、联合查询等临时表消耗。

 磁盘临时表严重影响 SQL 响应速度,从而增加连接持有时间。


3. 排序 (MySQL Sorts)

  • Sort Rows 高峰时可达 30~40;

  • Sort Scan 也非 0,说明未命中索引的排序操作存在。

 排序越多、越复杂,单个 SQL 执行越慢 → 应用端连接持有时间越长 → 导致连接池空闲不足。


4. Select Types 分布

  • Select Scan 稳定在高位,说明存在大量全表扫描;

  • Select Range 较低,表示索引未能充分利用。


5. 慢查询 (MySQL Slow Queries)

  • 21:45 左右出现明显慢 SQL 峰值,最高达 1.45/s;

  • 时间点与线程缓存耗尽、临时表升高高度吻合!


综合分析:导致连接获取慢的根因

MySQL 层 临时表多 / 排序多 / 索引未命中 SQL 响应时间延长,连接被长时间占用
MySQL 层 Thread Cache 命中率下降 新建线程成本高,拖慢新连接初始化
应用层 连接池配置不足 / minIdle 过低 无法预热足够连接,应对突发请求
应用层 SQL 过慢 + 连接未及时释放 导致连接池回收和复用效率变差

 优化建议

A. MySQL 层建议

慢查询排查 开启 slow_query_log,排查 21:45 的慢 SQL
索引优化 优化 SELECT SCAN 多的表结构,添加合适联合索引
避免临时表 检查是否有复杂嵌套子查询 / group by 未配 index
调大线程缓存 thread_cache_size = 128
临时表优化 调整 tmp_table_size 和 max_heap_table_size 至 128M 起步
排序优化 优化 ORDER BY 相关 SQL,能使用索引就用索引

B. 应用层建议(Druid)

spring:
  datasource:
    druid:
      initial-size: 30
      min-idle: 30
      max-active: 100
      max-wait: 30000
      validation-query: SELECT 1
      test-on-borrow: true
      test-while-idle: true
      time-between-eviction-runs-millis: 5000
      remove-abandoned: true
      remove-abandoned-timeout: 180
      log-abandoned: true

重点说明:

  • initial-sizemin-idle: 提高初始连接和空闲连接数,快速预热;

  • test-on-borrow: 确保连接可用,避免使用“死连接”;

  • remove-abandoned: 回收长时间未关闭连接,防止泄露;

  • max-wait: 缩短应用等待连接时间,提高失败可控性;

总结一句话:

MySQL 当前存在执行慢 / 缓存线程耗尽 / 排序临时表密集的问题,间接导致应用端连接获取慢,连接池设置不当进一步放大了问题

当前mysql数据库设置:

SHOW VARIABLES LIKE 'thread_cache_size';

68 

将thread_cache_size 调整到128后,后续再看结果


网站公告

今日签到

点亮在社区的每一天
去签到