衡量数据库性能的主要指标包括事务吞吐率和响应时间,所以测试时也主要考虑这两个指标。注意不要过于看重事务吞吐率而忽略了响应时间,因为合理的响应时间意味着良好的用户体验,所以在合理的响应时间范围内的事务吞吐率才有意义。
事务吞吐率是指数据库操作的速率,即每秒能完成多少事务,由于MySQL InnoDB默认的模式是自动提交,所以也可以近似地将其看作每秒查询数。
响应时间指的是响应请求的总耗时,包括等待时间、执行时间及传输数据的时间。
以下这两个工具可以满足大部分情况下的性能测试和压力测试,特别推荐sysbench。
MySQL自带的mysqlslap也是一个不错的工具,它是从5.1.4版开始的一个MySQL官方提供的压力测试工具,可通过模拟多个并发客户端访问MySQL来执行压力测试。
安装环境:操作系统为Centos6.8 64bit,数据库为MySQL5.7
yum install automake libtool unzip -y
cd /opt/software/
wget https://github.com/akopytov/sysbench/archive/1.0.zip -O "sysbench-1.0.zip"
unzip sysbench-1.0.zip
cd sysbench-1.0
./autogen.sh
./configure --with-mysql-includes=/usr/local/mysql/include --with-mysql-libs=/usr/local/mysql/lib
make
make install
include ld.so.conf.d/*.conf
/usr/local/mysql/lib
/sbin/ldconfig -v
error while loading shared libraries: libmysqlclient.so.20: cannot open shar
shell> rm -rf sysbench-1.0
shell> find / -name 'libmysqlclient.so.20'
/usr/local/mysql/lib/libmysqlclient.so.20
shell> ln -s /usr/local/mysql/lib/libmysqlclient.so.20 /usr/lib/
-max-time:运行时间限制,单位是秒,执行到max-time即结束并输出,不等任务完全执行完
–num-threads:线程数。
–max-requests:查询数限制。
percentile 95%:95%请求的最大响应时间,也就是删除5%的响应时间最长的请求,然后从剩余的请求中选取最大的响应时间值。
execution time (avg/stddev):执行总时间/方差。
$ sysbench --test=cpu help
WARNING: the --test option is deprecated. You can pass a script name or path on the command line without any options.
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
cpu options:
--cpu-max-prime=N upper limit for primes generator [10000]
sysbench --test=cpu --cpu-max-prime=20000 run
$ sysbench --num-threads=20 --max-requests=100 --test=cpu --debug --cpu-max-prime=10000000 run
sysbench --test=memory --memory-block-size=8K --memory-total-size=4G run
$ sysbench --num-threads=10 --max-requests=100 --test=memory --memory-block-size=8k --memory-total-size=32G --memory-oper=read run
$ sysbench --num-threads=10 --max-requests=100 --test=memory --memory-block-size=8k --memory-total-size=32G --memory-oper=write run
sysbench --test=threads --num-threads=64 --thread-yields=1000 --thread-locks=8 run
磁盘性能测试很重要,MySQL很多优化跟磁盘有关,特别是将磁盘顺序读改为内存中随机读等。
对于磁盘的测试,可以通过如下命令查看sysbench提供了哪些测试选项:
shell> sysbench --test=fileio help
sysbench --test=fileio --num-threads=16 --file-total-size=12G --file-test-mode=rndrw prepare
sysbench --test=fileio --num-threads=16 --file-total-size=12G --file-test-mode=rndrw run
sysbench --test=fileio --num-threads=16 --file-total-size=12G --file-test-mode=rndrw cleanup
上述代码分为3个步骤,第一条命令初始化文件,第二条命令执行测试,第三条命令清理文件。
–num-threads参数指定了最大创建16个线程,–file-total-size参数指定创建文档的总大小为12GB,–file-test-mode指定文档的读写模式为随机读写。
磁盘I/O性能测试是进行数据库基准测试时要着重加以研究的。我们需要衡量各种因素,比如操作类型、读写的频率、I/O大小、是随机读写还是顺序读写、写的类型是异步还是同步、并发线程情况、操作系统缓存状态及文件系统有哪些调优等因素。
文件测试类型(f i le-test-mode)有如下几种。
下面场景测试的是,20个文件,每个10GB,随机读写,文件大小总量在200G。
$ sysbench --file-num=20 --num-threads=20 --test=fileio --file-total-size=200G --max-requests=1000000 --file-test-mode=rndrw prepare
$ sysbench --file-num=20 --num-threads=20 --test=fileio --file-total-size=200G --max-requests=1000000 --file-test-mode=rndrw run
$ sysbench --file-num=20 --num-threads=20 --test=fileio --file-total-size=200G --max-requests=1000000 --file-test-mode=rndrw cleanup
shell> sysbench --test=fileio --file-num=16 --file-total-size=2G prepare
shell> sysbench --test=fileio --file-num=16 --file-total-size=2G --file-test-mode=rndrd --time=180 --events=10000000 --threads=16 --file-extra-flags=direct --file-fsync-freq=0 --file-block-size=16384 run
shell> sysbench --test=fileio --file-num=16 --file-total-size=2G cleanup
FATAL: Failed to read file! file: 14 pos: 126887736 errno = 22 (Inv
......
File operations:
reads/s: 29031.95
writes/s: 0.00
fsyncs/s: 0.00
Throughput:
read, MiB/s: 453.62
written, MiB/s: 0.00
General statistics:
total time: 180.0006s
total number of events: 5225792
Latency (ms):
min: 0.04
avg: 0.55
max: 42.86
95th percentile: 1.08
sum: 2869653.40
Threads fairness:
events (avg/stddev): 326612.0000/48808.82
execution time (avg/stddev): 179.3533/0.15
可能用户需要测试随机读、随机写、随机读写、顺序写、顺序读等所有这些模式,并且还需要测试不同的线程和不同文件块下磁盘的性能表现,这时可能需要类似如下的脚本来帮用户自动完成这些测试:
#!/bin/sh
set -u
set -x
set -e
for size in 8G,64G; do
for mode in seqwr seqrewr seqrd rndrd rndwr rndrw; do
for blksize in 4096 16384; do
sysbench --test=fileio --file-num=16 --file-total-size=$size prepare
for threads in 1 4 8 16 32; do
echo "===== test $blksize in $threads threads"
echo PARAMS $size $mode $threads $blksize> sysbench-size-$size-mode-$mode-threads-$threads-blksz-$blksize
for i in 1 2 3; do
sysbench --test=fileio --file-total-size=$size --file-test-mode=$mode --time=180 --events=1000000 --threads=$threads --file-num=16 --file-extra-flags=direct --file-fsync-freq=0 --file-block-size=$blksize run | tee -a sysbench-size-$size-mode-$mode-threads-$threads-blksz-$blksize 2 >&1
done
done
sysbench --test=fileio --file-num=16 --file-total-size=$size cleanup
done
done
done
mysql> create database sbtest;
mysql> grant all privileges on sbtest.* to test@'localhost' identified by 'test';
sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --mysql-table-engine=innodb --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=test --mysql-password=test --mysql-socket=/tmp/mysql.sock prepare
sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=test --mysql-password=test --mysql-socket=/tmp/mysql.sock --max-time=60 --max-requests=0 --num-threads=2 --report-interval=10 run
sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=test --mysql-password=test --mysql-socket=/tmp/mysql.sock cleanup
mysql> CREATE DATABASE IF NOT EXISTS sysbenchtest;
mysql> CREATE USER sysbenchtest IDENTIFIED BY 'sysbenchtest';
mysql> GRANT ALL ON sysbenchtest.* TO sysbenchtest;
$ sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=sysbenchtest --mysql-password=sysbenchtest --mysql-socket=/var/lib/mysql/mysql.sock --oltp-test-mode=complex --mysql-db=sysbenchtest --mysql-table-engine=innodb --threads=30 --events=5000000 prepare
$ sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=sysbenchtest --mysql-password=sysbenchtest --mysql-socket=/var/lib/mysql/mysql.sock --oltp-test-mode=complex --mysql-db=sysbenchtest --mysql-table-engine=innodb --threads=30 --events=5000000 run
$ sysbench --test=/usr/local/share/sysbench/tests/include/oltp_legacy/oltp.lua --oltp-tables-count=32 --oltp-table-size=100000 --mysql-user=sysbenchtest --mysql-password=sysbenchtest --mysql-socket=/var/lib/mysql/mysql.sock --oltp-test-mode=complex --mysql-db=sysbenchtest --mysql-table-engine=innodb --threads=30 --events=5000000 cleanup
SQL statistics:
queries performed:
read: 85092
write: 24312
other: 12156
total: 121560
transactions: 6078 (604.46 per sec.)
queries: 121560 (12089.21 per sec.)
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
General statistics:
total time: 10.0539s
total number of events: 6078
Latency (ms):
min: 6.54
avg: 49.52
max: 282.87
95th percentile: 112.67
sum: 300997.27
Threads fairness:
events (avg/stddev): 202.6000/6.28
execution time (avg/stddev): 10.0332/0.01
$ sysbench /usr/local/share/sysbench/oltp_read_write.lua --mysql-user=sysbenchtest --mysql-password=sysbenchtest --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-db=sysbenchtest --tables=10 --table-size=1000000 --events=5000000 --report-interval=5 --time=180 --threads=30 prepare
$ sysbench /usr/local/share/sysbench/oltp_read_write.lua --mysql-user=sysbenchtest --mysql-password=sysbenchtest --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-db=sysbenchtest --tables=10 --table-size=1000000 --events=5000000 --report-interval=5 --time=180 --threads=30 run
mysql> SET GLOBAL max_connections=2000;
mysql> SET GLOBAL max_prepared_stmt_count=500000;
......
[ 70s ] thds: 100 tps: 531.80 qps: 10692.57 (r/w/o: 7469.57/2159.61/1063.39) lat (ms,95%): 376.49 err/s: 0.00 reconn/s: 0.00
[ 75s ] thds: 100 tps: 534.80 qps: 10644.45 (r/w/o: 7451.63/2123.01/1069.80) lat (ms,95%): 376.49 err/s: 0.00 reconn/s: 0.00
[ 80s ] thds: 100 tps: 525.12 qps: 10512.63 (r/w/o: 7369.91/2092.68/1050.04) lat (ms,95%): 442.73 err/s: 0.00 reconn/s: 0.00
......
* soft nproc 65535
* soft nofile 65535
* hard nproc 65535
* hard nofile 65535
innodb_log_file_size=1073741824
sync_binlog=0
innodb_buffer_pool_size=107374182
......
[ 60s ] thds: 100 tps: 872.70 qps: 17428.43 (r/w/o: 12188.62/3494.61/1745.20) lat (ms,95%): 196.89 err/s: 0.00 reconn/s: 0.00
[ 65s ] thds: 100 tps: 850.92 qps: 17098.77 (r/w/o: 11952.05/3444.48/1702.24) lat (ms,95%): 204.11 err/s: 0.00 reconn/s: 0.00
[ 70s ] thds: 100 tps: 870.40 qps: 17401.23 (r/w/o: 12194.02/3466.21/1741.00) lat (ms,95%): 204.11 err/s: 0.00 reconn/s: 0.00
......
mysqlslap是MySQL 5.1.4及以后版本自带的一个用于实现负载性能测试和压力测试的工具。它可以模拟多个客户端对数据库进行施压,并生成报告以衡量数据库的一些指标。其工作原理可分为如下三个步骤。
mysqlslap -u root --engine=innodb --auto-generate-sql
--auto-generate-sql-unique-query-number=100 --auto-generate-sql-unique-write-number=100 --auto-generate-sql-write-number=1000
--create-schema=test --auto-generate-sql-load-type=mixed --concurrency=10,100 --number-of-queries=1000 --iterations=1
--number-char-cols=1 --number-int-cols=8 --auto-generate-sql-secondary-indexes=1 --verbose
time mysqlslap -u root --engine=innodb --auto-generate-sql-load-type=key
--auto-generate-sql --auto-generate-sql-write-number=100000 --auto-generate-sql-guid-primary
--number-char-cols=10 --number-int-cols=10 --concurrency=10,100 --number-of-queries=5000
mysqlslap -uroot --engine=innodb --auto-generate-sql --auto-generate-sql-write-number=20000000 --auto-generate-sql-add-autoincrement --auto-generate-sql-secondary-indexes=2 --concurrency=50 --number-of-queries=1000000 --number-char-cols=3 --number-int-cols=2
先通过shell脚本生成测试数据,再使用LOAD DATA导入数据,是最快的测试数据生成方式。
CREATE TABLE `users` (
`userid` int(11) unsigned NOT NULL,
`user_name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
$ vi init_data.sh
for i in {1..1000}
do
echo $i,user_$i
done > a.lst
$ chmod +x init_data.sh
$ ./init_data.sh
$ chown mysql:mysql a.lst
mysql> LOAD DATA LOCAL INFILE '/root/a.lst' INTO TABLE d1.users FIELDS TERMINATED BY ',';
Query OK, 1000 rows affected (0.01 sec)
Records: 1000 Deleted: 0 Skipped: 0 Warnings: 0