本文将通过简单干净的实践方式,向读者展示在使用不同连接池以及不使用连接池的情况下,SpringBoot应用程序对接MySQL进行增删改查的性能对比。同时,还将探讨更新和查询时索引字段的关键性。
在开始之前,让我们先来思考一个问题:一张拥有21个字段的表,存放着100万条数据,会占用多少空间容量?如果这100万条数据在不使用连接池的方式下,以10个并发每条一条插入,需要多长时间?
根据经验,问题1大约需要350MB左右的空间容量,而问题2可能需要2到3小时的时间。尽管这些数字可能因机器配置而异,但重要的是你可以亲自验证它们。本文将使用ApacheBench对MySQL连接池进行压测,帮助读者更好地了解相关知识。
通过这个实践,我们可以得出结论:在使用连接池的情况下,SpringBoot应用程序对接MySQL的性能明显优于不使用连接池的情况。无论是插入、删除、更新还是查询操作,连接池都能够显著减少等待时间和资源消耗。
此外,索引字段在更新和查询时起着关键的作用。合适的索引可以提高查询效率并减少扫描的数据量。因此,在设计数据库结构时,我们应该仔细考虑如何合理地使用索引。
希望本文能够帮助读者更好地理解SpringBoot应用程序对接MySQL时连接池的重要性以及性能优化的方法。通过实践和实验,我们可以更加深入地了解并应用这些知识。
本文涉及的工程:
xfg-dev-tech-connection-pool:https://gitcode.net/KnowledgePlanet/road-map/xfg-dev-tech-connection-pool - 工程内含有环境的安装脚本;mysql-docker-compose.yml、apachebench-docker-compose.yml、road_map_8.0.sql
一、案例背景
拿100万订单数据,压到数据库中!
初始化一个空的数据库表,并向数据库表中写入100万数据。之后在分别不使用连接池和使用不同的连接池(c3p0、dbcp、druid、hikari)写入数据,测试各个连接池的性能。这也能让大家知道,日常我们应该选择哪个连接池。
二、环境配置
因为本章节很偏实操,所以需要大家做下提前安装好 Docker 环境,以便于执行本章节工程中的脚本和代码。可参考别的教程里面 Docker、Portainer 学习安装和使用。
1.在 IntelliJ IDEA 打开 xfg-dev-tech-connection-pool 分别点开 mysql-docker-compose、apachebench-docker-compose,之后点击安装即可。
2.执行完脚本,你可以得到一份安装好的 MySQL 8.0 并安装了数据库表。另外一份是用于压测使用的 ApacheBench
3.连接 MySQL 的工具,推荐使用开源免费的 Sequel Ace
三、工程说明
在 xfg-dev-tech-connection-pool 工程中提供了不同连接池的配置和一些非常常用的 SQL 操作,以及提供了对应的接口进行压测使用;
序号 | 接口 | 说明 |
---|---|---|
1 | http://127.0.0.1:8091/api/mysql/cacheData | 用于缓存数据的接口,拿缓存好的数据压测更新、查询 |
2 | http://127.0.0.1:8091/api/mysql/insert | 插入数据接口 |
3 | http://127.0.0.1:8091/api/mysql/updateOrderStatusByUserId | 使用索引字段 userId 更新订单状态 |
4 | http://127.0.0.1:8091/api/mysql/updateOrderStatusByUserMobile | 使用无索引字段 userMobile 更新订单状态 |
5 | http://127.0.0.1:8091/api/mysql/updateOrderStatusByOrderId | 使用索引字段 orderId 更新订单状态 |
6 | http://127.0.0.1:8091/api/mysql/selectByUserId | 使用索引字段 userId 查询订单 |
7 | http://127.0.0.1:8091/api/mysql/selectByUserMobile | 使用无索引字段 userMobile 查询订单,测试中添加索引 |
8 | http://127.0.0.1:8091/api/mysql/selectByOrderId | 使用有索引字段 orderId 查询订单 |
9 | http://127.0.0.1:8091/api/mysql/selectByOrderIdAndUserId | 区分度高的索引字段在前,区分度低的索引字段在后 |
10 | http://127.0.0.1:8091/api/mysql/selectByUserIdAndOrderId | 区分度低的索引字段在前,区分度高的索引字段在后 |
具体代码实现可以直接对照工程来看,以及按需添加SQL语句进行性能压测验证。
四、库表语句
SQL:xfg-dev-tech-connection-pool/docs/sql/road_map_8.0.sql
这是本节所需要测试的一个订单表和测试前所建的索引字段。以及初始化了100万数据,占用350M空间。
接下来,我们就可以做测试验证了。
五、压测指令
ApacheBench 官网教程:https://httpd.apache.org/docs/2.4/programs/ab.html
常用的如:ab -c 20 -n 1000
http://127.0.0.1:8091/hi - 20个并发1000次
六、压测验证
首先在测试前,正式测试前,你大概需要花费几十分钟来初始化100万数据。
执行脚本;ab -c 20 -n 1000000
http://127.0.0.1:8091/api/mysql/insert - 如果你在工程中配置了 no-pool 大概要花费几个小时才能跑完,这就使用和不使用连接池的差距。
- 连接池比对
条件;
插入1万条数据
连接池配置 initialPoolSize=5、minPoolSize=5、maxPoolSize=20
此时数据库已经有100万数据,分别用几个链接方式插入数据。hikari 放到最后,它是 SpringBoot 的默认连接池。
脚本;ab -c 20 -n 10000 http://127.0.0.1:8091/api/mysql/insert
no-pool | c3p0 | dbcp | druid | hikari | |
---|---|---|---|---|---|
耗时 | 88.990 seconds | 24.228 seconds | 33.656 seconds | 25.971 seconds | 25.002 seconds |
50% | 155ms | 39ms | 60ms | 45ms | 43ms |
80% | 223ms | 61ms | 86ms | 64ms | 64ms |
90% | 291ms | 75ms | 103ms | 75ms | 76ms |
通过对比可以发现,如果不使用连接池,会比使用连接池,要占用更多的时间连接数据库使用数据库。
c3p0、hikari 的性能还是非常不错的,dbcp 相对是弱一些的。所以这可以给你在使用连接池时有一个参考。也可以结合你的机器再次进行压测验证。
2. 更新对比
条件;
使用接口,向内存加入600条数据。每个测试方式,分别消耗200条。ab -c 10 -n 600 http://127.0.0.1:8091/api/mysql/cacheData
之后使用无索引字段、有索引但区分度不高的字段以及使用有索引区分度非常好的字段来更新。
脚本;
1 | ab -c 20 -n 200 http://127.0.0.1:8091/api/mysql/updateOrderStatusByUserMobile |
无索引 | 有索引-区分度不高 | 有索引-区分度很高 | |
---|---|---|---|
耗时 | 24小时+ | 24小时+ | 0.432 seconds |
50% | 24小时+ | 24小时+ | 35ms |
80% | 24小时+ | 24小时+ | 48ms |
90% | 24小时+ | 24小时+ |
无索引的时候;会把整个表的这个记录,全部锁上。那么越执行越慢,最后拖垮数据库。甚至可能1天都执行不完。
有索引-区分度不高;几乎是一样的,如果你批量的对一个用户的所有数据都更新,也会锁很多记录。
有索引-区分度很高;只要你锁的是自己的一条记录,就与别人没有影响。效率也会非常高。
3. 查询对比
条件;
查询100万加的数据库表记录,每次缓存记录5000条数据id;ab -c 10 -n 5000 http://127.0.0.1:8091/api/mysql/cacheData
userId 有索引、orderId 有索引、userMobie 无索引。
每次查询的时候,都要关闭服务重启,避免有缓存干扰结果。
脚本:
1 | ab -c 20 -n 5000 http://127.0.0.1:8091/api/mysql/selectByUserMobile |
无索引 | 有索引-区分度不高 | 有索引区分度高 | 高在前 | 低在前 | |
---|---|---|---|---|---|
耗时 | 6小时+ | 8.343 seconds | 2.051 seconds | 2.168 seconds | 3.279 seconds |
50% | 7s | 13ms | 7ms | 7ms | 11ms |
80% | 9s | 20ms | 10ms | 11ms | 17ms |
90% | 15s | 26ms | 13ms | 13ms | 22ms |
无索引,还是查询字段的。非常危险。
不要在一些区分度不高的字段建索引。当然本案例中,userId 最多也就1000来个用户所产生的1百万数据,这样的情况更适合分库分表。
区分度很高的字段,查询效率会非常好。
把高区分度的索引字段放在前面,更有利于查询。—— 注意不要测试完上一个,直接测试下一个。有缓存的情况下,会影响对比结果。
这就是整个数据库表的压测过程了。如果你有使用的诉求,需要做技术调研,那么一定要做一些这样的压测处理。这样有真实数据才好讲道理。