对于我们编写的几乎每个网络或移动应用程序来说,其底层的关键组件之一就是数据库。对于编写使用数据库且高性能且资源高效的应用程序,必须处理一项关键资源,但与 CPU、内存等不同,它通常不是很明显。该资源是数据库连接。
什么是数据库连接?
使用MySQL作为DB的数据库连接
数据库连接是由数据库驱动程序创建的对象,数据库驱动程序是一个软件,用于管理与数据库通信的细节,并使我们的应用程序代码能够轻松地使用数据库。例如,MySQL连接可以通过com.mysql.jdbc.Driver驱动程序创建。连接维护许多东西,其中包括用于数据交换的套接字 (套接字是两台机器之间连接的松散逻辑表示)以及与数据库的会话,与典型的 Web 会话非常相似。
需要连接池
连接管理不善的陷阱
未正确关闭数据库连接是难以检测的错误来源。最常见的错误是:
-
'Too many connections'错误,数据库不接受客户端连接进行查询处理。
-
某种形式的内存溢出(例如:'Heap OutOfMemoryError'):由于打开的连接在内存中累积而发生。
即使 Web 应用程序中服务请求的单个位置没有正确关闭连接,随着时间的推移,也可能会因上述错误而导致应用程序崩溃。
使用连接池通常可以减少或消除此类错误。
引入连接池的效果
考虑以下简单程序,它在数据库上运行 1000 个查询:
for i = 1 to 1000
connection = Driver.getConnection()
result = connection.executeQuery(query)
connection.close()
上面代码中我们创建和关闭连接的次数随着执行的查询数量线性增加。
该程序可以通过使用一个非常简单的连接池来高度优化,连接池只是活动数据库连接的缓存。
Pool = CreateConnectionPool(size = 2)
for i = 1 to 1000
connection = Pool.getConnection()
result = executeQuery(query, connection)
Pool.returnConnection(connection)
连接池的简化表示
在这里,创建连接被从池中获取连接所取代,而关闭连接则被返回到池中所取代,后者速度更快。
无池(~34 秒)
// Query Execution without pool ---> ~34 sec
public static void main(String[] args) throws SQLException {
long start = System.currentTimeMillis();
for(int i = 0 ;i < 1000; i++){
// 1. Application will load the suitable MySQL Driver, eg: com.mysql.jdbc.Driver, com.mysql.cj.jdbc.Driver
// 2. Driver will connect to the MySQL DB using the provided URL for the DB and credentials
// 3. Driver will return a connection object corresponding to created DB connection
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db", "root", "");
connection.createStatement().execute("select count(*) from places");
// release connection
connection.close();
}
System.out.println(System.currentTimeMillis() - start);
带池(非常幼稚的实现)(~16 秒)
//Query execution using a very simplistic Connection Pool ---> ~16 sec
public static void main(String[] args) throws SQLException {
long start = System.currentTimeMillis();
// create pool
List<Connection> pool = new ArrayList<>();
for(int i = 0; i < 10;i++){
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db", "root", "");
pool.add(connection);
}
Random r = new Random();
for(int i = 0 ;i < 1000; i++){
// get connection
int randomIdx = r.nextInt(10);
Connection connection = pool.get(randomIdx);
pool.remove(randomIdx);
connection.createStatement().execute("select count(*) from places");
// release connection
pool.add(randomIdx, connection);
}
System.out.println(System.currentTimeMillis() - start);
}
连接池虽然以非常简单的方式实现,但却导致程序执行时间减少了50% 以上(执行时间从 34 秒至 16 秒)。
复杂的连接池库,例如HikariCP、C3P0等,可以带来巨大的性能提升并为您的应用程序带来资源效率。
如何确定连接池的大小
假设您有一个 Web 应用程序,其中处理每个请求都需要对数据库记录执行操作,并且您在阅读本文后决定使用连接池。
如果您的 Web 应用程序要处理 100 个并发请求的负载,那么连接池的大小应该是多少?100,对吗(每个请求 1 个)?
答案是,这取决于几个因素,而且大多数情况下,必须通过实验得出适当的值。需要考虑的一些因素如下:
-
您的网络应用程序处理的并发请求数
-
平均查询执行时间
-
DB资源(CPU核心、磁盘速度)
但它通常比人们通常想象的要低。例如,对于上例中的 100 个并发请求,好的池大小大多不会大于 10。最后,这里有一个图可以直观地考虑大小调整:
作者:Ajay Joshi
更多技术干货请关注公号【云原生数据库】
squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。
irds.cn,多数据库管理平台(私有云)。