线上数据库连接数爆满导致堵塞

背景

集团架构组封装各中间件组件starter,为了统一管理需要改造项目架构组依赖为主POM,升级上线一周后线上晚上22:10分左右开始大量服务告警,提示连不上数据库。并且每周出现两三次数据库连接数爆满情况。

升级前:hikaricp +mariadb-java-client:2.5.6

升级后:druid +mariadb-java-client:2.6.0

调查问题

开始以为数据库挂了,我们直接call dba 去看数据库,dba说数据库正常,没有任何异常。通过监控可以看到数据库的连接数被打满,然后让dba 查下是不是死锁堵住线程。发现线上有ALTER RENAME sql执行阻塞数据库,导致连接数打满。

1
2
3
4
ALTER TABLE xxxx RENAME TO xxx

-- 手动开启事务
statement/sql/set_option set autocommit=1, session_track_schema=1, sql_mode = concat(@sql_mode,',STRICTTRANS_TABLE')

详细问题去查发现一个sql 查询手动开启事务,开启之后应用并没有提交事务(日志显示这个操作timeout),导致在ALTER RENAME时候在等待MDL 读锁释放,需要加 MDL 写锁,导致ALTER RENAME sql之后的sql 全部都被阻塞住,把数据库的连接池直接打满,导致全部应用服务不可用。

我们查询业务代码并未发现手动开启事务的处理,猜测和升级druid +mariadb-java-client有关系,回滚数据库依赖,到现在并未出现数据库连接数爆满情况。

验证

这里的实验环境是 MySQL 5.6

session A session B session C session D
begin
select * from t
select * from t
ALTER TABLE t_1000 RENAME TO t
select * from t

我们可以看到 session A 先启动,这时候会对表 t 加一个 MDL 读锁。由于 session B 需要的也是 MDL 读锁,因此可以正常执行。

之后 session C 会被 blocked,是因为 session A 的 MDL 读锁还没有释放,而 session C 需要 MDL 写锁,因此只能被阻塞。

如果只有 session C 自己被阻塞还没什么关系,但是之后所有要在表 t 上新申请 MDL 读锁的请求也会被 session C 阻塞。所有对表的增删改查操作都需要先申请 MDL 读锁,就都被锁住,等于这个表现在完全不可读写了。

如果t个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新session 再请求的话,这个库的线程很快就会爆满.