如何从入门到自定义sysbench测试项进行MySQL性能压测?

摘要:sysbench是一个开源的、基于LuaJIT(LuaJIT 是 Lua 的即时编译器,可将代码直接翻译成机器码,性能比原生 lua 要高) 的、可自定义脚本的多线程基准测试工具,也是目前用得最多的 MySQL 性
sysbench是一个开源的、基于LuaJIT(LuaJIT 是 Lua 的即时编译器,可将代码直接翻译成机器码,性能比原生 lua 要高)的、可自定义脚本的多线程基准测试工具,也是目前用得最多的 MySQL 性能压测工具。 基于 sysbench,我们可以对比 MySQL 在不同版本、不同硬件配置、不同参数(操作系统和数据库)下的性能差异。 下面会从 sysbench 的基本用法出发,逐渐延伸到 sysbench 的一些高级玩法,譬如如何阅读自带的测试脚本、如何自定义测试项等。除此之外,使用 sysbench 对 CPU 进行测试,网上很多资料都语焉不详,甚至是错误的,所以这次也会从源码的角度分析 CPU 测试的实现逻辑及 --cpu-max-prime 选项的具体含义。 本文主要包括以下几部分: 安装sysbench sysbench用法讲解 对MySQL进行基准测试的基本步骤 如何分析MySQL基准测试结果 如何使用sysbench对服务器进行测试 MySQL常见测试场景及对应的 SQL 语句 如何自定义sysbench测试脚本 安装 sysbench 下面是 sysbench 源码包的安装步骤。 #yum-yinstallmakeautomakelibtoolpkgconfiglibaio-developenssl-develmysql-devel #cd/usr/src/ #wgethttps://github.com/akopytov/sysbench/archive/refs/tags/1.0.20.tar.gz #tarxvf1.0.20.tar.gz #cdsysbench-1.0.20/ #./autogen.sh #./configure #make-j #makeinstall 安装完成后,压测脚本默认会安装在/usr/local/share/sysbench目录下。 我们看看该目录的内容。 #ls/usr/local/share/sysbench/ bulk_insert.luaoltp_insert.luaoltp_read_write.luaoltp_write_only.luatests oltp_common.luaoltp_point_select.luaoltp_update_index.luaselect_random_points.lua oltp_delete.luaoltp_read_only.luaoltp_update_non_index.luaselect_random_ranges.lua 除了oltp_common.lua是个公共模块,其它每个 lua 脚本都对应一个测试场景。 sysbench 用法讲解 sysbench 命令语法如下: sysbench[options]...[testname][command] 命令中的testname是测试项名称。sysbench 支持的测试项包括: *.lua:数据库性能基准测试。 fileio:磁盘 IO 基准测试。 cpu:CPU 性能基准测试。 memory:内存访问基准测试。 threads:基于线程的调度程序基准测试。 mutex:POSIX 互斥量基准测试。 command是 sysbench 要执行的命令,支持的选项有:prepare,prewarm,run,cleanup,help。注意,不是所有的测试项都支持这些选项。 options是配置项。sysbench 中的配置项主要包括以下两部分: 1. 通用配置项。这部分配置项可通过sysbench --help查看。例如, #sysbench--help ... Generaloptions: --threads=Nnumberofthreadstouse[1] --events=Nlimitfortotalnumberofevents[0] --time=Nlimitfortotalexecutiontimeinseconds[10] ... 2. 测试项相关的配置项。各个测试项支持的配置项可通过sysbench testname help查看。例如, #sysbenchmemoryhelp sysbench1.0.20(usingbundledLuaJIT2.1.0-beta2) memoryoptions: --memory-block-size=SIZEsizeofmemoryblockfortest[1K] --memory-total-size=SIZEtotalsizeofdatatotransfer[100G] --memory-scope=STRINGmemoryaccessscope{global,local}[global] --memory-hugetlb[=on|off]allocatememoryfromHugeTLBpool[off] --memory-oper=STRINGtypeofmemoryoperations{read,write,none}[write] --memory-access-mode=STRINGmemoryaccessmode{seq,rnd}[seq] 对 MySQL 进行基准测试的基本步骤 下面以oltp_read_write为例,看看使用 sysbench 对 MySQL 进行基准测试的四个标准步骤: prepare 生成压测数据。默认情况下,sysbench 是通过 INSERT INTO 命令来导入测试数据的。如果是使用LOAD DATA LOCAL INFILE 命令来导入,sysbench 导数速度能提升30%,具体可参考:使用 LOAD DATA LOCAL INFILE,sysbench 导数速度提升30% #sysbencholtp_read_write--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=30prepare 命令中各个选项的具体含义如下: oltp_read_write:测试项,对应的是/usr/local/share/sysbench/oltp_read_write.lua。这里也可指定脚本的绝对路径名。 --mysql-host、--mysql-port、--mysql-user、--mysql-password:分别代表 MySQL 实例的主机名、端口、用户名和密码。 --mysql-db:库名。不指定则默认为sbtest。 --tables :表的数量,默认为 1。 --table-size :单表的大小,默认为 10000。 --threads :并发线程数,默认为 1。注意,导入时,单表只能使用一个线程。 prepare:执行准备工作。 oltp_read_write 用来压测 OLTP 场景。在 sysbench 1.0 之前, 该场景是通过 oltp.lua 这个脚本来测试的。不过该脚本在 sysbench 1.0 之后就被废弃了,但为了跟之前的版本兼容,该脚本放到了/usr/local/share/sysbench/tests/include/oltp_legacy/目录下。 鉴于 oltp_read_write.lua 和 oltp.lua 两者的压测内容完全一致。从 sysbench 1.0 开始,压测 OLTP 场景建议直接使用 oltp_read_write。 prewarm 预热。主要是将磁盘中的数据加载到内存中。 #sysbencholtp_read_write--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=30prewarm 除了需要将命令设置为prewarm,其它配置与prepare中一样。 run 压测。 #sysbencholtp_read_write--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 其中, --time :压测时间。不指定则默认为 10 秒。除了 --time,也可通过 --events 限制需要执行的 event 的数量。 --report-interval=10 :每 10 秒输出一次测试结果,默认为 0,不输出。 cleanup 清理数据。 #sysbencholtp_read_write--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30cleanup 这里只需指定 --tables ,sysbench 会串行执行DROP TABLE IF EXISTS sbtest操作。 如何分析 MySQL 基准测试结果 下面我们分析下 oltp_read_write 场景下的压测结果。注:右滑可以看到每个指标的具体含义。 Threadsstarted! [10s]thds:64tps:5028.08qps:100641.26(r/w/o:70457.59/20121.51/10062.16)lat(ms,95%):17.32err/s:0.00reconn/s:0.00 # thds 是并发线程数。tps 是每秒事务数。qps 是每秒操作数,等于 r(读操作)加上 w(写操作)加上 o(其他操作,主要包括 BEGIN 和 COMMIT)。lat 是延迟,(ms,95%)是 95%的查询时间小于或等于该值,单位毫秒。err/s 是每秒错误数。reconn/s 是每秒重试的次数。 [20s]thds:64tps:5108.93qps:102192.09(r/w/o:71533.28/20440.64/10218.17)lat(ms,95%):17.32err/s:0.00reconn/s:0.00 [30s]thds:64tps:5126.50qps:102505.50(r/w/o:71756.30/20496.60/10252.60)lat(ms,95%):17.32err/s:0.00reconn/s:0.00 [40s]thds:64tps:5144.50qps:102907.20(r/w/o:72034.07/20583.72/10289.41)lat(ms,95%):17.01err/s:0.00reconn/s:0.00 [50s]thds:64tps:5137.29qps:102739.80(r/w/o:71916.99/20548.64/10274.17)lat(ms,95%):17.01err/s:0.00reconn/s:0.00 [60s]thds:64tps:4995.38qps:99896.35(r/w/o:69925.98/19979.61/9990.75)lat(ms,95%):17.95err/s:0.00reconn/s:0.00 SQLstatistics: queriesperformed: read:4276622#读操作的数量 write:1221892#写操作的数量 other:610946#其它操作的数量 total:6109460#总的操作数量,total=read+write+other transactions:305473(5088.63persec.)#总的事务数(每秒事务数) queries:6109460(101772.64persec.)#总的操作数(每秒操作数) ignorederrors:0(0.00persec.)#忽略的错误数(每秒忽略的错误数) reconnects:0(0.00persec.)#重试次数(每秒重试的次数) Generalstatistics: totaltime:60.0301s#总的执行时间 totalnumberofevents:305473#执行的event的数量 #在oltp_read_write中,默认参数下,一个event其实就是一个事务 Latency(ms): min:5.81#最小耗时 avg:12.57#平均耗时 max:228.87#最大耗时 95thpercentile:17.32#95%event的执行耗时 sum:3840044.28#总耗时 Threadsfairness: events(avg/stddev):4773.0156/30.77#平均每个线程执行event的数量 # stddev 是标准差,值越小,代表结果越稳定。 executiontime(avg/stddev):60.0007/0.01#平均每个线程的执行时间 输出中,重点关注三个指标: 每秒事务数,即我们常说的 TPS。 每秒操作数,即我们常说的 QPS。 95% event 的执行耗时。 TPS 和 QPS 反映了系统的吞吐量,越大越好。执行耗时代表了事务的执行时长,越小越好。在一定范围内,并发线程数指定得越大,TPS 和 QPS 也会越高。 使用 sysbench 对服务器进行测试 除了数据库基准测试,sysbench 还能对服务器的性能进行测试。服务器资源一般包括四大类:CPU、内存、IO和网络。sysbench 可对CPU、内存和磁盘IO进行测试。下面我们具体来看看。 cpu CPU 性能测试。支持的选项只有一个,即--cpu-max-prime。 CPU 测试的命令如下: #sysbenchcpu--cpu-max-prime=20000--threads=32run 输出中,重点关注events per second。值越大,代表 CPU 的计算性能越强。 CPUspeed: eventspersecond:25058.08 下面是 CPU 测试相关的代码,可以看到,sysbench 是通过计算--cpu-max-prime范围内的质数来衡量 CPU 的计算能力的。 质数(prime number)又称素数,指的是大于 1,且只能被 1 和自身整除的自然数。在代码实现时,对于自然数 n,一般会用 2 到根号 n 之间的整数去除,如果都无法整除,则意味着 n 是个质数。 intcpu_execute_event(sb_event_t*r,intthread_id) { unsignedlonglongc; unsignedlonglongl; doublet; unsignedlonglongn=0; (void)thread_id;/*unused*/ (void)r;/*unused*/ //max_prime即命令行中指定的--cpu-max-prime for(c=3;c<max_prime;c++) { t=sqrt((double)c); for(l=2;l<=t;l++) if(c%l==0) break; if(l>t) n++; } return0; } memory 内存测试,支持的选项有: --memory-block-size:内存块的大小,默认为 1KB。测试时建议设置为 1MB。 --memory-total-size:要传输的数据的总大小。默认为 100GB。 --memory-scope:内存访问范围,可指定 global、local,默认为 global。 --memory-hugetlb:是否从 HugeTLB 池中分配内存,默认为 off。 --memory-oper:内存操作类型,可指定 read、write、none,默认为 write。 --memory-access-mode:内存访问模式,可指定 seq(顺序访问)、rnd(随机访问),默认为 seq。 内存测试的命令如下: #sysbench--test=memory--memory-block-size=1M--memory-total-size=100G--num-threads=1run 输出中,重点关注以下部分: 102400.00MiBtransferred(23335.96MiB/sec) 23335.96 MiB/sec 即数据在内存中的顺序写入速率。 fileio 磁盘 IO 测试。支持的选项有: --file-num:需要创建的文件数,默认为128。 --file-block-size:数据块的大小,默认为16384,即16KB。 --file-total-size:需要创建的文件总大小,默认为2GB。 --file-test-mode:测试模式,可指定 seqwr(顺序写)、seqrewr(顺序重写)、seqrd(顺序读)、rndrd(随机读)、rndwr(随机写)、rndrw(随机读写)。 --file-io-mode:文件的操作模式,可指定 sync(同步 IO)、async(异步 IO)、mmap,默认为 sync。 --file-async-backlog:每个线程异步 IO 队列的长度,默认为 128。 --file-extra-flags:打开文件时指定的标志,可指定 sync、dsync、direct,默认为空,没指定。 --file-fsync-freq:指定持久化操作的频率,默认为 100,即每执行 100 个 IO 请求,则会进行一次持久化操作。 --file-fsync-all:在每次写入操作后执行持久化操作,默认为 off。 --file-fsync-end:在测试结束时执行持久化操作,默认为 on。 --file-fsync-mode:持久化操作的模式,可指定 fsync、fdatasync,默认为 fsync。fdatasync 和 fsync类似,只不过 fdatasync 只会更新数据,而 fsync 还会同步更新文件的属性。 --file-merged-requests:允许合并的最多 IO 请求数,默认为0,不合并。 --file-rw-ratio:混合测试中的读写比例,默认为1.5。 磁盘 IO 测试主要分为以下三步: #准备测试文件 #sysbenchfileio--file-num=1--file-total-size=10G--file-test-mode=rndrwprepare #测试 #sysbenchfileio--file-num=1--file-total-size=10G--file-test-mode=rndrwrun #删除测试文件 #sysbenchfileio--file-num=1--file-total-size=10G--file-test-mode=rndrwcleanup 输出中,重点关注以下两部分: Fileoperations: reads/s:4978.26 writes/s:3318.84 fsyncs/s:83.07 Throughput: read,MiB/s:77.79 written,MiB/s:51.86 其中,reads/s 加上 writes/s 即我们常说的 IOPS。read, MiB/s 加上 written, MiB/s 即我们常说的吞吐量。 MySQL 常见测试场景及对应的 SQL 语句 接下来会列举 MySQL 常见的测试场景及各个场景对应的 SQL 语句。 为了让大家清晰的知道 SQL 语句的含义,首先我们看看测试表的表结构。 除了 bulk_insert 会创建单独的测试表,其它场景都会使用下面的表结构。 mysql>showcreatetablesbtest.sbtest1\G ***************************1.row*************************** Table:sbtest1 CreateTable:CREATETABLE`sbtest1`( `id`intNOTNULLAUTO_INCREMENT, `k`intNOTNULLDEFAULT'0', `c`char(120)NOTNULLDEFAULT'', `pad`char(60)NOTNULLDEFAULT'', PRIMARYKEY(`id`), KEY`k_1`(`k`) )ENGINE=InnoDBAUTO_INCREMENT=1000001DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci 1rowinset(0.00sec) bulk_insert 批量插入测试。 #sysbenchbulk_insert--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 下面是 bulk_insert 场景下创建的测试表。 mysql>showcreatetablesbtest.sbtest1\G ***************************1.row*************************** Table:sbtest1 CreateTable:CREATETABLE`sbtest1`( `id`intNOTNULL, `k`intNOTNULLDEFAULT'0', PRIMARYKEY(`id`) )ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci 1rowinset(0.01sec) 测试对应的 SQL 语句如下: INSERTINTOsbtest1VALUES(?,?),(?,?),(?,?),(?,?)... oltp_delete 删除测试。 #sysbencholtp_delete--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 基于主键进行删除。测试对应的 SQL 语句如下: DELETEFROMsbtest1WHEREid=? oltp_insert 插入测试。 #sysbencholtp_insert--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: INSERTINTOsbtest1(id,k,c,pad)VALUES(?,?,?,?) oltp_point_select 基于主键进行查询。 #sysbencholtp_point_select--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: SELECTcFROMsbtest1WHEREid=? oltp_read_only 只读测试。 #sysbencholtp_read_only--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: SELECTcFROMsbtest1WHEREid=?#默认会执行 10次,由--point_selects 选项控制。 SELECTcFROMsbtest1WHEREidBETWEEN?AND? SELECTSUM(k)FROMsbtest1WHEREidBETWEEN?AND? SELECTcFROMsbtest1WHEREidBETWEEN?AND?ORDERBYc SELECTDISTINCTcFROMsbtest1WHEREidBETWEEN?AND?ORDERBYc oltp_read_write 读写测试。 测试对应的 SQL 语句如下: SELECTcFROMsbtest1WHEREid=?#默认会执行 10次,由--point_selects 选项控制。 SELECTcFROMsbtest1WHEREidBETWEEN?AND? SELECTSUM(k)FROMsbtest1WHEREidBETWEEN?AND? SELECTcFROMsbtest1WHEREidBETWEEN?AND?ORDERBYc SELECTDISTINCTcFROMsbtest1WHEREidBETWEEN?AND?ORDERBYc UPDATEsbtest1SETk=k+1WHEREid=? UPDATEsbtest1SETc=?WHEREid=? DELETEFROMsbtest1WHEREid=? INSERTINTOsbtest1(id,k,c,pad)VALUES(?,?,?,?) oltp_update_index 基于主键进行更新,更新的是索引字段。 #sysbencholtp_update_index--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: UPDATEsbtest1SETk=k+1WHEREid=? oltp_update_non_index 基于主键进行更新,更新的是非索引字段。 #sysbencholtp_update_non_index--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: UPDATEsbtest1SETc=?WHEREid=? oltp_write_only 只写测试。 #sysbencholtp_write_only--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: UPDATEsbtest1SETk=k+1WHEREid=? UPDATEsbtest1SETc=?WHEREid=? DELETEFROMsbtest1WHEREid=? INSERTINTOsbtest1(id,k,c,pad)VALUES(?,?,?,?) select_random_points 基于索引进行随机查询。 #sysbenchselect_random_points--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: SELECTid,k,c,pad FROMsbtest1 WHEREkIN(?,?,?,?,?,?,?,?,?,?) select_random_ranges 基于索引进行随机范围查询。 #sysbenchselect_random_ranges--mysql-host=10.0.0.64--mysql-port=3306--mysql-user=admin--mysql-password=Py@123456--mysql-db=sbtest--tables=30--table-size=1000000--threads=64--time=60--report-interval=10run 测试对应的 SQL 语句如下: SELECTcount(k) FROMsbtest1 WHEREkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND?ORkBETWEEN?AND? 如何自定义 sysbench 测试脚本 下面通过 bulk_insert.lua 和 oltp_point_select.lua 这两个脚本分析下 sysbench 测试脚本的实现逻辑。 首先看看 bulk_insert.lua。 #catbulk_insert.lua #!/usr/bin/envsysbench cursize=0 functionthread_init() drv=sysbench.sql.driver() con=drv:connect() end functionprepare() locali localdrv=sysbench.sql.driver() localcon=drv:connect() fori=1,sysbench.opt.threadsdo print("Creatingtable'sbtest"..i.."'...") con:query(string.format([[ CREATETABLEIFNOTEXISTSsbtest%d( idINTEGERNOTNULL, kINTEGERDEFAULT'0'NOTNULL, PRIMARYKEY(id))]],i)) end end functionevent() if(cursize==0)then con:bulk_insert_init("INSERTINTOsbtest"..thread_id+1.."VALUES") end cursize=cursize+1 con:bulk_insert_next("("..cursize..","..cursize..")") end functionthread_done(thread_9d) con:bulk_insert_done() con:disconnect() end functioncleanup() locali localdrv=sysbench.sql.driver() localcon=drv:connect() fori=1,sysbench.opt.threadsdo print("Droppingtable'sbtest"..i.."'...") con:query("DROPTABLEIFEXISTSsbtest"..i) end end 下面,我们看看这几个函数的具体作用: thread_init():线程初始化时调用。这个函数常用来创建数据库连接。 prepare():指定 prepare 时调用。这个函数常用来创建测试表,生成测试数据。 event():指定 run 时调用。这个函数会定义需要测试的 SQL 语句。 thread_done():线程退出时调用。这个函数常用来关闭 Prepared Statements 和数据库连接。 cleanup():指定 cleanup 时调用。这个函数常用来删除测试表。 如果我们要自定义测试脚本,只需实现这几个函数即可。 如果我们要基于 sbtest 表自定义测试项,就要分析 oltp*.lua 脚本的实现逻辑。 下面,以oltp_point_select.lua 脚本为例。 #!/usr/bin/envsysbench ... require("oltp_common") functionprepare_statements() -- point_selects 是 oltp_point_select 中支持的选项,默认为 10,这里调整为了 1。 sysbench.opt.point_selects=1 prepare_point_selects() end functionevent() execute_point_selects() end 与 bulk_insert.lua 不一样的是,oltp_point_select.lua 只简单的定义了两个函数:prepare_statements()和event()。实际上,不仅仅是 oltp_point_select.lua,其它 oltp*.lua 脚本也只定义了这两个函数。 虽然只定义了这两个函数,但脚本导入了 oltp_common 模块,所以实际上,脚本中的 prepare_point_selects(),execute_point_selects() 以及 bulk_insert.lua 中的 thread_init(),prepare(),thread_done(),cleanup() 都是在oltp_common.lua这个公共模块中定义的。 接下来,我们看看 prepare_point_selects() 和 execute_point_selects() 这两个函数的实现逻辑。 首先看看prepare_point_selects()。 它调用的是prepare_for_each_table()。prepare_for_each_table()是一个基础函数。所有prepare 相关的函数都会调用prepare_for_each_table(), 只不过不同的 prepare 函数会传入不同的参数名。 prepare_for_each_table()会填充两张表(Lua 中的表既可用来表示数组,也可用来表示集合):stmt 和 param。其中,stmt 用来存储 Prepared Statements 语句,param 用来存储 Prepared Statements 语句相关的参数类型。 填充完毕后,最后再通过 bind_param 函数将两者绑定在一起。 可以看到,无论是 Prepared Statements 语句还是相关的参数类型,都是在 stmt_defs 定义的。 functionprepare_point_selects() prepare_for_each_table("point_selects") end functionprepare_for_each_table(key) fort=1,sysbench.opt.tablesdo --t是表的序号,key是测试项的名字 stmt[t][key]=con:prepare(string.format(stmt_defs[key][1],t)) localnparam=#stmt_defs[key]-1 ifnparam>0then param[t][key]={} end forp=1,nparamdo localbtype=stmt_defs[key][p+1] locallen iftype(btype)=="table"then len=btype[2] btype=btype[1] end ifbtype==sysbench.sql.type.VARCHARor btype==sysbench.sql.type.CHARthen param[t][key][p]=stmt[t][key]:bind_create(btype,len) else param[t][key][p]=stmt[t][key]:bind_create(btype) end end ifnparam>0then stmt[t][key]:bind_param(unpack(param[t][key])) end end end 接下来,我们看看 stmt_defs 的内容。 localstmt_defs={ point_selects={ "SELECTcFROMsbtest%uWHEREid=?", t.INT}, simple_ranges={ "SELECTcFROMsbtest%uWHEREidBETWEEN?AND?", t.INT,t.INT}, sum_ranges={ "SELECTSUM(k)FROMsbtest%uWHEREidBETWEEN?AND?", t.INT,t.INT}, order_ranges={ "SELECTcFROMsbtest%uWHEREidBETWEEN?AND?ORDERBYc", t.INT,t.INT}, distinct_ranges={ "SELECTDISTINCTcFROMsbtest%uWHEREidBETWEEN?AND?ORDERBYc", t.INT,t.INT}, index_updates={ "UPDATEsbtest%uSETk=k+1WHEREid=?", t.INT}, non_index_updates={ "UPDATEsbtest%uSETc=?WHEREid=?", {t.CHAR,120},t.INT}, deletes={ "DELETEFROMsbtest%uWHEREid=?", t.INT}, inserts={ "INSERTINTOsbtest%u(id,k,c,pad)VALUES(?,?,?,?)", t.INT,t.INT,{t.CHAR,120},{t.CHAR,60}}, } 可以看到,stmt_defs 是一张表,里面定义了不同测试项对应的 Prepared Statements 语句和参数类型。 具体到 point_selects 这个测试项,它对应的 Prepared Statements 语句是SELECT c FROM sbtest%u WHERE id=?,对应的参数类型是t.INT。 梳理完 prepare_point_selects() 函数的实现逻辑。最后我们看看execute_point_selects()函数的实现逻辑。 functionexecute_point_selects() localtnum=get_table_num() locali -- point_selects 对应命令行中的--point_selects 选项,默认为 10。 fori=1,sysbench.opt.point_selectsdo param[tnum].point_selects[1]:set(get_id()) stmt[tnum].point_selects:execute() end end 逻辑也非常简单,先赋值,最后执行。 所以如果我们要基于 sbtest 表自定义测试项,最关键的一步其实就是在 stmt_defs 中定义 Prepared Statements 语句和相关的参数类型。至于 prepare_xxx 和 execute_xxx 函数,实现起来都非常简单。 总结 1. 基准测试一般会关注三个指标:TPS/QPS、响应耗时和并发量。 2. 只有进行全链路压测,我们才知道系统的瓶颈在哪里。不能想当然的以为,数据库不容易横向扩展,系统瓶颈就一定会出在数据库层。事实上,很多系统在设计之初就引入了缓存,而缓存会分担很大一部分读流量,这种架构下的数据库压力其实并不大。 3. 不能简单的将 sysbench 的测试结果(TPS/QPS) 作为业务系统的吞吐量指标,因为两者的业务模型并不一致。 4. 如果要自定义测试脚本,实现的方式有两种: 自己实现测试相关的所有函数,具体实现细节可参考 bulk_insert.lua。 基于 sbtest 表自定义测试项。实现过程中最关键的一步是在 stmt_defs 中定义 Prepared Statements 语句和相关的参数类型。