上篇文章写了 MySQL 写入压测的几种单线程的方式,本来想抛砖引玉,只是提供一些个人的经验和思路。后来有粉丝后台留言,想看看并发怎么处理,所以有了今天这篇文章。
并发在性能测试中应用十分广泛。根据我个人的经验,几乎所有压测都会用到并发。下面我来分享一下 MySQL 写入性能测试当中并发的使用。
首先,我们需要明确一个问题:并发对象。针对 MySQL 测试当中的实际情况,我列举了 3 个并发对象:java.sql.Statement
、 java.sql.Connection
以及 database
。
先说我自测最大的每秒写入行数:50w,如果再优化一下程序,应该会更高,但就测试结果,高也不会高很多了。粗估 100w 以内。
我们先来进行一次基准测试,因为我的电脑已经处于一个薛定谔状态,性能非常不稳定。为了简单快速演示使用方法,这次我用了固定的 sql。
用例如下:
package com.funtest.temp
import com.funtester.db.mysql.FunMySql
import com.funtester.frame.SourceCode
class MysqlTest extends SourceCode {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
String sql = "insert into user (name, age, level, region, address) values ('FunTester', 23, 2, '地球村', '八组一对')";
String ipPort = "127.0.0.1:3306";// 服务端地址
String database = "funtester"// 服务端地址
String user = "root";// 用户名
String password = "funtester";// 密码
def base = new FunMySql(ipPort, database, user, password);// 创建数据库操作基础类
def statement = base.connection.createStatement();// 创建 SQL 语句对象
while (true) {
statement.executeUpdate(sql);// 执行插入语句
}
statement.close();// 关闭资源
base.close();// 关闭资源
}
}
测试结果如下:
行数 | 秒数 |
---|---|
9826 | 36 |
10278 | 37 |
10208 | 38 |
10220 | 39 |
9802 | 40 |
8975 | 41 |
9957 | 42 |
9412 | 43 |
9884 | 44 |
9412 | 45 |
9640 | 46 |
10304 | 47 |
可以看出来比之前的测试结果要好很多,这下大家应该能理解我的电脑薛定谔性能了吧。
之前讨论过 Statement
在查询场景当中实际上是不支持并发的,当时还分析了源码,有兴趣的同学可以翻一翻原来的文章,这里不再赘述原因。至于写入场景,并没有进行相关源码,为了简单,我们直接进行测试了。
下面是用例 case:
package com.funtest.temp
import com.funtester.db.mysql.FunMySql
import com.funtester.frame.SourceCode
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class MysqlTest extends SourceCode {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
String sql = "insert into user (name, age, level, region, address) values ('FunTester', 23, 2, '地球村', '八组一对')";
String ipPort = "127.0.0.1:3306";// 服务端地址
String database = "funtester"// 服务端地址
String user = "root";// 用户名
String password = "funtester";// 密码
def base = new FunMySql(ipPort, database, user, password);// 创建数据库操作基础类
def statement = base.connection.createStatement();// 创建 SQL 语句对象
ExecutorService executors = Executors.newFixedThreadPool(10);// 创建线程池
10.times {
executors.execute {// 10个线程
while (true) {
statement.executeUpdate(sql);// 执行 SQL 语句
}
}
} statement.close();// 关闭资源
base.close();// 关闭资源
}
}
简单用了 10 个线程跑跑看。结果如下:
行数 | 时间 |
---|---|
9584 | 42 |
10263 | 43 |
10098 | 44 |
9744 | 45 |
8864 | 46 |
9019 | 47 |
10133 | 48 |
9768 | 49 |
9613 | 50 |
9886 | 51 |
9835 | 52 |
6585 | 53 |
可以看出,其实没多大区别。在测试过程中也没有报错,说明 Statement
是可以支持并发的,但是实际效果并不明显。
下面我们对 Connection
进行并发,每个线程都创建一个 Statement
这方方案设计既简单又避免相互干扰,是一种很好的隔离策略。
用例的 Case 如下:
import com.funtester.db.mysql.FunMySql
import com.funtester.frame.FunPhaser
import com.funtester.frame.SourceCode
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class MysqlTest extends SourceCode {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
String sql = "insert into user (name, age, level, region, address) values ('FunTester', 23, 2, '地球村', '八组一对')";
String ipPort = "127.0.0.1:3306";// 服务端地址
String database = "funtester"// 服务端地址
String user = "root";// 用户名
String password = "funtester";// 密码
def base = new FunMySql(ipPort, database, user, password);// 创建数据库操作基础类
ExecutorService executors = Executors.newFixedThreadPool(10);// 创建线程池
def phaser = new FunPhaser()// 创建 Phaser 10.times {
phaser.register()// 注册线程
executors.execute {// 10个线程
def statement = base.connection.createStatement();// 创建 SQL 语句对象
while (true) {
statement.executeUpdate(sql);// 执行 SQL 语句
}
phaser.done()// 完成线程
}
} executors.shutdown();// 关闭线程池
phaser.await()// 等待所有线程执行完
base.close();// 关闭资源
}
}
测试结果如下:
行数 | 时间 |
---|---|
10193 | 57 |
10095 | 58 |
9952 | 59 |
9991 | 0 |
9893 | 1 |
9880 | 2 |
8195 | 3 |
7834 | 4 |
8695 | 5 |
8633 | 6 |
9078 | 7 |
8613 | 8 |
可以看出,性能依旧一般般,相差无几。
下面我们进行 database
级别的并发,创建更多的 Connection
来实现期望中更好的写入性能。
行数 | 时间 |
---|---|
38549 | 32 |
43925 | 33 |
32172 | 34 |
44419 | 35 |
42545 | 36 |
40741 | 37 |
34487 | 38 |
47211 | 39 |
43269 | 40 |
45396 | 41 |
36748 | 42 |
这性能一下子就上去了。
下面我们再重复一下单线程性能最高的方法,单词插入 N 行的方案,再次测试,结果如下:
行数 | 时间 |
---|---|
241440 | 12 |
250660 | 13 |
252880 | 14 |
246870 | 15 |
242760 | 16 |
214790 | 17 |
257260 | 18 |
250010 | 19 |
251720 | 20 |
这下是不是感觉 MySQL
写入性能符合要求了呢?
再实际的工作中,场景会更加复杂,影响写入性能的因素比较多。像前两个 Case,虽然理论上性能会提升很多,但实际结果就是相差无几,很可能就是因为触达了单个 Connection
的性能瓶颈。
而 MySQL 写入性能影响因素比较多,除了硬件以外,我简单列举几个。
MySQL 写入性能受多个因素影响,了解并优化这些因素可以显著提升数据库的写入效率。以下是一些主要的影响因素:
数据库配置
在真实的场景中,针对不同的因素采取不同的策略,在不断学习当中,提升技术实力。