首页 > 数据库 >DBCP一个配置,浪费了MySQL 50%的性能!

DBCP一个配置,浪费了MySQL 50%的性能!

时间:2024-03-26 13:46:31浏览次数:32  
标签:autocommit 数据库 50% dbcp sql MySQL DBCP true 连接

1. 引言

研究背景

数据库性能的重要性

数据库性能优化对于保证应用的响应速度和处理大量数据的能力至关重要。它可以显著减少查询时间,提高事务处理效率,降低硬件成本,并确保系统稳定性与可扩展性。优化后的数据库能够更好地服务于用户需求,增强客户满意度,对企业的长期发展和竞争力具有深远影响。

连接池在数据库性能中的作用

1. 降低连接开销:连接池预先创建并管理一组数据库连接,避免了频繁建立和关闭连接的开销,提高了应用程序的响应速度。

2. 提高资源利用率:通过复用已存在的连接,连接池使得数据库资源(如内存和连接数)得到更高效的利用。

3. 管理连接生命周期:连接池能够监控连接的健康状态,自动剔除失效的连接,并根据需要创建新的连接,确保连接的可用性。

4. 事务管理:连接池可以协助管理数据库事务,保证在同一连接中执行的操作能够满足事务的原子性、一致性、隔离性和持久性(ACID属性)。

5. 配置灵活性:连接池提供多种配置选项,如最小/最大连接数、连接超时时间等,帮助开发人员根据具体应用需求调整资源分配策略。

 

研究问题及目的

在应用压测过程中,发现数据库的TPS不高的情况下, 数据库CPU就很高,而且有个数据库的事务指标跟应用的特点不匹配。因此经过不断的试验、研究,最终发现是数据库连接池的autocommit配置导致的。因此,本篇文章的主要探讨:

通过实验验证autocommit=false的性能影响

通过源码分析解释性能影响的原因

2. 实验设计与方法

注:mysql服务端的autocommit默认值是ON,后续章节若无特殊说明,autocommit指应用侧dbcp的配置

实验环境说明

硬件配置:MySQL 5.7 4C16G

软件版本和配置:spring 4.1.3.RELEASE + mybatis 3.2.7 + mybatis-spring 1.2.2 + dbcp 1.4 + mydql 5.7

数据库连接池配置参数

#jdbc
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://host:port/my_db?connectTimeout=1000&socketTimeout=1000&serverTimezone=Asia/Shanghai
jdbc.mysql.connectionProperties=useUnicode=true;characterEncoding=utf8;rewriteBatchedStatements=true;autoReconnectForPools=true;failOverReadOnly=false;roundRobinLoadBalance=true;allowMultiQueries=true;useLocalSessionState=true

#dbcp
dbcp.initialSize=4
dbcp.maxActive=12
dbcp.maxIdle=12
dbcp.minIdle=4
dbcp.maxWait=6000
dbcp.defaultAutoCommit=true
dbcp.timeBetweenEvictionRunsMillis=60000
dbcp.numTestsPerEvictionRun=16
dbcp.minEvictableIdleTimeMillis=180000
dbcp.testWhileIdle=true
dbcp.testOnBorrow=false
dbcp.testOnReturn=false
dbcp.validationQuery=select 1
dbcp.removeAbandoned=true
dbcp.removeAbandonedTimeout=180
dbcp.logAbandoned=true

实验方法

实验方法:设计一个查询接口,根据主键ID查询一条数据。表中一共12000条数据,查询id的范围为[1,10000]。其中数据库表、sql如下

表结构如下

CREATE TABLE `task` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
  `cluster` varchar(100) NOT NULL DEFAULT '',
  `system` varchar(50) NOT NULL COMMENT '系统',
  `app_name` varchar(50) NOT NULL COMMENT '应用',
  部分字段省略....
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=77 DEFAULT CHARSET=utf8 COMMENT='任务';

sql语句

select field1,field2,...,fieldN from task where id = ${id}

实验分组

