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 -y install make automake libtool pkgconfig libaio-devel openssl-devel mysql-devel
# cd /usr/src/
# wget https://github.com/akopytov/sysbench/archive/refs/tags/1.0.20.tar.gz
# tar xvf 1.0.20.tar.gz
# cd sysbench-1.0.20/
# ./autogen.sh
# ./configure
# make -j
# make install
安裝完成後,壓測指令碼預設會安裝在 /usr/local/share/sysbench
目錄下。
我們看看該目錄的內容。
# ls /usr/local/share/sysbench/
bulk_insert.lua oltp_insert.lua oltp_read_write.lua oltp_write_only.lua tests
oltp_common.lua oltp_point_select.lua oltp_update_index.lua select_random_points.lua
oltp_delete.lua oltp_read_only.lua oltp_update_non_index.lua select_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
...
General options:
--threads=N number of threads to use [1]
--events=N limit for total number of events [0]
--time=N limit for total execution time in seconds [10]
...
2. 測試項相關的配置項。各個測試項支援的配置項可透過 sysbench testname help
檢視。例如,
# sysbench memory help
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
memory options:
--memory-block-size=SIZE size of memory block for test [1K]
--memory-total-size=SIZE total size of data to transfer [100G]
--memory-scope=STRING memory access scope {global,local} [global]
--memory-hugetlb[=on|off] allocate memory from HugeTLB pool [off]
--memory-oper=STRING type of memory operations {read, write, none} [write]
--memory-access-mode=STRING memory access mode {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%
# sysbench oltp_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=30 prepare
命令中各個選項的具體含義如下:
- 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
預熱。主要是將磁碟中的資料載入到記憶體中。
# sysbench oltp_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=30 prewarm
除了需要將命令設定為 prewarm
,其它配置與 prepare
中一樣。
run
壓測。
# sysbench oltp_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=10 run
其中,
-
--time :壓測時間。不指定則預設為 10 秒。除了 --time,也可透過 --events 限制需要執行的 event 的數量。
-
--report-interval=10 :每 10 秒輸出一次測試結果,預設為 0,不輸出。
cleanup
清理資料。
# sysbench oltp_read_write --mysql-host=10.0.0.64 --mysql-port=3306 --mysql-user=admin --mysql-password=Py@123456 --mysql-db=sbtest --tables=30 cleanup
這裡只需指定 --tables ,sysbench 會序列執行 DROP TABLE IF EXISTS sbtest
操作。
如何分析 MySQL 基準測試結果
下面我們分析下 oltp_read_write 場景下的壓測結果。注:右滑可以看到每個指標的具體含義。
Threads started!
[ 10s ] thds: 64 tps: 5028.08 qps: 100641.26 (r/w/o: 70457.59/20121.51/10062.16) lat (ms,95%): 17.32 err/s: 0.00 reconn/s: 0.00
# thds 是併發執行緒數。tps 是每秒事務數。qps 是每秒運算元,等於 r(讀操作)加上 w(寫操作)加上 o(其他操作,主要包括 BEGIN 和 COMMIT)。lat 是延遲,(ms,95%) 是 95% 的查詢時間小於或等於該值,單位毫秒。err/s 是每秒錯誤數。reconn/s 是每秒重試的次數。
[ 20s ] thds: 64 tps: 5108.93 qps: 102192.09 (r/w/o: 71533.28/20440.64/10218.17) lat (ms,95%): 17.32 err/s: 0.00 reconn/s: 0.00
[ 30s ] thds: 64 tps: 5126.50 qps: 102505.50 (r/w/o: 71756.30/20496.60/10252.60) lat (ms,95%): 17.32 err/s: 0.00 reconn/s: 0.00
[ 40s ] thds: 64 tps: 5144.50 qps: 102907.20 (r/w/o: 72034.07/20583.72/10289.41) lat (ms,95%): 17.01 err/s: 0.00 reconn/s: 0.00
[ 50s ] thds: 64 tps: 5137.29 qps: 102739.80 (r/w/o: 71916.99/20548.64/10274.17) lat (ms,95%): 17.01 err/s: 0.00 reconn/s: 0.00
[ 60s ] thds: 64 tps: 4995.38 qps: 99896.35 (r/w/o: 69925.98/19979.61/9990.75) lat (ms,95%): 17.95 err/s: 0.00 reconn/s: 0.00
SQL statistics:
queries performed:
read: 4276622 # 讀操作的數量
write: 1221892 # 寫操作的數量
other: 610946 # 其它操作的數量
total: 6109460 # 總的運算元量,total = read + write + other
transactions: 305473 (5088.63 per sec.) # 總的事務數(每秒事務數)
queries: 6109460 (101772.64 per sec.) # 總的運算元(每秒運算元)
ignored errors: 0 (0.00 per sec.) # 忽略的錯誤數(每秒忽略的錯誤數)
reconnects: 0 (0.00 per sec.) # 重試次數(每秒重試的次數)
General statistics:
total time: 60.0301s # 總的執行時間
total number of events: 305473 # 執行的 event 的數量
# 在 oltp_read_write 中,預設引數下,一個 event 其實就是一個事務
Latency (ms):
min: 5.81 # 最小耗時
avg: 12.57 # 平均耗時
max: 228.87 # 最大耗時
95th percentile: 17.32 # 95% event 的執行耗時
sum: 3840044.28 # 總耗時
Threads fairness:
events (avg/stddev): 4773.0156/30.77 # 平均每個執行緒執行 event 的數量
# stddev 是標準差,值越小,代表結果越穩定。
execution time (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 測試的命令如下:
# sysbench cpu --cpu-max-prime=20000 --threads=32 run
輸出中,重點關注events per second
。值越大,代表 CPU 的計算效能越強。
CPU speed:
events per second: 25058.08
下面是 CPU 測試相關的程式碼,可以看到,sysbench 是透過計算--cpu-max-prime
範圍內的質數來衡量 CPU 的計算能力的。
質數(prime number)又稱素數,指的是大於 1,且只能被 1 和自身整除的自然數。在程式碼實現時,對於自然數 n,一般會用 2 到根號 n 之間的整數去除,如果都無法整除,則意味著 n 是個質數。
int cpu_execute_event(sb_event_t *r, int thread_id)
{
unsigned long long c;
unsigned long long l;
double t;
unsigned long long n=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++;
}
return 0;
}
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=1 run
輸出中,重點關注以下部分:
102400.00 MiB transferred (23335.96 MiB/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 測試主要分為以下三步:
# 準備測試檔案
# sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw prepare
# 測試
# sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw run
# 刪除測試檔案
# sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw cleanup
輸出中,重點關注以下兩部分:
File operations:
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> show create table sbtest.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int NOT NULL AUTO_INCREMENT,
`k` int NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
bulk_insert
批次插入測試。
# sysbench bulk_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=10 run
下面是 bulk_insert 場景下建立的測試表。
mysql> show create table sbtest.sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int NOT NULL,
`k` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.01 sec)
測試對應的 SQL 語句如下:
INSERT INTO sbtest1 VALUES(?, ?),(?, ?),(?, ?),(?, ?)...
oltp_delete
刪除測試。
# sysbench oltp_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=10 run
基於主鍵進行刪除。測試對應的 SQL 語句如下:
DELETE FROM sbtest1 WHERE id=?
oltp_insert
插入測試。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
INSERT INTO sbtest1 (id, k, c, pad) VALUES (?, ?, ?, ?)
oltp_point_select
基於主鍵進行查詢。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
SELECT c FROM sbtest1 WHERE id=?
oltp_read_only
只讀測試。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
SELECT c FROM sbtest1 WHERE id=? # 預設會執行 10 次,由 --point_selects 選項控制。
SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ?
SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ?
SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c
SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c
oltp_read_write
讀寫測試。
測試對應的 SQL 語句如下:
SELECT c FROM sbtest1 WHERE id=? # 預設會執行 10 次,由 --point_selects 選項控制。
SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ?
SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ?
SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c
SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c
UPDATE sbtest1 SET k=k+1 WHERE id=?
UPDATE sbtest1 SET c=? WHERE id=?
DELETE FROM sbtest1 WHERE id=?
INSERT INTO sbtest1 (id, k, c, pad) VALUES (?, ?, ?, ?)
oltp_update_index
基於主鍵進行更新,更新的是索引欄位。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
UPDATE sbtest1 SET k=k+1 WHERE id=?
oltp_update_non_index
基於主鍵進行更新,更新的是非索引欄位。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
UPDATE sbtest1 SET c=? WHERE id=?
oltp_write_only
只寫測試。
# sysbench oltp_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=10 run
測試對應的 SQL 語句如下:
UPDATE sbtest1 SET k=k+1 WHERE id=?
UPDATE sbtest1 SET c=? WHERE id=?
DELETE FROM sbtest1 WHERE id=?
INSERT INTO sbtest1 (id, k, c, pad) VALUES (?, ?, ?, ?)
select_random_points
基於索引進行隨機查詢。
# sysbench select_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=10 run
測試對應的 SQL 語句如下:
SELECT id, k, c, pad
FROM sbtest1
WHERE k IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
select_random_ranges
基於索引進行隨機範圍查詢。
# sysbench select_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=10 run
測試對應的 SQL 語句如下:
SELECT count(k)
FROM sbtest1
WHERE k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ?
如何自定義 sysbench 測試指令碼
下面透過 bulk_insert.lua 和 oltp_point_select.lua 這兩個指令碼分析下 sysbench 測試指令碼的實現邏輯。
首先看看 bulk_insert.lua。
# cat bulk_insert.lua
#!/usr/bin/env sysbench
cursize=0
function thread_init()
drv = sysbench.sql.driver()
con = drv:connect()
end
function prepare()
local i
local drv = sysbench.sql.driver()
local con = drv:connect()
for i = 1, sysbench.opt.threads do
print("Creating table 'sbtest" .. i .. "'...")
con:query(string.format([[
CREATE TABLE IF NOT EXISTS sbtest%d (
id INTEGER NOT NULL,
k INTEGER DEFAULT '0' NOT NULL,
PRIMARY KEY (id))]], i))
end
end
function event()
if (cursize == 0) then
con:bulk_insert_init("INSERT INTO sbtest" .. thread_id+1 .. " VALUES")
end
cursize = cursize + 1
con:bulk_insert_next("(" .. cursize .. "," .. cursize .. ")")
end
function thread_done(thread_9d)
con:bulk_insert_done()
con:disconnect()
end
function cleanup()
local i
local drv = sysbench.sql.driver()
local con = drv:connect()
for i = 1, sysbench.opt.threads do
print("Dropping table 'sbtest" .. i .. "'...")
con:query("DROP TABLE IF EXISTS sbtest" .. 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/env sysbench
...
require("oltp_common")
function prepare_statements()
-- point_selects 是 oltp_point_select 中支援的選項,預設為 10,這裡調整為了 1。
sysbench.opt.point_selects=1
prepare_point_selects()
end
function event()
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 定義的。
function prepare_point_selects()
prepare_for_each_table("point_selects")
end
function prepare_for_each_table(key)
for t = 1, sysbench.opt.tables do
-- t 是表的序號,key 是測試項的名字
stmt[t][key] = con:prepare(string.format(stmt_defs[key][1], t))
local nparam = #stmt_defs[key] - 1
if nparam > 0 then
param[t][key] = {}
end
for p = 1, nparam do
local btype = stmt_defs[key][p+1]
local len
if type(btype) == "table" then
len = btype[2]
btype = btype[1]
end
if btype == sysbench.sql.type.VARCHAR or
btype == sysbench.sql.type.CHAR then
param[t][key][p] = stmt[t][key]:bind_create(btype, len)
else
param[t][key][p] = stmt[t][key]:bind_create(btype)
end
end
if nparam > 0 then
stmt[t][key]:bind_param(unpack(param[t][key]))
end
end
end
接下來,我們看看 stmt_defs 的內容。
local stmt_defs = {
point_selects = {
"SELECT c FROM sbtest%u WHERE id=?",
t.INT},
simple_ranges = {
"SELECT c FROM sbtest%u WHERE id BETWEEN ? AND ?",
t.INT, t.INT},
sum_ranges = {
"SELECT SUM(k) FROM sbtest%u WHERE id BETWEEN ? AND ?",
t.INT, t.INT},
order_ranges = {
"SELECT c FROM sbtest%u WHERE id BETWEEN ? AND ? ORDER BY c",
t.INT, t.INT},
distinct_ranges = {
"SELECT DISTINCT c FROM sbtest%u WHERE id BETWEEN ? AND ? ORDER BY c",
t.INT, t.INT},
index_updates = {
"UPDATE sbtest%u SET k=k+1 WHERE id=?",
t.INT},
non_index_updates = {
"UPDATE sbtest%u SET c=? WHERE id=?",
{t.CHAR, 120}, t.INT},
deletes = {
"DELETE FROM sbtest%u WHERE id=?",
t.INT},
inserts = {
"INSERT INTO sbtest%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()
函式的實現邏輯。
function execute_point_selects()
local tnum = get_table_num()
local i
-- point_selects 對應命令列中的 --point_selects 選項,預設為 10。
for i = 1, sysbench.opt.point_selects do
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 語句和相關的引數型別。