上篇文章写了 MySQL 写入压测的几种单线程的方式,本来想抛砖引玉,只是提供一些个人的经验和思路。后来有粉丝后台留言,想看看并发怎么处理,所以有了今天这篇文章。

并发在性能测试中应用十分广泛。根据我个人的经验,几乎所有压测都会用到并发。下面我来分享一下 MySQL 写入性能测试当中并发的使用。

首先,我们需要明确一个问题:并发对象。针对 MySQL 测试当中的实际情况,我列举了 3 个并发对象:java.sql.Statementjava.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

之前讨论过 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

下面我们对 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

下面我们进行 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 写入性能受多个因素影响,了解并优化这些因素可以显著提升数据库的写入效率。以下是一些主要的影响因素:

数据库配置

在真实的场景中,针对不同的因素采取不同的策略,在不断学习当中,提升技术实力。


↙↙↙阅读原文可查看相关链接,并与作者交流