在其它变量一致的情况下,开启autocommit与关闭autocommit 分别进行测试。通过压力机对接口进行梯度发压,对比mysql CPU使用率

实验结果

数据汇总

  autocommit=false 数据库CPU autocommit=true 数据库CPU
1000 14.3 10
2000 25.1 13.6
3000 35.8 19.4
4000 47 25.7
5000 58.1 30.8
6000 70.7 35.8
7000 81.4 40.4
8000 92.3 46.1
9000 97(8.5k) 51.2
10000   56.6
11000   62.6
12000   67.2
13000   74.2
14000   80.3
15000   86.4
16000   90.4
17000   93.9
18000   98.8

autocommit=false

数据库性能监控

 


 

应用侧接口性能监控

 


 

autocommit=true

数据库性能监控

 


 

应用侧性能监控

 


 

实验结论

autocommit=true(默认配置)支持的TPS是18K,此时CPU使用率在98%左右,而autocommit=false能支持的TPS是8.5K,此时CPU使用率也在98%左右。

明显看出,autocommit=false的配置,导致数据库性能下降了一倍。

3. 源码分析与讨论

根据上文的实验看出,连接池的autocommit属性对于性能的消耗是巨大的,接下来我们一步一步深究一下其原因。

注:没有特殊说明,流程图、时序图等,都是基于autocommit=false画出的

mybatis执行sql流程图

源码位于 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke

由下图可知,mybatis封装的sql执行步骤,还是离不开原生jdbc的三段式:获取连接、执行sql、关闭连接。不过由于框架的封装,很多细节隐藏到了其它中间件,比如获取连接、关闭连接,底层都是由dbcp处理的。因此autocommit如何作用,我们还要继续深入dbcp的源码。

 


 

 

源码分析autocommit=false如何起作用的

连接的管理,都是由dbcp实现的,而dbcp依赖了commons-pool框架。

获取连接from dbcp

 


 

在获取连接时,执行GenericObjectPool#borrowObject方法,即从连接池中获取一个可用的连接对象(可以是新建,也可以是从队列中获取闲置的),获取连接之后需要激活连接,代码为_factory.activateObject,这里的_factory是org.apache.commons.dbcp.PoolableConnectionFactory,其activateObject方法如下。conn.getAutoCommit()是获取连接的autocommit(默认true),_defaultAutoCommit是连接池的配置项,被项目配置为false。由于二者不一致,需要将连接的autocommit设置为true,此时mysql服务器远端也会被设置为false。

   public void activateObject(Object obj) throws Exception {
        if(obj instanceof DelegatingConnection) {
            ((DelegatingConnection)obj).activate();
        }
        if(obj instanceof Connection) {
            Connection conn = (Connection)obj;
            // autocommit=false 起作用的地方
            if (conn.getAutoCommit() != _defaultAutoCommit) {
                conn.setAutoCommit(_defaultAutoCommit);
            }
            if ((_defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) 
                    && (conn.getTransactionIsolation() != 
                    _defaultTransactionIsolation)) {
                conn.setTransactionIsolation(_defaultTransactionIsolation);
            }
            if ((_defaultReadOnly != null) && 
                    (conn.isReadOnly() != _defaultReadOnly.booleanValue())) {
                conn.setReadOnly(_defaultReadOnly.booleanValue());
            }
            if ((_defaultCatalog != null) &&
                    (!_defaultCatalog.equals(conn.getCatalog()))) {
                conn.setCatalog(_defaultCatalog);
            }
        }
    }

关闭连接 to dbcp(实际上是将连接归还给线程池)

 


 

 

在归还连接时,调用链路为 GenericObjectPool#returnObject > GenericObjectPool#addObjectToPool ,然后执行PoolableConnectionFactory#passivateObject的方法,有两个核心步骤进行数据库连接的配置:

1、如果连接不是自动提交且不是只读的,回滚

2、如果连接不是自动提交的,将其设置为自动提交

 

