TPC-C 是 TPC 组织(Transaction Processing Performance Council)推出的一系列性能测试标准中的一款,自推出以来,一直是数据库业界性能测试的重要参考,全世界各大数据库厂商都向 TPC 委员会提交了测试结果,以期望在 TPC-C 测试的排行榜上取得更好的成绩。OceanBase 数据库在 2019 年和 2020 年两次刷新了 TPC-C 的世界纪录,成为榜单上首个分布式关系型数据库。
在本篇文章中,通过在 OceanBase 数据库上运行 TPC-C 测试的方式,体验 OceanBase 数据库的 OLTP 能力。
关于 TPC-C
数据库模型
TPC-C Benchmark 规定了数据库的初始状态,其中 ITEM 表中包含固定的 10 万种商品,而仓库的数量可根据测试规模进行调整,假设 WAREHOUSE 表中有 W 条记录,那么:
STOCK表中应有 W×10 万条记录(每个仓库对应 10 万种商品的库存数据)。DISTRICT表中应有 W×10 条记录(每个仓库为 10 个地区提供服务)。CUSTOMER表中应有 W×10×3000 条记录(每个地区有 3000 个客户)。HISTORY表中应有 W×10×3000 条记录(每个客户一条交易历史)。ORDER表中应有 W×10×3000 条记录(每个地区 3000 个订单),并且最后生成的 900 个订单将被添加到NEW-ORDER表中,每个订单随机生成 5~15 条ORDER-LINE记录。
在测试过程中,每一个地区(DISTRICT)都有一个对应的终端(Terminal),模拟为用户提供服务。在每个终端的生命周期内,要循环往复地执行各类事务,当终端执行完一个事务的周期后,就进入下一个事务的周期。
客户下单后,包含若干个订单明细(ORDER-LINE)的订单(ORDER)被生成,并被加入新订单(NEW-ORDER)列表。
客户支付订单会产生交易历史(HISTORY)。每个订单(ORDER)平均包含 10 条订单项(ORDER-LINE),其中 1% 需要从远程仓库中获取,这些就是 TPC-C 模型中的 9 个数据表。
事务类型
该 Benchmark 包含 5 类事务:
- NewOrder:新订单请求从某一仓库中随机选取 5~15 件商品,创建新订单。其中 1% 的事务需要回滚(即
err)。一般地,新订单请求不可能超出全部事务请求的 45%。 - Payment:订单付款更新客户账户余额,反映其支付情况。在全部事务请求中占比 43% 。
- OrderStatus:最近订单查询随机选择一个用户,查询其最近一条订单,显示该订单内的每个商品状态。在全部事务请求中占比 4%。
- Delivery:配送模拟批处理交易,更新该订单用户的余额,把发货单从 NewOrder 中删除。在全部事务请求中占比 4%。
- StockLevel:库存缺货状态分析,在全部事务请求中占比 4%。
环境准备
OceanBase 集群
根据部署的 OceanBase 集群类型的差异以不同的方式观察 OceanBase 数据库的表现,如果是按照快速开始章节中创建的单节点 OceanBase 集群,那么本文中可以看到 OceanBase 数据库在单机形态下的运行情况。如果希望体验 OceanBase 数据库分布式架构的 Scalable OLTP 能力,那么建议采用至少三节点的 OceanBase 集群进行测试。
本例中使用的租户模式为 MySQL 模式,租户名为 test,您可以创建自己的租户,具体步骤详见体验多租户特性
安装 BenchmarkSQL
TPC 组织为 TPC-C 定义了严格、详细的测试标准,一般情况下开发者如果想要模拟 TPC-C 的场景进行测试,可以使用目前常用的开源测试工具。例如本文中使用的 BenchmarkSQL。 BenchmarkSQL 的官方下载地址为:https://sourceforge.net/projects/benchmarksql/。
注意
测试环境需要有 Java 运行环境,且版本不低于 V1.8.0。
适配 Benchmark SQL5
由于 Benchmark SQL5 不支持 OceanBase 数据库的 TPC-C 测试,本节将详细介绍如何通过修改 BenchMarkSQL5 部分源码支持 OceanBase 数据库。
修改
benchmarksql-5.0/src/client/jTPCC.java文件,增加 OceanBase 数据库相关内容。if (iDB.equals("firebird")) dbType = DB_FIREBIRD; else if (iDB.equals("oracle")) dbType = DB_ORACLE; else if (iDB.equals("postgres")) dbType = DB_POSTGRES; else if (iDB.equals("oceanbase")) dbType = DB_OCEANBASE; else { log.error("unknown database type '" + iDB + "'"); return; }修改
benchmarksql-5.0/src/client/jTPCCConfig.java文件,增加 OceanBase 数据库类型。public final static int DB_UNKNOWN = 0, DB_FIREBIRD = 1, DB_ORACLE = 2, DB_POSTGRES = 3, DB_OCEANBASE = 4;修改
benchmarksql-5.0/src/client/jTPCCConnection.java文件,在 SQL 子查询增加AS L别名。default: stmtStockLevelSelectLow = dbConn.prepareStatement( "SELECT count(*) AS low_stock FROM (" + " SELECT s_w_id, s_i_id, s_quantity " + " FROM bmsql_stock " + " WHERE s_w_id = ? AND s_quantity < ? AND s_i_id IN (" + " SELECT ol_i_id " + " FROM bmsql_district " + " JOIN bmsql_order_line ON ol_w_id = d_w_id " + " AND ol_d_id = d_id " + " AND ol_o_id >= d_next_o_id - 20 " + " AND ol_o_id < d_next_o_id " + " WHERE d_w_id = ? AND d_id = ? " + " ) " + " )AS L"); break;重新编译修改后的源码。
[oceanbase@testdrier test]# cd benchmarksql-5.0 [oceanbase@testdrier benchmarksql-5.0]# ant修改文件:
benchmarksql-5.0/run/funcs.sh,添加 OceanBase 数据库类型。function setCP() { case "$(getProp db)" in firebird) cp="../lib/firebird/*:../lib/*" ;; oracle) cp="../lib/oracle/*" if [ ! -z "${ORACLE_HOME}" -a -d ${ORACLE_HOME}/lib ] ; then cp="${cp}:${ORACLE_HOME}/lib/*" fi cp="${cp}:../lib/*" ;; postgres) cp="../lib/postgres/*:../lib/*" ;; oceanbase) cp="../lib/oceanbase/*:../lib/*" ;; esac myCP=".:${cp}:../dist/*" export myCP } ...省略 case "$(getProp db)" in firebird|oracle|postgres|oceanbase) ;; "") echo "ERROR: missing db= config option in ${PROPS}" >&2 exit 1 ;; *) echo "ERROR: unsupported database type 'db=$(getProp db)' in ${PROPS}" >&2 exit 1 ;; esac修改
benchmarksql-5.0/run/runDatabaseBuild.sh。AFTER_LOAD="indexCreates foreignKeys extraHistID buildFinish" # 修改为: AFTER_LOAD="indexCreates buildFinish"
修改配置文件
配置文件 props.ob 在 BenchmarkSQL 的 run/ 目录下:
db=oceanbase
driver=com.alipay.oceanbase.jdbc.Driver
conn=jdbc:oceanbase://127.0.0.1:2881/tpccdb?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&allowMultiQueries=true
user=root@test
password=root
warehouses=10
loadWorkers=2
//fileLocation=/data/temp/
terminals=10
//To run specified transactions per terminal- runMins must equal zero
runTxnsPerTerminal=0
//To run for specified minutes- runTxnsPerTerminal must equal zero
runMins=10
//Number of total transactions per minute
limitTxnsPerMin=0
//Set to true to run in 4.x compatible mode. Set to false to use the
//entire configured database evenly.
terminalWarehouseFixed=true
//The following five values must add up to 100
newOrderWeight=45
paymentWeight=43
orderStatusWeight=4
deliveryWeight=4
stockLevelWeight=4
// Directory name to create for collecting detailed result data.
// Comment this out to suppress.
resultDirectory=my_result_%tY-%tm-%td_%tH%tM%tS
osCollectorScript=./misc/os_collector_linux.py
osCollectorInterval=1
//osCollectorSSHAddr=user@dbhost
//osCollectorDevices=net_eth0 blk_sda
说明
db:指定数据库类型。此处保持和模板一致即可。driver:驱动程序文件,推荐使用 OceanBase 数据库的 JDBC 驱动:oceanbase-client,如果需要使用该驱动,请联系技术支持人员获取。·conn`:此处的 IP 建议填写 OceanBase Server 的 IP,端口为 OceanBase Server 部署端口,其他部分保持和模板一致。user&password:根据环境中使用的用户名、租户名以及密码即可。如果环境中有多个 OceanBase 集群,则user的格式建议为{user_name}@{tenant_name}#{cluster_name}。warehouses:指定仓库数,仓库数决定性能测试的成绩。如果希望针对多节点的 OceanBase 集群进行测试,建议选择 1000 仓以上。如果机器配置有限,可以选择 100 仓进行测试。loadWorkers:指定仓库数据加载时的并发。如果机器配置较高,该值可以设置大一些,例如 100。如果机器配置有限,该值需要设置小一些,如 10 并发。过高的并发可能会导致内存消耗太快,出现报错,导致数据加载需要重新进行。terminals:指定性能压测时的并发数。建议并发数不要高于仓库数 * 10。否则,会有不必要的锁等待。在生产环境中,建议将此参数设置为最多 1000。在测试环境中,建议从 100 开始。runMins:指定性能测试持续的时间。时间越久,越能考验数据库的性能和稳定性。建议不要少于 10 分钟,生产环境中机器建议不少于 1 小时。
数据准备
创建 tpccdb 数据库
在测试租户 test 中,创建本次测试的数据库 tpccdb:
CREATE DATABASE tpccdb;
创建表
建表脚本通常放在 benchmarskSQL 的 run/sql.common 下或者其他指定目录下。建表脚本如下,采用分区表方式创建,大部分表按照仓库 ID 做 HASH 分区。分区数取决于要测试的数据规模和机器数。 如果集群只有 1 台或 3 台机器,分区数设置 9 个即可。如果是 5000 仓以上,或者集群中节点数较多,则分区数可以调整到 99。
[root@obce-0000 run]# cat sql.common/tableCreates_parts.sql
create table bmsql_config (
cfg_name varchar(30) primary key,
cfg_value varchar(50)
);
-- drop tablegroup tpcc_group;
create tablegroup tpcc_group binding true partition by hash partitions 9;
create table bmsql_warehouse (
w_id integer not null,
w_ytd decimal(12,2),
w_tax decimal(4,4),
w_name varchar(10),
w_street_1 varchar(20),
w_street_2 varchar(20),
w_city varchar(20),
w_state char(2),
w_zip char(9),
primary key(w_id)
)tablegroup='tpcc_group' partition by hash(w_id) partitions 9;
create table bmsql_district (
d_w_id integer not null,
d_id integer not null,
d_ytd decimal(12,2),
d_tax decimal(4,4),
d_next_o_id integer,
d_name varchar(10),
d_street_1 varchar(20),
d_street_2 varchar(20),
d_city varchar(20),
d_state char(2),
d_zip char(9),
PRIMARY KEY (d_w_id, d_id)
)tablegroup='tpcc_group' partition by hash(d_w_id) partitions 9;
create table bmsql_customer (
c_w_id integer not null,
c_d_id integer not null,
c_id integer not null,
c_discount decimal(4,4),
c_credit char(2),
c_last varchar(16),
c_first varchar(16),
c_credit_lim decimal(12,2),
c_balance decimal(12,2),
c_ytd_payment decimal(12,2),
c_payment_cnt integer,
c_delivery_cnt integer,
c_street_1 varchar(20),
c_street_2 varchar(20),
c_city varchar(20),
c_state char(2),
c_zip char(9),
c_phone char(16),
c_since timestamp,
c_middle char(2),
c_data varchar(500),
PRIMARY KEY (c_w_id, c_d_id, c_id)
)tablegroup='tpcc_group' partition by hash(c_w_id) partitions 9;
create table bmsql_history (
hist_id integer,
h_c_id integer,
h_c_d_id integer,
h_c_w_id integer,
h_d_id integer,
h_w_id integer,
h_date timestamp,
h_amount decimal(6,2),
h_data varchar(24)
)tablegroup='tpcc_group' partition by hash(h_w_id) partitions 9;
create table bmsql_new_order (
no_w_id integer not null ,
no_d_id integer not null,
no_o_id integer not null,
PRIMARY KEY (no_w_id, no_d_id, no_o_id)
)tablegroup='tpcc_group' partition by hash(no_w_id) partitions 9;
create table bmsql_oorder (
o_w_id integer not null,
o_d_id integer not null,
o_id integer not null,
o_c_id integer,
o_carrier_id integer,
o_ol_cnt integer,
o_all_local integer,
o_entry_d timestamp,
PRIMARY KEY (o_w_id, o_d_id, o_id)
)tablegroup='tpcc_group' partition by hash(o_w_id) partitions 9;
create table bmsql_order_line (
ol_w_id integer not null,
ol_d_id integer not null,
ol_o_id integer not null,
ol_number integer not null,
ol_i_id integer not null,
ol_delivery_d timestamp,
ol_amount decimal(6,2),
ol_supply_w_id integer,
ol_quantity integer,
ol_dist_info char(24),
PRIMARY KEY (ol_w_id, ol_d_id, ol_o_id, ol_number)
)tablegroup='tpcc_group' partition by hash(ol_w_id) partitions 9;
create table bmsql_item (
i_id integer not null,
i_name varchar(24),
i_price decimal(5,2),
i_data varchar(50),
i_im_id integer,
PRIMARY KEY (i_id)
);
create table bmsql_stock (
s_w_id integer not null,
s_i_id integer not null,
s_quantity integer,
s_ytd integer,
s_order_cnt integer,
s_remote_cnt integer,
s_data varchar(50),
s_dist_01 char(24),
s_dist_02 char(24),
s_dist_03 char(24),
s_dist_04 char(24),
s_dist_05 char(24),
s_dist_06 char(24),
s_dist_07 char(24),
s_dist_08 char(24),
s_dist_09 char(24),
s_dist_10 char(24),
PRIMARY KEY (s_w_id, s_i_id)
)tablegroup='tpcc_group' use_bloom_filter=true partition by hash(s_w_id) partitions 9;
运行如下命令建表。
./runSQL.sh props.ob sql.common/tableCreates_parts.sql
加载数据
加载数据即数据初始化,加载数据的速度,取决于机器配置,配置越高的机器,加载数据的速度越快。
./runLoader.sh props.ob
加载数据的 INSERT SQL 使用了 Batch Insert 特性,这点是在 props.ob 里的 JDBC URL 里指定的。开启该特性的写入性能会有明显提升。
创建索引
当数据初始化完成后,登录到 test 租户,在 tpccdb 中补充创建如下两个索引。
[root@obce-0000 run]# cat sql.common/indexCreates.sql
create index bmsql_customer_idx1
on bmsql_customer (c_w_id, c_d_id, c_last, c_first) local;
create index bmsql_oorder_idx1
on bmsql_oorder (o_w_id, o_d_id, o_carrier_id, o_id) local;
开始测试
在开始性能测试之前,建议您先登录到对应租户做一次集群合并(major freeze),获得更好的测试结果。您可以通过如下的方式手动触发合并,这个过程并不是必须的。
obclient[oceanbase]> ALTER SYSTEM MAJOR FREEZE TENANT [=] tenant_name_list;
当看到如下查询返回 IDLE 时,表示合并完成。
MySQL [oceanbase]> SELECT * FROM oceanbase.CDB_OB_ZONE_MAJOR_COMPACTION;
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
| TENANT_ID | ZONE | BROADCAST_SCN | LAST_SCN | LAST_FINISH_TIME | START_TIME | STATUS |
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
| 1 | zone1 | 1664503499339325817 | 1664503499339325817 | 2022-09-30 10:05:19.442115 | 2022-09-30 10:04:59.369976 | IDLE |
| 1002 | zone1 | 1 | 1 | 1970-01-01 08:00:00.000000 | 1970-01-01 08:00:00.000000 | IDLE |
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
2 rows in set
合并完成后,开始执行测试:
./runBenchmark.sh props.ob
TPC-C 用 tpmC 值(Transactions per Minute)来衡量系统最大有效吞吐量。其中 Transactions 以 NewOrder Transaction 为准,即最终衡量单位为每分钟处理的订单数。
体验 OceanBase 数据库 Scalable OLTP
上面的测试中,不管是单节点集群测试,还是多节点集群,默认情况下参与事务处理的只有一个副本 Zone,及租户 Leader 所在的副本。这是因为默认设置下,租户的 Leader 是按照 Zone 维度以一定优先级分布的。
OceanBase 作为分布式数据库,用户可以选择将一个租户的所有数据分区的多个 Leader 打散 Shuffle 到多个副本上,实现计算处理能力的多机线性扩展。
如果您的集群环境是三节点或更多节点的配置,您只需以管理员身份执行以下命令,然后再次启动测试,即可体验分布式集群在扩展模式下的处理能力。
alter tenant test set primary_zone='RANDOM';



