MySQL 性能:基准测试工具包(BMK-kit)
MySQL 性能:基准测试工具包(BMK-kit)
2022-06-20 19:45 | MySQL, 性能, OCI, 工具, 使用指南, Sysbench, InnoDB, TPCC, dbSTRESS, Linux, SSL, XFS, 压缩
作者:Dimitri
以下是关于部署和使用基准测试工具包(BMK-kit)的简要使用指南。这个工具包的主要目的是简化在运行各种 MySQL 基准测试负载时的操作,减少复杂性和潜在错误。
一般来说,操作非常简单,像这样:
$ bash /BMK/sb_exec/sb11-Prepare_50M_8tab-InnoDB.sh 32 # 准备数据
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
# 每个负载级别运行 OLTP_RW 5 分钟..
bash /BMK/sb_exec/sb11-OLTP_RW_50M_8tab-uniform-ps-trx.sh $users 300
sleep 15
done
最新的公开在线版本的以下使用指南始终可以从这里获取:http://dimitrik.free.fr/blog/posts/mysql-perf-bmk-kit.html
前言
自从 v.1.0 版本以来,我将新的(基于 Lua 的)Sysbench 视为负载生成“平台”(而不再仅仅是另一个测试应用)。工具包中提供的所有测试都运行在 Sysbench 上,因此所有结果输出都是类似的,运行任何测试只需要一个 Sysbench 二进制文件。工具包中提供的 Sysbench 二进制文件是动态编译的,已与 MySQL 客户端库链接,MySQL 客户端库也包含在同一个包中,因此一般情况下它应该可以正常工作。
关于创建这个 BMK-kit 的最初想法,源于需要用简短的标签标记我在 MySQL 上运行的每个测试负载,以便快速了解在运行过程中应用了哪些测试条件。事实上,周围有多少人,就有多少种“标签”方式 😉)
例如,对于 Sysbench 的 OLTP 读写测试,8 个表,每个表 1000 万行,均匀访问,使用预处理语句,启用事务 — 这种测试通常会使用像这样的标签:
rw8x10upstr
— 这个标签可能足够简短,但不容易解码 😉)
我个人更倾向于使用像这样的格式:
OLTP_RW-10Mx8tab-uniform-ps-trx
如果同样的测试使用了“帕累托”(Pareto)访问模式,并且没有使用预处理语句或事务,标签则为:
OLTP_RW-10Mx8tab-pareto-notrx
等等。
随着时间的推移,我意识到更有帮助的是,拥有一套专门的 shell 脚本,这些脚本的名字与这些“标签”完全相同,可以在这些“标记”条件下执行所需的测试!——这样做不仅能清晰指明到底执行了什么测试,还能保护我的测试免受错误或遗漏的选项/参数等影响。毕竟,使用 Sysbench 进行性能评估时,仍然可能出现很多常见错误。
于是,我最终编写了大量不同的 shell 脚本,以自动执行我预计要运行的各种测试负载,最终形成了这个 BMK-kit 包。
注意:这个工具包及其工作负载场景是以“我喜欢”的方式提出来的。如果你不同意某些内容或更喜欢其他测试方法,欢迎自己实现任何你想要的功能,但请不要期望我会为某个细节进行辩论或辩护。我只是以自己的方式做事,并在这里分享,以防对其他人有用。但我并不主张这是绝对正确的,你永远可以按照自己的方式做事,这正是生活的美妙之处… 😉)
设置 BMK-kit
该测试工具包需要最小的配置,但其脚本中有一些“可配置”的地方。例如,默认情况下使用 /BMK
目录作为主目录——但是可以通过 BMK_HOME
环境变量轻松更改等。
因此,在接下来的所有步骤中,我将使用
/BMK
作为 默认 目录,请注意,你可以将工具包部署在 任何地方,只需要将BMK_HOME
环境变量指向你的目录。
至此,部署和设置工具包的步骤如下:
- 从以下地址下载
BMK-kit.tgz
压缩包:http://dimitrik.free.fr/BMK-kit.tgz - 将其部署到任何位置,或者作为
root
用户从/
目录开始:
# cd /
# tar xzf /path/to/BMK-kit.tgz
- 这将创建
/BMK
目录并部署所有需要的脚本 - 现在,你需要编辑
/BMK/.bench
默认情况下,你的 .bench
配置文件将如下所示:
#!/bin/bash
#===================================================================================
# Benchmark-kit 配置设置..
#===================================================================================
BMK_HOME=${BMK_HOME:=/BMK}
export BMK_HOME
#-----------------------------------------
# Linux/x64(默认)或 Linux/arm64
#
EXT="x64"
## EXT="arm64"
#-----------------------------------------
# MySQL 连接配置
#
user=dim
pass=dimitri
host=127.0.0.1
port=5400
socket=/apps/mysql8/data/mysql.sock
mysql=/apps/mysql8/bin/mysql
# 最大连接数:单个 Sysbench-1.1 进程
sb11_MAX=20000
- 你只需要编辑该文件,提供连接到 MySQL 服务器的详细信息(
user
、password
、host
、IPport
、UNIXsocket
路径和数据库名) - 如果你使用的是 arm64 架构的 Linux,需要在
.bench
文件中注释掉EXT=x64
,并取消注释EXT=arm64
sb11_MAX
值:该值在.bench
文件中用于当需要测试非常大量的 MySQL 用户连接时(例如超过 10K 用户)—因为有时系统或 Sysbench 本身可能会出现一些问题,导致无法再连接新的用户(或断开连接等)。一种解决方法是并行启动更多的 Sysbench 进程,而sb11_MAX
参数用于指示每个 Sysbench 进程应该分配多少个用户(目前默认设置为一个非常大的值,这样只会使用一个 Sysbench 进程,但如果你遇到这种问题,可以将其更改为较小的值(例如 1000))。
到此为止,你已经准备好开始你的第一个基准测试负载!确保你的 MySQL 服务器正在运行,创建数据库,然后按照下一步操作。
BMK-kit 中任何测试负载的主要步骤都是相似的:
- 首先“准备”数据库(初始化数据加载)
- 然后执行你想要的测试负载,使用“准备好的”数据
每个脚本的名称都是“明确”的,能够自我解释将执行哪种类型的测试以及使用哪些选项等,从而最大程度减少潜在的错误。
本地主机和 MySQL
如果你不清楚,MySQL 有一些历史上的配置约定,这可能会让人感到困惑,但我们必须在代码中保留这些配置以保持向后兼容性:
- 当你将主机名设置为
localhost
时 => 用户连接通过 UNIX 套接字进行 - 而将主机名设置为
127.0.0.1
时 => 用户连接通过本地 IP 栈(回环)进行
请始终确认你想要测试的连接类型,因为通过 UNIX 套接字的通信速度更快(有时快 50%,甚至更多),但如果最终你打算使用 TCP/IP 连接,这种结果可能不具有代表性。另一方面,使用 UNIX 套接字可以跳过 IP 栈,帮助你更好地发现其他瓶颈等——这完全取决于你,只要始终明白自己在做什么 😃)
注意:通过 UNIX 套接字与 IP 回环进行连接的性能差异尚未完全解释清楚,但似乎是 Linux 内核中的纯软件问题,在 x64 和 ARM64 的 Linux 系统中都观察到了类似的性能差距——例如可以查看此处的讨论:http://dimitrik.free.fr/blog/posts/mysql-performance-80-ga-ip-port-vs-unix-socket-impact.html
使用 SSL
默认情况下,我现在随 BMK-kit 提供的 Sysbench 二进制文件已编译为 MySQL 8.0 客户端库和 OpenSSL-1.1.1L,因此你不需要在 .bench
配置中做任何额外的配置来启用 Sysbench 端的 SSL。
然而,为了在测试负载期间强制使用 SSL,你需要在命令行中添加 --mysql-ssl=REQUIRED
选项:
# -- 无 SSL 执行 --
$ time bash /BMK/sb_exec/sb11-Prepare_10M_8tab-InnoDB.sh 32
$ bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-trx.sh 128 120
# -- 使用 SSL 执行 --
$ time bash /BMK/sb_exec/sb11-Prepare_10M_8tab-InnoDB.sh 32 --mysql-ssl=REQUIRED
$ bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-trx.sh 128 120 --mysql-ssl=REQUIRED
依此类推… 😉)
另外,为了在你的测试场景中强制执行 SSL 条件,你可以使用 require SSL
选项创建数据库用户,例如:
mysql> drop user 'dim'@'%';
mysql> create user 'dim'@'%' identified WITH caching_sha2_password by 'xxxxx';
mysql> grant SELECT,INSERT,DELETE,UPDATE,CREATE,DROP,PROCESS,USAGE,INDEX on *.* to 'dim'@'%' ;
mysql> grant SHOW DATABASES on *.* to 'dim'@'%' ;
mysql> alter user 'dim'@'%' require SSL; -- <= REQUIRE SSL (!)
Sysbench “原始” 工作负载
首先,你需要加载数据。这里我将展示最常见的测试场景,但你可以进一步探索并选择自己喜欢的测试。
Sysbench “原始” 工作负载场景是非常好的“入门票”测试用例,用于评估你的 MySQL 实例的容量,系统的性能,最终是整个平台的性能。
包含 8 个表,每个表 1000 万行(10Mx8tab)的数据集是最常见的起始点,因为它既不小(不只是运行在 CPU 缓存上),也不大(在写入操作时不会涉及过多的 IO 活动)。该数据集大约代表 20GB 数据,因此使用 32GB 的 BP 大小足够将所有数据加载到内存中。
准备 10Mx8tab 数据集
要准备数据,你只需要执行以下命令:
$ time bash /BMK/sb_exec/sb11-Prepare_10M_8tab-InnoDB.sh 32
注意:
32
是要用于并行加载数据的用户连接数(我修改了原始的 Sysbench 脚本以支持这一点,因此在上述示例中,每个表将使用 4 个用户连接并行加载)。如果你的系统容量允许,并且负载能够加速,你可以增加这个数字(你需要自己进行测量,看看在哪个并行度下最优)。默认情况下,所有“Prepare”脚本使用 32 个用户连接。
只读点查询(Point-Select)工作负载
Point-Select 工作负载是最简单的一种,它进行的是纯粹的“键 => 值”查找,测试 MySQL 代码的整个堆栈,评估代码效率和查询执行的最佳延迟。在内存中执行,这是评估系统设置及其可扩展性限制的最简单方式。只会涉及内存访问和 CPU 性能,因此,如果你在此测试负载中已经观察到性能问题,通常是系统/平台本身存在问题 😉)
一个运行测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096
do
bash /BMK/sb_exec/sb11-OLTP_RO_10M_8tab-uniform-ps-p_sel1-notrx.sh $users 300
sleep 15
done
注释:
- 在这个示例中,我们执行点查询工作负载,并增加负载级别
- 从 1 个用户连接开始,然后依次使用 2、4 个用户连接,直到 1024 个并发用户,每个级别执行 300 秒(5 分钟)
sb11-OLTP_RO_10M_8tab-uniform-ps-p_sel1-notrx.sh
是执行所需测试负载的脚本(所有其他测试脚本也是通过类似的方式自动生成的)- 执行时需要两个命令行参数:第一个参数是要使用的用户连接数,第二个参数是执行时间(单位:秒)— 在之后,你可以提供一些“特定”参数(如果需要)— 更多内容稍后介绍…
- 关于脚本名称中使用的缩写:
sb11
— 表示脚本将使用 Sysbench 1.1 版本(.bench
配置中的 sb11 值)OLTP_RO_10M_8tab
— 只读工作负载,8 个表,每个表 1000 万行uniform
— 使用“均匀”访问模式ps
— 使用预处理语句p_sel1
— 使用 1 个“点查询”查询,来自 OLTP_RO 场景notrx
— 不使用事务
在所有其他测试负载的脚本中都使用了类似的“逻辑”,因此通过脚本名称,你可以直接知道将生成什么样的负载场景(比如如果你使用相同的脚本,但没有 ps
,就意味着它将执行相同的负载,但 不使用预处理语句,或者如果脚本名称中有 socket
,就意味着它将使用 UNIX 套接字而不是 IP 端口(默认)进行连接,依此类推)…
例如,去掉预处理语句的相同测试:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096
do
bash /BMK/sb_exec/sb11-OLTP_RO_10M_8tab-uniform-p_sel1-notrx.sh $users 300
sleep 15
done
OLTP_RO
OLTP 是一个“混合型”的只读工作负载,较为复杂,因为它包含:
- 10 个点查询
- 1 个简单范围查询
- 1 个有序范围查询
- 1 个求和范围查询
- 1 个去重范围查询
例如,总共有 14 个查询。并且,如果点查询大多不“敏感”于错误配置,只要使用足够大的 BP 大小来保持数据在内存中(32GB),其他查询就会更加敏感 😉
例如:
- 使用 latin1 或 UTF8 字符集时,数据的字符集选择会有显著差异(MySQL 8.0 之前,UTF8 的开销要大得多——更多细节请见: http://dimitrik.free.fr/blog/posts/mysql-performance-80-and-utf8-impact.html 和 http://dimitrik.free.fr/blog/posts/mysql-performance-80-ga-more-in-depth-latin1-utf8mb4.html)
- 另外,如果你使用的是 UTF8(MySQL 8.0 默认字符集),并且
sort_buffer
没有配置足够大,你会遇到很糟糕的体验 😉)(例如,256KB 的值在当前示例中是合适的) - 你还需要注意你使用的 malloc 库(我当前选择的是
tcmalloc-4.4.5
,它来自 OL7 仓库) - 注意:例如,去重范围查询可能会非常占用内存,尤其是在临时表空间的使用上,需要注意 😉
现在来看一个如何运行 OLTP_RO 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RO_10M_8tab-uniform-notrx.sh $users 300
sleep 15
done
如你所见,唯一与“点查询”执行的不同之处是脚本名称中没有 p_sel1
😉
注意:只读工作负载通常不使用事务,结果会以 QPS(查询/秒)报告。如果因某些原因在调查此工作负载的性能时需要使用事务,请注意,
begin
和commit
会被 Sysbench 视为查询,并且你需要在最终的 QPS 数字中手动剔除它们,以避免报告“虚假的”结果 😉)
OLTP_RW
OLTP 读写(OLTP_RW)使用事务,并在同一个事务中执行所有来自 OLTP_RO 的只读查询,然后执行 2 次 UPDATE、1 次 INSERT 和 1 次 DELETE 查询。在这里,许多 MySQL 配置和调优选项将发挥作用,但首先要考虑的是——你系统上使用的存储!
下面是如何运行 OLTP_RW 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-trx.sh $users 300
sleep 15
done
OLTP_RW+
OLTP_RW+ 是 OLTP_RW 的逻辑代号,代表“更写密集型”的 OLTP_RW——事实上,它与 OLTP_RW 完全相同,唯一的区别是“范围”SELECT 查询被省略了,但仍然保留了 10 个点查询(即减少了 SELECT 的数量,从而降低了读写比)。脚本名称通过 “p_sel10” 前缀反映了这一点。
下面是如何运行 OLTP_RW+ 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-p_sel10-trx.sh $users 300
sleep 15
done
OLTP_RW++
OLTP_RW++ 是 OLTP_RW 的逻辑代号,代表“更写密集型”的 OLTP_RW——它与 OLTP_RW+ 完全相同,但只执行 1 个点查询(例如,我们强制进行更多的写操作,但仍然执行 1 次 SELECT 查询,以避免变成纯写操作)。脚本名称通过 “p_sel1” 前缀反映了这一点。
下面是如何运行 OLTP_RW++ 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-p_sel1-trx.sh $users 300
sleep 15
done
UPDATE-NoKEY(无索引更新)
如果 Point-Select 工作负载是最具侵略性的只读工作负载,那么 UPDATE-NoKEY 测试就是写操作中的最具侵略性:
- 它不改变任何索引数据(NoKEY / 无索引)
- 所以,所有的数据更新都是就地发生的
- 只有事务管理的完整堆栈被用来测试其效率
- 自 MySQL 8.0 以来,事务管理已经有了很多改进
- 但在这个工作负载下,我们仍然没有实现扩展性,原因是
trx_sys
的争用(此项工作仍在进行中) - 因此,你不必惊讶地看到 1 CPU 插槽与 2 CPU 插槽之间的性能差异
- 这个工作负载在没有事务的情况下执行
- 纯粹是更新“轰炸”
下面是如何运行 UPDATE-NoKEY 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-upd_noidx1-notrx.sh $users 300
sleep 15
done
是的,与 OLTP_RW 脚本的主要区别在于脚本名称中包含了 upd_noidx1
,表示仅执行 OLTP_RW 场景中的“无索引更新”查询。
UPDATE-KEY(有索引更新)
如果你将上述脚本名称中的 upd_noidx1
替换为 upd_idx1
,你将运行 UPDATE-KEY(有索引更新)工作负载。它不再是“就地”更新,因为它将涉及二级索引的值,可能会涉及更多的内部锁和争用。
下面是如何运行 UPDATE-KEY 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-upd_idx1-notrx.sh $users 300
sleep 15
done
Write-Only(只写)
另外,如果你将 upd_noidx1
替换为 upd_insdel1
,则会得到只写工作负载的脚本名称 😉)——但是请记住,对于混合写操作,你必须使用 事务!!——否则你将遇到数据冲突和错误!——因此,之前脚本中的 notrx
会改为 trx
,就像这里一样:
下面是如何运行 Write-Only 测试的简单示例:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-upd_insdel1-trx.sh $users 300
sleep 15
done
注意:在只写负载中,你只有 2 个 UPDATE(索引更新和无索引更新),以及 1 次 DELETE 和 1 次 INSERT,这就是为什么脚本使用
upd_insdel1
这个代号,但至少它是明确的,通过脚本名称我们可以直接知道执行了哪种测试。
50Mx8tab 数据集
到目前为止,10M x 8 表的数据集对于快速(通常是“内存中”)的 MySQL / 系统评估已经相当不错。但如果你想深入测试,你可以切换到每个表 50M 行的数据集(仍然使用 8 个表),这样将生成超过 100GB 的数据空间,并允许你测试:
- 使用 128GB BP 大小的内存工作负载(检查 QPS / TPS 是否与之前 10M 行的数据相同,如果不同,尝试解释“为什么” ;-))
- 使用 64GB BP 大小的部分 IO 限制工作负载
- 使用 32GB BP 大小的重度 IO 限制工作负载(同时充分评估你的存储性能)
要执行类似的测试,只需将脚本名称中的 10M
更改为 50M
,然后你就完成了 😉)
注意:如果你将脚本名称中的数字改为
500M
,你将在所有测试中使用 1TB 的数据,这将变得更加具有挑战性,尤其是在系统 RAM 限制时。
数据访问模式
Sysbench 与“均匀”访问模式一起,还支持“帕累托”和其他几种访问模式。历史上,Sysbench 中的“均匀”访问模式并不是默认的,这导致许多供应商得出并发布了虚假的结果。
对我而言,最有趣并值得使用的访问模式有以下三种:
uniform
— 完全随机和均匀访问,广泛的轰炸,IO 限制下较为艰难pareto
— 随机,但“分组”访问,访问的行相对集中,创建对同一行的并发访问,涉及行锁和扩展性限制zipfian
— 一些行比其他行更频繁访问,但方式更广泛,因此在执行过程中通常不会造成行锁
在本文的最后,你将看到在不同的测试条件下,Sysbench 所有可用数据访问选项的基准结果(8 表与单表,内存型与 IO 限制型等)。我希望从结果中可以更清楚地了解为什么我只选择了上面这三种访问模式。
“原始” Sysbench 工作负载的新扩展
我为 “原始” Sysbench 脚本添加了一些新扩展:
--table-name=name
:用于表名称的基本名称(默认:sbtest
)--mysql-table-partitions=N
:为每个表使用 N 个范围(id)分区(默认:0)--mysql-table-compression=name
:额外的表透明压缩选项,例如:lz4
--rnd-data=N
:行值中随机数据的百分比(%):1 … 100(用于评估较高/较低的压缩可能性,默认:100)--extra-cols=N
:向表中添加 N 列额外列--extra-cols-type=TYPE
:用于额外列的数据类型(例如:VARCHAR(32))--extra-cols-default=value
:为额外列使用的默认值(你还可以使用#
和@
符号在默认值中随机化数据(每个/
符号将被随机数字(0-9)替换,每个@
将被随机字母(a-z)替换)——这对于 BLOB/TEXT 和任何其他数据类型(包括 INT 等)也有效)--extra-cols-options=...
:额外列的选项--select-star="..."
:允许替换 SELECT 查询中的默认c
列,适用于点查询和简单/有序范围查询:- 这可以是其他列的列表,例如:
--select-star="id,k"
- 这可以是简单的星号:
--select-star="*"
(获取SELECT * FROM ...
) - 这可以是 JSON 对象:
--select-star="JSON_OBJECT(id,k,c,pad)"
- 这可以是 JSON 数组:
--select-star="JSON_ARRAY(id,k,c,pad)"
- 等等…
- 这可以是其他列的列表,例如:
--rnd-loop=N
:使用随机循环代替 rand():0=默认,1=块,2=移位,3=循环(默认:0)--update-range-size=N
:允许范围更新(默认情况下是单行更新)--load-mode=name
:初始数据加载模式 [original, parallel, parallel_ordered](默认:parallel)--load-bulk-size=N
:初始数据加载时使用的批量插入的行数(默认:1000)--trx-retry=on/off
:在发生错误/回滚/死锁时重新执行相同的事务(默认:off)--mysql-session-options=...
:额外的会话选项- 例如:
--mysql-session-options="set session sort_buffer_size=1000; set session join_buffer_size=256000"
- 实际上可以在此处添加任何 SQL 语句,通过
;
分隔,它们将在每个用户连接开始时执行
- 例如:
--mysql-query-hint=...
:在所有查询中使用给定的查询提示- 例如:
--mysql-query-hint="MAX_EXECUTION_TIME(100)"
- 例如:
--sleep-before-commit=N
:用于更高级的测试场景的额外选项,在 COMMIT 之前睡眠 N 微秒以模拟更长的数据锁定(默认:0)--sleep-after-query=N
:用于更高级的测试场景的额外选项,在每个查询之后睡眠 N 微秒以模拟更长的用户“思考时间”(默认:0)--extra-query-before="..."
:在每个事件/事务之前执行额外的查询--extra-query-after="..."
:在每个事件/事务之后执行额外的查询- 示例:
--extra-query-before="select WAIT_FOR_EXECUTED_GTID_SET( @@global.gtid_executed );"
- 示例:
你可以将这些选项作为扩展参数添加到脚本中,例如:
$ time bash /BMK/sb_exec/sb11-Prepare_10M_8tab-InnoDB.sh 32 \
--rnd-data=50 --mysql_table_compression=lz4
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-ps-trx.sh $users 300 \
--rnd-data=50 --mysql-table-compression=lz4
sleep 15
done
重新连接工作负载
历史上,总是需要测试那些不断重新连接到 MySQL 服务器的 OLTP 工作负载(例如,Web 服务器执行 PHP 查询并在页面生成后断开连接等)。过去,这类评估的真实需求主要是了解我们能在 MySQL 上每秒完成多少次重新连接——例如,目标是:
- 连接
- 执行快速 SELECT
- 断开连接
对于这种测试场景,我们使用的是点查询工作负载,并在每次 SELECT 查询后重新连接。为了满足这一需求,我实现并添加了一个明确的 Lua 脚本,该脚本执行点查询并进行重新连接,并提供相应的 Shell 脚本(可能会在以后添加更多的重新连接工作负载,先看看情况…)
例如,要开始 10Mx8tab 数据集的点查询重新连接:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-OLTP_RO_10M_8tab-uniform-p_sel1-reconnect-notrx.sh $users 300
sleep 15
done
重要提示
- 在重新连接工作负载中使用预处理语句没有意义,这个选项将被忽略
- 确保在你的“客户端”主机上启用 TCP 复用,以避免用尽套接字:
$ sudo sysctl net.ipv4.tcp_tw_reuse=1
TPCC
Percona 团队已将 TPCC 工作负载移植到 Sysbench-1.1(Lua 脚本),这大大简化了对该工作负载的进一步调查和代码修改,以检查各种测试条件。由于测试是通过 Sysbench 执行的,因此使用步骤与其他 Sysbench 脚本完全相同(比历史上使用的 DBT2 开源实现的 TPCC 要简单得多)。
最终,这使得能够提出一个“解决方法”,绕过“原始” TPCC 实现中涉及的过多索引锁争用,并将其作为测试用例的一部分——有关更多细节,请参见此文章:MySQL 8.0 TPCC 谜题
到目前为止,你可以使用这两种变体——在查询中使用 NULL 或 DEFAULT 值。
注意:要使用 NULL 变体,你需要明确使用脚本名称中包含 “-NULL” 的脚本,这不是默认的。
准备 TPCC 数据集
TPCC 数据集的“大小”以创建的“数据仓库”(DWH)数量来衡量。这个数字用 W
来表示(例如,1000W 意味着 1000 个数据仓库,依此类推)……
历史上,MySQL 在 TPCC 工作负载上的扩展性并不好,为了减少 MySQL 内部的争用,但仍然运行 TPCC 工作负载,可以在同一个 MySQL 服务器实例上并行运行多个 TPCC 测试(这至少可以查看在内部争用被修复后 MySQL 是否能够保持更高的负载)。在 Sysbench-TPCC 脚本中实现了相同的方法,但方式更加简单——你可以简单地使用来自同一个 Sysbench 进程的多个数据集(就像在同一个 MySQL 实例中有多个 TPCC 数据库一样,TPCC 会并行使用它们)。
最常见的用例是使用 1 或 10 个 TPCC 数据集。
要准备一个 1000W 数据集的单个 TPCC 数据集,执行以下命令:
$ time bash /BMK/sb_exec/sb11-Prepare_TPCC_1000W-InnoDB-NoFK.sh 32
要准备 10 个 TPCC 数据集,每个数据集为 100W(总数据量为 1000W),执行以下命令:
$ time bash /BMK/sb_exec/sb11-Prepare_TPCC_10x100W-InnoDB-NoFK.sh 32
几点说明:
NoFK
—— 历史上大多数 TPCC 工作负载的常见用法是“没有外键”(FK),你可以在脚本名称中跳过NoFK
来测试“带外键”情况NoFK2
—— 与NoFK
相同,但也不创建用于 FK 的二级索引NULL
—— (当脚本名称中包含时)表示
使用 NULL 值(如原始 TPCC),否则在数据库模式和查询中使用 DEFAULT 值,这样可以减少索引锁争用并提高整体扩展性(可以轻松地为任何生产工作负载进行适配)
例如,如果你确实需要使用 NULL 值:
$ time bash /BMK/sb_exec/sb11-Prepare_TPCC_1000W-NULL-InnoDB-NoFK.sh 32
注意:通常情况下,使用小于 1000W 的数据集来测试 TPCC 工作负载是没有意义的!——从一开始就采用 1000W(或更多)数据集进行测试,以免浪费时间并基于无用的结果得出结论……然而,如果你的目标是重现具有高“数据并发性”的测试工作负载(例如,许多用户将争夺访问相同数据),那么使用较小的数据仓库数量是完全合理的!——例如,使用 100W 数据集,你就能得到这个效果!——所以,这里的主要信息是“始终理解你在做什么”,然后一切都会很顺利 😉)
运行 TPCC 工作负载
最终,整个过程中的最简单步骤是:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-TPCC_1000W.sh $users 300
sleep 15
done
或者对于 10x100W 数据集:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024
do
bash /BMK/sb_exec/sb11-TPCC_10x100W.sh $users 300
sleep 15
done
注意:只需确保在脚本中使用与最初的 “Prepare” 脚本一致的 “NULL 或非 NULL” 命名,以及数据集大小,那么一切都会顺利进行 😉)
“原始” TPCC 工作负载的新扩展
我为 “原始” TPCC 脚本添加了一些新的扩展:
--trx-retry=on/off
:在回滚或错误时重新执行相同的事务(默认:off)--trx-debug=N
:调试模式 - 只执行 5 个事务中的一个(--trx-debug=[1-5]
),(默认:0(关闭))--use-fk=N
:使用外键 – 0:不使用,但使用二级索引,1:使用并使用二级索引,2:不使用(默认:1)--for-update=N
:使用 FOR UPDATE – 0:不使用,1:使用,2:强制使用(默认:1)--force-primary=0/1
:强制在某些查询中使用主键索引 – 0:不使用,1:使用(默认:0)--mysql-table-compression=name
:额外的表透明压缩选项,例如:lz4
--mysql-session-options=...
:额外的会话选项- 例如:
--mysql-session-options="set session sort_buffer_size=1000; set session join_buffer_size=256000"
- 实际上可以在此处添加任何 SQL 语句,用
;
分隔,这些语句将在每个用户连接开始时执行
- 例如:
与 “原始” Sysbench 工作负载一样,你可以将这些选项作为扩展参数添加到测试场景脚本中。
dbSTRESS
这个基准测试用例有着悠久的历史,并且基于真实的客户工作负载。令人惊讶的是,过去它帮助揭示了 MySQL / InnoDB 设计中的许多深层问题。然而,随着时间的推移,系统和 MySQL 本身变得更快, 旧版的实现 开始变得过于缓慢和低效。因此,一旦 Sysbench-1.1 发布,我计划将 dbSTRESS 工作负载移植到 Sysbench 中,现在终于完成了!😉)
dbSTRESS 中的数据库模式仅由 5 个表组成,模拟图书馆(或库存)对象的放置:
- STAT
- HISTORY
- OBJECT
- SECTION
- ZONE
这些表之间的关系(“通过引用 ID”)如下:
[STAT] <--(1:M)-- [HISTORY] --(20:1)--> [OBJECT] --(N:1)--> [SECTION] --(P:1)--> [ZONE]
对于每个 OBJECT 记录,HISTORY 表中有 20 条记录(表示过去 20 次更改的历史轨迹)。而 STAT、SECTION 和 ZONE 表中的记录数量是固定的:
- STAT:1000 行
- SECTION:100 行
- ZONE:10 行
STAT 记录作为 HISTORY 记录的“标签”(或状态)使用。每条 HISTORY 记录的“标签”可能与其他记录不同,且任何 HISTORY 记录可以随着时间的推移改变其“标签”。
然后,SECTION 和 ZONE 记录定义了 OBJECT 的“放置”——共有 10 个 ZONE,每个 ZONE 包含 10 个 SECTION,总共 100 个 SECTION。每个 OBJECT 都属于这些 SECTION 中的一个。任何给定的 OBJECT 记录都不能更改其“放置”。
考虑到 STAT、SECTION 和 ZONE 表的固定大小,以及 OBJECT 和 HISTORY 之间的固定 1:20 关系——显然,数据库的大小取决于你创建的 OBJECT 数量。
不同表之间的所有关系通过各自的“引用 ID”来管理(通常引用“父级”主键)。历史上,为了简化 RDBMS 比较,没有使用外键或其他约束。目前,我保持这种方式,但将来可能会添加一个选项来启用外键(开/关),我们拭目以待。
与其他 Sysbench 工作负载类似,你还可以创建多个 dbSTRESS “实例”,以便“分散”锁定到多个 OBJECT 表及其对应的表。
准备数据集
这里的逻辑与其他工作负载类似。例如,要准备一个包含 10M OBJECT 的数据集,你可以执行以下命令:
$ time bash /BMK/sb_exec/sb11-Prepare_dbSTRESS_10M-InnoDB.sh 32
或者,要准备 10 个 OBJECT 表,每个表 1M 行:
$ time bash /BMK/sb_exec/sb11-Prepare_dbSTRESS_10x1M-InnoDB.sh 32
稍微再深入一点,我需要提到,如果你在 HISTORY 表中创建了主键索引,你可能会遇到不同的瓶颈(默认情况下没有主键索引,就像许多应用程序中那样)。我让你自己去发现为什么会有这样的影响,但对于使用主键索引的测试架构,你可以使用名称中带有 “-HPK” 的脚本,如:
$ time bash /BMK/sb_exec/sb11-Prepare_dbSTRESS_10M-InnoDB-HPK.sh 32
测试场景
dbSTRESS 生成 OLTP 工作负载,最大程度地压测数据库。工作负载的性能水平主要通过 TPS(每秒事务数)来衡量,但你也可以选择每个“事务”中执行的查询数量,因此任何结果都应与其场景上下文一起呈现,以便有意义(QPS 也是如此)。在任何给定的事务中,我们首先随机选择一个 OBJECT 引用,然后执行读操作或读写操作:
读取操作包括 2 个 SELECT 查询:
- SEL1:通过给定的 OBJECT ID 读取相关的
OBJECT --> SECTION --> ZONE
数据 - SEL2:通过给定的 OBJECT ID 读取相关的
HISTORY --> STAT
数据
写入操作包括:
- 删除一个 HISTORY 记录,并为给定的 OBJECT 插入新的 HISTORY 记录
- 更新给定 OBJECT 的一个 HISTORY 记录
注意:删除和插入的顺序总是一起执行,如果在写入操作中执行。这样做是为了防止数据库“故意”不断增长。然而,如果在 dbSTRESS 工作负载期间,你遇到 InnoDB 清除延迟,你可能会遇到数据库大小“非故意”增长的问题,这是由于 UNDO 空间的增加。
此外,你可以调整 dbSTRESS 事务中只有读取操作或同时进行读取和写入操作的比例——这使得测试用例可以更加或更加激进地进行数据写入。
默认情况下,提供以下测试场景:
sb11-dbSTRESS_10M-RO-notrx.sh
:只读,包含 SEL1 和 SEL2 查询sb11-dbSTRESS_10M-RO-SEL1-notrx.sh
:只读,仅包含 SEL1 查询sb11-dbSTRESS_10M-RO-SEL2-notrx.sh
:只读,仅包含 SEL2 查询sb11-dbSTRESS_10M-RW1-trx.sh
:读+写,读写比 1:1sb11-dbSTRESS_10M-RW10-trx.sh
:读+写,读写比 10:1sb11-dbSTRESS_10M-RW1-UPD-trx.sh
:读+写,但写入仅为 UPDATE,读写比 1:1sb11-dbSTRESS_10M-RW10-UPD-trx.sh
:读+写,但写入仅为 UPDATE,读写比 10:1
注释:
- 希望显而易见的是,脚本名称中的
10M
表示 10M 个 OBJECT 记录。 RO
:只读,如果添加了SEL1
或SEL2
,则表示仅使用给定的 SELECT 查询。trx
或notrx
:表示是否使用事务(BEGIN/COMMIT)。RW
表示读+写,后跟读写比(RW1
= 1:1,RW10
= 10:1,依此类推)。- 如果名称中包含
UPD
,则仅在写入操作中执行 UPDATE 查询。
为了让它更复杂:
xDEAD
:不使用 ID 范围中的“防死锁”限制。xREF
:允许在同一事务中使用不同的 OBJECT ID 来执行读取/写入操作。
dbSTRESS工作负载示例
准备10M数据集:
$ time bash /BMK/sb_exec/sb11-Prepare_dbSTRESS_10M-InnoDB.sh 32
运行只读工作负载:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RO-notrx.sh $users 300
sleep 15
done
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RO-SEL1-notrx.sh $users 300
sleep 15
done
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RO-SEL2-notrx.sh $users 300
sleep 15
done
运行读写工作负载:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RW1-trx.sh $users 300
sleep 15
done
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RW10-trx.sh $users 300
sleep 15
done
完整的dbSTRESS选项(Sysbench)
在使用上述脚本时,或者直接通过Sysbench执行dbSTRESS脚本时,可以添加其他选项:
--obj-table-size=num
: OBJECT表中的行数(默认:1000000)--obj-tables=num
: OBJECT表的数量(默认:1)--mysql-storage-engine=name
: 使用的存储引擎(默认:InnoDB)--mysql-table-options=ops
: 附加的表选项,例如:‘organization=heap’--ordered-load=on/off
: 是否按主键顺序加载数据(默认:off)--SEL1=num
: 每个事务中的SEL1查询数量(默认:1)--SEL2=num
: 每个事务中的SEL2查询数量(默认:1)--updates=num
: 每个事务中的UPDATE查询数量(默认:1)--delete-inserts=num
: 每个事务中的DELETE/INSERT组合数量(默认:1)--rw-ratio=num
: 只读(RO)和读写(RW)事务的比例(默认:1)--hpk=on/off
: 是否使用PRIMARY KEY作为历史表(默认:off)--anti-dead=on/off
: 防止死锁,每个会话使用自己的REF范围(默认:on)--same-ref=on/off
: 是否在同一事务中使用相同的OBJECT REF值(默认:on)--skip-trx=on/off
: 是否在AUTOCOMMIT模式下执行所有查询(默认:off)--for-update=on/off
: 是否在所有SELECT查询中使用FOR UPDATE(默认:off)--mysql-table-compression=name
: 附加的表透明压缩选项,例如:‘lz4’--mysql-session-options=...
: 附加的会话选项- 示例:
--mysql-session-options="set session sort_buffer_size=1000; set session join_buffer_size=256000"
- 实际上,可以使用任何类型的SQL语句,通过
;
分隔,这些语句将在每个用户连接开始时执行。
例如,如果我想强制默认的读写测试在所有SELECT查询中使用FOR UPDATE,我可以在参数中添加--for-update=on
:
$ for users in 1 2 4 8 16 32 64 128 256 512 1024 2048
do
bash /BMK/sb_exec/sb11-dbSTRESS_10M-RW1-trx.sh $users 300 --for-update=on
sleep 15
done
SYNC_file模块
SYNC_file.lua
模块是BMK-kit中新增的,用于提供一种简单的方法来同步多个线程、多个Sysbench进程或多个远程客户端主机的启动——所有这些都期望在相同的时间启动指定的测试。
此模块集成到所有上述测试工作负载中,具体操作如下:
- 对于任何测试脚本,您可以提供附加选项
--sync-file=filename
- 注意:此“filename”应为文件的完整路径
- 如果使用此选项,测试脚本将在所有Sysbench线程初始化完成并连接到MySQL服务器后创建指定文件
- 然后,所有Sysbench线程将进入等待状态,直到该同步文件被删除。一旦文件被删除,所有线程将被释放并开始执行!
- 默认情况下,线程会每10毫秒检查一次文件
- 但是,在小型系统上启动许多线程可能会导致较高的CPU使用率
- 您可以通过
--sync-wait=N
(N为毫秒)来更改默认的等待时间
我们来看一个例子,使用512个用户运行TPCC-1000W工作负载,通过/tmp/start
文件进行同步,等待时间为20毫秒:
$ bash /BMK/sb_exec/sb11-TPCC_1000W.sh 512 300 --sync-file=/tmp/start --sync-wait=20
...
=> SYNC-file : synchronization via /tmp/start
=> SYNC-file : ready to start..
- 首先,您会看到确认消息,表示同步文件已被识别并正在使用
- 当所有512个线程完成初始化并且都已连接到MySQL服务器时,您将看到“ready to start”的消息
- 此时,所有Sysbench线程都会循环等待,直到同步文件被删除
一旦同步文件被删除,测试将开始,您将看到以下确认消息:
=> SYNC-file : started !
关于测试工作负载的常见注意事项
有一些重要的点值得提及,因为其中一些可能与不同的人产生分歧,而其他一些则是避免考虑“假阳性”或“虚假”结果时需要特别注意的事项。
单表与多表
关于这个话题的意见与讨论不计其数,几乎每个人都有不同的看法——甚至有一些声音大声疾呼,认为TPCC工作负载(仅举例)是“过时”的,因为它没有使用像今天许多生产系统中那样的数百张表。你也能看到很多人运行Sysbench时使用数百张小表等。
那么,为什么这些说法对我来说没有意义?
- 首先,你需要始终记住你进行基准测试的“目的”是什么。
- 如果你的目标是重现生产场景 => 那么就专注于这个目标,并尽可能精确地重现它以匹配你的生产环境。
- 但如果你是在运行“通用”的工作负载并已经准备好的场景,那么首先要理解每个测试用例。
- 因为每个“通用”工作负载的目的是指示潜在的问题,或者帮助你评估数据库引擎、配置设置以及你使用的操作系统/硬件堆栈。
- 任何单表工作负载的目标都是让你看到如果生产环境中的某一张表变得“热门”,并且被所有活跃用户同时并发访问时,会发生什么。
- 而多表工作负载则是展示如果你能够将用户分配到多个表中,或者迫使他们使用相同表的不同分区等方式,整体处理情况会如何变化。
- 然而,并不是所有用于基准测试的场景都真正遵循这种“分表”策略——许多时候,表的选择可能是完全随机的,这样一来,工作负载可能会周期性地“随机转换”成单表负载或多表负载等……为了避免这种“意外”,我在我的所有脚本中都会明确“分配”用户到特定的表(例如,如果你在8张表上运行64个用户的OLTP_RW负载,系统会将64个用户分成8组,每组只使用一个特定的表)。
IO绑定型工作负载
奇怪的是,在IO绑定型工作负载中,很容易得到“假”或“假阳性”结果。你需要非常小心你的操作和测试过程。首先,始终记得要测试所有负载级别,而不仅仅是其中的一个(就像Percona的团队经常做的那样)。
单表上的IO绑定:
- 单表IO绑定的最大问题是历史性的文件IO锁。
- 你可以在MySQL 8.0中看到
fil_shard
互斥锁的争用,在早期版本中则是fil_sys
的争用。 - 从MySQL 8.0开始,这种历史性的全局锁被“分片”(所以,在多表IO绑定中,你就有很小的机会观察到这种争用)。
- 然而,在早期版本中,这是每次文件IO操作都会涉及的全局锁,可能会迅速对性能产生影响,而即使你有更快的存储也无法解决这个问题。
注意:如果你将所有表放在同一个文件(表空间)中,即使是MySQL 8.0版本,多个表的IO绑定也可能会遇到相同的问题——这就是为什么需要为高IO活动的表使用专用文件的原因!
多表上的IO绑定:
- 多表IO绑定负载的最大问题是获得虚假结果。
- 如何识别结果是否是“虚假”?
- 首先,要仔细查看你的IO流量!——如果你看到TPS上升,但IO活动没有变化,那就有很大的可能性你的结果是虚假的。
- 为什么会发生这种情况?
- 在多表工作负载中,很容易让某张表在InnoDB缓存池中被“缓存”得更好,因此对该表的所有查询将从内存中读取,执行速度远快于其他表,这样会误导你,让你认为性能有所提升,然而实际上并非如此;-)
- 为了检查性能是否真正得到提升,可以尝试在单表上重放相同的工作负载和相同的数据量——这将给你最终的判断。
- 还需要测试多个并发用户的负载级别,以便更全面地了解整体情况(例如,1、2、4……1024个用户)。
一个“经典”的虚假IO绑定OLTP_RW结果的例子如下所示:
- 你可以看到在128个用户时,TPS有了显著的“提高”!
- 然而,这个TPS结果是“虚假”的。
- 你可以明显看到,在128个用户的负载下,IO活动显示的是完全相反的趋势;-)
- 同时,测试其他负载级别也很重要,因为你会更加清楚地看到128个用户的TPS结果与整体趋势不符。
由于我收到了一些关于“虚假”IO绑定结果的更多问题:
- 首先——记住数据访问模式是“均匀”的。
- 并且数据集的大小远大于缓存池的大小。
- 这意味着大多数页面访问将涉及IO读取的概率非常高。
- 同时,这个负载是OLTP_RW型的(即我们也有写操作)。
- 这意味着我们会迅速产生很多脏页。
- 因此,在读取新页面之前,我们需要在缓存池中找到一个空闲的页面。
- 要获得空闲页面,我们需要从LRU(最近最少使用)链表中逐出一个页面,而逐出页面之前必须先刷新它。
- 也就是说,这一切都与IO活动相关。
- 这里的主要目标是“IO绑定的正确性”,例如,我们可能是在评估新的存储供应商,或者某些特殊的文件系统特性等。
- 如果“正确性”无法得到确认——那么这个结果就是“虚假的”(或者如果你愿意,称它为“假阳性”;-))
现在:
- 我们可以看到,在某些时刻,TPS大幅上升,但IO活动却减少了……
- 如果我们期望进行的是一个重度IO绑定的测试,为什么会发生这种情况?
- 因为如果我们处理更高的TPS率,那么脏页的数量应该会更多,对吧?
- 而且,访问模式是“均匀的”,例如,命中相同数据的概率很低!
- 我们可以怀疑,在更高的负载级别下,可能有更多的用户,因此“潜在地”已经缓存的数据会被重复使用,从而使TPS变得更高,为什么不呢?
- 好吧,但更多的变化意味着更多的IO写入,至少也会有类似的IO读取,但情况并非如此……
- 那么,为什么在下一个负载级别,TPS反而下降了??增加了更多用户后,“使用缓存数据”的概率应该更高才对,怎么回事?
- 其实,TPS增长的主要原因是:
- 我们使用了8张表,并且在某个时刻,其中一张表在缓存池中的缓存比其他表要多。
- 因此,与这张表交互的用户将获得更高的提交速率。
- 这样,整体TPS就会更高。
- 而与缓存较少的表交互的用户将会有较低的TPS。
- 这一现象在过去很容易观察到,因为Sysbench只能处理1张表,而要处理8张表,我们需要启动8个Sysbench进程并行运行。
- 总而言之,在这种情况下,IO绑定的“正确性”没有得到尊重,因此给出的TPS结果是“虚假的”,如果目标是评估存储或与IO相关的文件系统特性,这个结果应该被忽略。
IP端口与UNIX套接字
一般来说,如果使用UNIX套接字而非IP端口(即使是“回环”接口用于本地主机),在任何测试工作负载中得到的结果通常会更好——这一点对Linux系统无论是Intel/AMD/x64架构还是ARM64架构都成立。而且,结果差距可能超过30%!——例如,见此文:MySQL性能对比:IP端口与UNIX套接字的影响
目前,只有“原始”Sysbench工作负载中包含“socket”名称的测试脚本,因为这些脚本代表了最具攻击性的测试。我认为TPCC或dbSTRESS不需要这样做,但如果这些工作负载也能看到一些性能提升,未来可以做出相应的调整。
注意:仅当你在同一系统上“本地”测试MySQL服务器时,使用“回环”或UNIX套接字才有意义。但如果你的生产环境不是“本地”运行的呢?——如果不是,那么也需要测试通过真实网络进行通信的情况,以确保这不会成为MySQL的主要瓶颈。
何时在Sysbench中使用均匀(Uniform)/帕累托(Pareto)/Zipfian访问模式
再次提醒,始终记住你究竟想要测试什么,然后根据需求选择合适的“随机”选项:
均匀(Uniform):完全随机的数据访问,因此不期望并发访问相同的数据。
- 适用于对整体配置和使用的硬件进行一般评估。
- 也非常适合IO绑定工作负载的模拟,即使数据集不大,也能给出一些具有代表性的结果,帮助评估存储、文件系统等。
- 由于这种访问方式大多是“可预测”的,因此能够帮助你更好地理解系统的限制、调优设置等。
帕累托(Pareto):与“均匀”访问模式相反,会导致数据的并发访问!
- 非常适合评估数据库引擎管理数据锁定的能力。
- 还可以检测死锁及数据库在数据热点锁定条件下继续执行的能力。
- 特别有趣的是,可以测试
--trx-retry=on
选项,查看数据库引擎在回滚后如何处理尝试重放相同事务的用户(有些引擎在初期阶段跳过死锁检测并中止活动事务,因此,如果用户使用不同数据重新启动新事务 => 这会使得总体TPS看起来更高,但这对于实际应用程序可能不公平,因为用户会一次次地重试相同的事务)。
Zipfian:与“帕累托”类似,只是数据并发性较低。
- 适用于你想了解数据库引擎在非所有数据库数据都持续访问的情况下的性能表现,而是部分数据被访问的情况。
- 如果整个数据库数据都缓存到内存中,这种模式的影响不大。
- 但在IO绑定条件下,会完全不同:
- 你可以评估存储是否“足够好”,即大多数访问的数据能够适应内存(缓存池大小)。
- 或者,你可能需要将最常访问的数据放置在更快的存储中。
- 等等。
总结
为了总结上面所有提到的常见注意事项,我们来看看以下几种选择组合如何影响最终的OLTP_RW工作负载性能:
- 随机模式:
- 均匀(Uniform)
- 帕累托(Pareto)
- 特殊(原始Sysbench中的默认模式)
- 高斯(Gaussian)
- Zipfian
- 场景:
- 单表(Single-Table)
- 多表(Multi-Table)
- 工作负载条件:
- 内存中(无IO读取)
- 部分IO绑定(超过60%的数据可以缓存到BP中)
- IO绑定(少于33%的数据可以缓存到BP中)
内存中
OLTP_RW,10Mx8tab(20GB):
OLTP_RW,50Mx1tab(12.5GB):
- 有趣的是,唯一真正影响结果的随机模式是“帕累托”。
- 当使用单表时,与8表的性能差距更为显著。
不过,这些工作负载都在内存中。那IO绑定的情况呢?
部分IO绑定 | BP大小=64G
OLTP_RW,50Mx8tab(100GB):
OLTP_RW,400Mx1tab(100GB):
强IO绑定 | BP大小=32G
OLTP_RW,50Mx8tab(100GB):
OLTP_RW,400Mx1tab(100GB):
希望这些内容现在能让你清楚明了?——祝你测试顺利;-)
Sysbench 输出
如果你之前从未见过Sysbench的结果输出,这里有几个示例。首先,所有上述工作负载的默认输出“格式”是完全相同的,这对于理解所获得的结果非常有帮助;-)
通常,一旦测试开始,你将看到相应的启动信息,测试结束时,Sysbench会报告一个结果摘要,类似于以下这个1024用户的OLTP_RW结果:
sysbench 1.1.0 (using bundled LuaJIT 2.1.0-beta3)
Running the test with following options:
Number of threads: 1024
...
Threads started!
...
SQL statistics:
queries performed:
read: 15985872
write: 4567392
other: 2283696
total: 22836960
transactions: 1141848 (3801.40 per sec.)
queries: 22836960 (76028.07 per sec.)
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
Throughput:
events/s (eps): 3801.4034
time elapsed: 300.3754s
total number of events: 1141848
Latency (ms):
min: 19.91
avg: 269.10
max: 4241.68
95th percentile: 493.24
sum: 307269899.28
Threads fairness:
events (avg/stddev): 1115.0859/17.93
execution time (avg/stddev): 300.0683/0.02
最重要的结果摘要指标包括:
- QPS(每秒查询数,例如上面示例中的76K)
- TPS(每秒事务数,例如上面的3.8K)
- 查询响应时间:
- 平均(上面为269ms)
- 95% 百分位(上面为493ms)
- 最大(上面为4.2秒!)
这些指标通常足以让你对测试性能有一个初步了解:
- QPS和TPS告诉你处理吞吐量
- 而QPS与TPS的比率则提供了每个事务大约执行了多少个查询
- (在上面的例子中,每个事务大约执行20个查询)
- 响应时间则反映了整体处理的稳定性:
- 在上面的例子中,95%的响应时间比最大响应时间低了大约10倍
- 这意味着最大响应时间是非常特殊的,且不常见
- 另一方面,95%的响应时间几乎是平均响应时间的两倍
- 这意味着大多数响应时间都接近平均值
- 所以,整体处理相当稳定,唯一需要确认的是,493ms的响应时间在特定情况下是否可以接受
然而,个人而言,我更感兴趣的是实时看到Sysbench当前运行的测试工作负载的结果统计。这个功能可以通过使用--report-interval=N
选项来启用,其中N
表示报告时间间隔(以秒为单位)。
例如,使用OLTP_RW:
$ bash /BMK/sb_exec/sb11-OLTP_RW_10M_8tab-uniform-trx.sh 256 300 --report-interval=1
这将使你在运行期间看到如下输出:
[ 1s ] thds: 1024 tps: 3148.59 qps: 77199.23 (r/w/o: 56873.00/13033.03/7293.21) lat (ms,95%): 549.52 err/s: 0.00 reconn/s: 0.00
[ 2s ] thds: 1024 tps: 3972.10 qps: 81953.76 (r/w/o: 56304.64/17705.92/7943.21) lat (ms,95%): 442.73 err/s: 0.00 reconn/s: 0.00
[ 3s ] thds: 1024 tps: 4359.29 qps: 84721.70 (r/w/o: 60014.04/15987.07/8720.59) lat (ms,95%): 442.73 err/s: 0.00 reconn/s: 0.00
[ 4s ] thds: 1024 tps: 4057.11 qps: 81225.16 (r/w/o: 57150.52/15964.43/8110.22) lat (ms,95%): 419.45 err/s: 0.00 reconn/s: 0.00
[ 5s ] thds: 1024 tps: 4410.87 qps: 81122.65 (r/w/o: 54418.42/17899.48/8804.74) lat (ms,95%): 475.79 err/s: 0.00 reconn/s: 0.00
[ 6s ] thds: 1024 tps: 3577.92 qps: 79383.33 (r/w/o: 58109.78/14097.70/7175.85) lat (ms,95%): 434.83 err/s: 0.00 reconn/s: 0.00
[ 7s ] thds: 1024 tps: 4176.04 qps: 81865.78 (r/w/o: 55109.52/18416.18/8340.08) lat (ms,95%): 419.45 err/s: 0.00 reconn/s: 0.00
[ 8s ] thds: 1024 tps: 4243.74 qps: 84787.79 (r/w/o: 61201.24/15087.07/8499.48) lat (ms,95%): 411.96 err/s: 0.00 reconn/s: 0.00
[ 9s ] thds: 1024 tps: 4476.26 qps: 85672.03 (r/w/o: 58498.43/18239.07/8934.52) lat (ms,95%): 356.70 err/s: 0.00 reconn/s: 0.00
[ 10s ] thds: 1024 tps: 4072.91 qps: 83152.23 (r/w/o: 59076.74/15911.66/8163.83) lat (ms,95%): 390.30 err/s: 0.00 reconn/s: 0.00
...
- 你可以从中看到当前的QPS和TPS
- 95%的查询响应时间
- 如果有错误的话,也可以看到
个人建议,你应该始终以实时统计的方式执行任何Sysbench工作负载,因为:
- 如果你的测试因某些意外原因在最后崩溃,你将无法看到总结
- 但通过实时统计输出,你仍然可以大致了解最终结果
- 此外,我总是使用1秒的间隔来进行实时报告
- 这样可以帮助你发现测试过程中是否存在停顿
- 因为生产环境中的停顿可能会产生比其他问题更大的负面影响!——一定要记住这一点。