public void passivateObject(Object obj) throws Exception {
        if(obj instanceof Connection) {
            Connection conn = (Connection)obj;
            // 判断是否需要rollback
            if(!conn.getAutoCommit() && !conn.isReadOnly()) {
                conn.rollback();
            }
            conn.clearWarnings();
            // 如果连接不是autocommit,设置autocommit=true
            if(!conn.getAutoCommit()) {
                conn.setAutoCommit(true);
            }
        }
        if(obj instanceof DelegatingConnection) {
            ((DelegatingConnection)obj).passivate();
        }
    }

为什么autocommit=false会消耗一半的性能?

我们先来看一下,应用程序执行一条sql在mysql general_log的显示。下表是dbcp的autocommit=false,执行一条查询语句时,mysql general_log显示的sql明细

序号 sql sql说明 源码位置 触发执行逻辑的框架
1 SET autocommit=0 将连接autocommit属性设置为false,所有sql手动提交 PoolableConnectionFactory#activateObject line: 704 dbcp
2 select fields from task where id = 1 执行业务sql    
3 commit 提交事务,这个提交是mybatis框架执行的,前提条件是sqlSession不是spring管理的 SqlSessionInterceptor#invoke line:362 mybatis
4 select @@session.transaction_read_only 查询session是否只读 PoolableConnectionFactory#passivateObject line: 684 dbcp
5 rollback 回滚所有事务 PoolableConnectionFactory#passivateObject line: 685 dbcp
6 SET autocommit=1 将连接autocommit属性恢复为true PoolableConnectionFactory#passivateObject line: 689 dbcp

那么autocommit=true时,general_log如何显示呢?如下表,仅有一条业务sql!!!

序号 sql sql说明 源码位置 触发执行逻辑的框架
1 select fields from task where id = 1 执行业务sql    

至此,终于破案了。因为autocommit的频繁开启关闭,会导致以下问题:

1.性能开销:每次改变autocommit的状态都需要执行额外的操作,这会增加CPU的工作负载。 2.事务管理:在autocommit关闭的情况下,MySQL会将后续的操作视为一个事务,直到显式地执行COMMITROLLBACK。频繁切换autocommit模式意味着频繁地开始和结束事务,这可能会导致事务日志的增长和额外的磁盘I/O操作。 3.锁定资源:在事务处理期间,可能会锁定一些资源,直到事务提交或回滚。频繁切换autocommit模式可能会导致锁定时间变长,增加了死锁的可能性,影响并发性能。 4.网络开销:如果更改autocommit状态的操作是在应用程序与数据库服务器之间进行的,那么这也会增加网络通信的开销。

4. 结论与建议

结论

前提条件:在spring+mybatis+dbcp(autocommit=false)+mysql(autocommit默认true)的框架下

1.每次调用SqlSessionTemplate(属于mybatis-spring)的sql方法,应用程序与mysql之间会多出5次网络io,mysql多执行5个sql 2.在极端场景下,mysql 性能下降50%。
一般情况下应用层不需要开启事务的案例:
1、单条select
2、多条select
3、单条insert
4、单条update
5、单条delete
极端场景,就是以上5种案例占数据库所有sql的比例100%,那么你的数据库有50%的CPU是浪费的。

我的应用就属于以上极端场景,因此在调整autocommit后,配置不变的情况下,承担了原来翻倍的业务增长,为公司节省了数据库成本10w元/年

建议

1、dbcp连接池,将配置autocommit设置为true,与数据库保持一致。在需要事务控制的业务逻辑上,使用spring的@Transactional注解,或者使用mybatis原生的SqlSession管理事务等等。

2、其它连接池中间件如C3P0、HikariCP、BoneCP等都支持自定义配置autocommit,也可能存在本文实验的问题。验证方法很简单:将mysql数据库general_log开启,然后找一个接口调用一下,看看是不是有多余的5条sql出现。

 

标签:autocommit,数据库,50%,dbcp,sql,MySQL,DBCP,true,连接
From: https://www.cnblogs.com/Jcloud/p/18096491

相关文章

  • Linux(2)系统基本操作-Mysql数据库原生安装_Mysql常用命令_安装和使用过程常用问题
    二、Linux系统基本操作1、查询centos版本[root@host-10-150-223-171~]#uname-aLinuxhost-10-150-223-1713.10.0-957.el7.x86_64#1SMPThuNov823:39:32UTC2018x86_64x86_64x86_64GNU/Linux[root@host-10-150-223-171~]#cat/etc/redhat-releaseCentO......
  • java计算机毕业设计(附源码)新知书店(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:新知书店,作为一家专注于传播知识和文化的零售场所,承载着促进社会文化发展和满足人们精神需求的重要使命。在数字化时代背景下,实体书店面临着前所未有的挑......
  • Mysql SQL优化
    ​​Mysql查询执行的过程链接一、索引的代价我们虽然可以根据我们的喜好在不同的列上建立索引,但是建立索引是有代价的,所以不要建太多的索引:【1】空间上的代价:每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用16KB的存储空间,一......
  • linux下的mysql的安装方式--yum--二进制
    linux下的mysql的安装方式--yum--二进制1.yum安装yum方式wgethttp://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpmrpm-ivhmysql-community-release-el7-5.noarch.rpmyum-yinstallmysql-community-serversystemctlstartmysqlsystemctlstatusm......
  • HFCG-3500+ 信号调节 LTCC High Pass Filter 3900-16500MHz 0805 陶瓷滤波器 Mini-Cir
    Mini-Circuits是一家专注于射频和微波产品的跨国公司,总部位于美国纽约。Mini-Circuits提供的产品涵盖了信号调理、信号发生、频率合成、混合信号处理等领域,广泛应用于无线通信、雷达、测试测量、航空航天等行业。制造商:   Mini-Circuits   产品种类:   信号调......
  • 数据库的四个特性?MySQL是如何实现的?
    首先MySQL中,数据库的四个特性分为:原子性一致性隔离性持久性也就是我们常说的ACID。那么这四个特性数据库是如何实现的呢?持久性--->redolog:redolog(重做日志):redolog本身是一种日志,记录的是事务提交时对数据页(MySQL在硬盘中存储数据文件的最小单位,由数据行组成)的......
  • 1、融合通信专业术语知识学习VOIP、SIP、350M集群等
    摘自百度:1、VoIP和SIP的概念:VoIP和SIP都是通信领域中的重要概念,它们各自具有独特的功能和应用场景,但也存在一定的联系。VoIP,即VoiceoverInternetProtocol,是一种语音通话技术,它利用互联网协议(IP)进行语音通话与多媒体会议。这种技术将模拟声音信号数字化,并以数据封包的形式在IP......
  • 别让 Docker 毁了你的 MySQL!
    Docker容器技术的火热,越来越多的应用开始被"装箱"。我们这些开发者享受着Docker带来的便捷和灵活,几乎恨不得把所有东西都塞进容器里。于是,连MySQL这样的有状态服务,也开始频频出现在Docker的舞台上。但是,把MySQL装进Docker,真的是个明智的选择吗?是否会给系统稳......
  • Centos7安装mysql8.0
    一、卸载MariaDB#查看版本rpm–qa|grepmariadb#卸载rpm–e--nodeps文件名#查看是否卸载干净rpm–qa|grepmariadb 二、安装mysql1、下载资源包 官网下载https://dev.mysql.com/downloads/mysql/Wget下载1、下载​wgethttps://dev.mysql.com/get/D......
  • MySQL Delete 表数据后,磁盘空间并未释放,为什么?
    有开发小哥咨询了一个问题,记录一下处理过程分享给有需要的朋友。问题如下:MySQL数据库中有几张表增删比较频繁、数据变动剧烈且数据量大,导致数据增长过快,磁盘占用多。为了节约成本,定期进行数据备份,并通过delete删除表记录,但是执行delete操作后发现磁盘空间并未释放,这是为什么?MySQL......