从OceanBase TPC-C测试报告看ORACLE兼容性进展
Posted OceanBase技术闲谈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从OceanBase TPC-C测试报告看ORACLE兼容性进展相关的知识,希望对你有一定的参考价值。
概述
此次OceanBase集群使用了207台云服务器,客户端压测程序使用了64台云服务器。结果跑到6088万tpmc,并且8小时持续运行性能抖动不超过0.5%。
当然本文不是来介绍OB的TPC-C性能,而是从披露的测试报告里看看OceanBase新增的几个功能。如视图、复制表、存储过程等。
复制表功能
分布式数据库的痛点
不管是哪种架构的分布式数据库,当集群规模变大的时候,不同业务表的数据很大概率会在不同的节点上。此时如果这两个表要做表连接,在分布式数据库内部就会是一个跨节点的请求。相比节点内部的表连接,跨节点的表连接性能会有明显下降。尤其是非拆分表和拆分表做表连接时,这种跨节点请求很难避免。
分布式数据库中间件使用小表广播方案,将非拆分表冗余到所有分库内部,这样把这个分布式表连接下推到各个分库内部变成本地表连接,性能有明显提升。小表广播的原理是中间件自动在各个分库内部创建这个表,内部组件会解析底层源表所在数据库增量日志(多是mysql)并生成sql应用到所有分库内的表上。这个数据同步是独立于底层数据库外部做的逻辑同步。所以理论上它并没有很好的高可用能力,节点故障时也不能保证数据强一致。
尽管理论上不完美,但由于小表通常是配置表,记录变化少,而其带来的好处却非常明显,导致小表广播非常受开发欢迎,成为分布式数据库中间件产品的基本标配。
在OceanBase内部,水平拆分的方案是使用分区。分区是数据迁移、高可用的最小单元。OceanBase为了避免某些分区跨节点连接,使用了表分组(tablegroup
)的技术,将业务上联系紧密的分区约束在一个节点内部。但对于非分区表和分区表的表连接,以前就没有好的办法规避跨节点请求了。OceanBase 2.2.x 新增了表的复制表属性。当表是复制表的时候,OceanBase会保证主副本到其他所有副本的同步为全同步。
复制表功能
复制表的语法就是在普通建表语法通过locality
在租户所有节点创建只读副本(其中 F
就是默认的全功能副本,R
就是只读副本,后面是描述其分布范围),同时通过duplicate_scope
指定复制表属性应用范围。如果是复制表,则主副本到所有备副本的Redo同步为全同步,性能上比Paxos
协议同步Redo可能会更慢一些。不过由于用于复制表的数据变更通常不多,这点影响可以忽略。
下面创建2个表结构一致的表,一个是复制表,一个是普通表。下面的sql是在tpc-c的测试schema做了一些细微的改动,方便这里演示。
DROP tablegroup tpcc_group;
create tablegroup tpcc_group binding true partition by hash partitions 8;
DROP TABLE ordr;
create table ordr (
o_id number not null,
o_d_id number not null,
o_w_id number not null,
o_c_id number,
o_entry_d date,
o_carrier_id number,
o_ol_cnt number,
o_all_local number,
index iordr(o_w_id, o_d_id, o_c_id, o_id) storing (o_entry_d,o_carrier_id,o_ol_cnt ) local BLOCK_SIZE=8192,
primary key ( o_w_id, o_d_id, o_id )
)tablegroup tpcc_group
COMPRESS FOR QUERY pctfree=0 BLOCK_SIZE=8192 progressive_merge_num=1 USE_BLOOM_FILTER = TRUE
partition by hash(o_w_id) partitions 8;
drop table ordl;
create table ordl (
ol_o_id number not null,
ol_d_id number not null,
ol_w_id number not null,
ol_number number not null,
ol_i_id number,
ol_supply_w_id number,
ol_delivery_d date,
ol_quantity number,
ol_amount decimal(6,2),
ol_dist_info char(24),
primary key (ol_w_id, ol_d_id, ol_o_id, ol_number )
)tablegroup tpcc_group
COMPRESS for QUERY pctfree=0 BLOCK_SIZE=8192
partition by hash(ol_w_id) partitions 8;
create table item (i_id int
, i_name varchar(24)
, i_price decimal(5,2)
, i_data varchar(50)
, i_im_id int
, primary key(i_id)
) COMPRESS FOR QUERY pctfree=0 BLOCK_SIZE=16384duplicate_scope
='cluster'
locality
='F,R{all_server}@zone1, F,R{all_server}@zone2,F,R{all_server}@zone3' primary_zone='zone1';
create table item2 (i_id int
, i_name varchar(24)
, i_price decimal(5,2)
, i_data varchar(50)
, i_im_id int
, primary key(i_id)
) COMPRESS FOR QUERY pctfree=0 BLOCK_SIZE=16384
确认一下相关几个表主副本的位置。可以看出表ordr
和ordl
是分区表,同一个分区表不同分区的主副本分布在不同节点上,两个分区表在一个表分组里,所以同号分区在一个节点内部。item
表只有一个主副本,其他副本是备副本(下图备副本位置没有查出)。
SELECT t1.tenant_id,t1.tenant_name,t2.database_name,t3.table_id, t3.table_Name, t3.tablegroup_id, t3.part_num , t4.partition_Id, t4.svr_ip, t4.role
from `gv$tenant` t1 join `gv$database` t2 on (t1.tenant_id=t2.tenant_id)
join gv$table t3 on (t2.tenant_id=t3.tenant_id and t2.database_id=t3.database_id and t3.index_type=0)
left join __all_virtual_meta_table t4 on (t3.tenant_id=t4.tenant_id and (t3.tablegroup_id=t4.table_id or t3.table_id=t4.table_id))
where t1.tenant_id=1002 and t4.role in (1) and table_name in ('ordr','ordl','item','item2')
order by tablegroup_id, table_name , partition_id
;
备注: 以前给的常用SQL里查询分区位置的SQL,在OB 2.2.x版本后要修改一下。当多个表在一个分区组内时,__all_virtual_meta_table
的table_id
列就是分区组的id;否则就是普通表的table_id
。
复制表执行计划对比
首先看普通表的三表连接,其中item2
表是普通非分区表。所以三表连接,很可能就有跨节点的请求。从执行计划里的 EXCHANGE IN/OUT
可以确认。
再看复制表的三表连接,其中item
表是普通非分区表,但是是复制表。三表连接的时候,分区表ordr
和ordl
会选取所在节点的item
的备副本(或只读副本,这里没有只读副本)进行本地连接。从执行计划里也可以确认,没有远程执行计划。
实践经验
不管是小表广播还是复制表,都有个前提就是非拆分表(或非分区表)的更新不要太频繁。根据小表广播的客户使用经验看,不少客户会滥用这个功能,把很多表设置为广播表。有的甚至为了解决不同拆分维度表之间的连接性能问题而将拆分表设置为广播表,结果给后台同步任务增加很多不必要的压力,导致广播表的延时更大。
对于OceanBase而言,同样也不建议把分区表设置为复制表(虽然技术上是可以),那个违背了分区表的设计初衷。
复制表的备副本或只读副本如果出现故障,OceanBase会临时将该副本从全同步成员里拉黑。当该节点恢复时或者OceanBase集群补齐了该节点上原有副本时,会重新将该副本赎回到全同步成员列表里。所以复制表的高可用也是自动保证的。
可更新视图功能
视图是数据库常用的功能,通常用来查询。用视图再跟其他表做关联查询时,执行计划往往会很复杂。适当的将一些条件下推到视图内部,可以提升视图关联查询的性能。
视图还有一个特殊用法就是用于更新,这有一些前提条件,跟ORACLE可更新视图原理一样,不同之处在于OceanBase视图的数据可以是分布式的。即使更新产生了分布式事务,OceanBase也是保证视图数据强一致的。
下面直接看一个可更新视图的UPDATE
例子。
CREATE OR REPLACE VIEW stock_item AS
SELECT /*+ leading(s) use_merge(i) */
i_price,
i_name,
i_data,
s_i_id,
s_w_id,
s_order_cnt,
s_ytd,
s_remote_cnt,
s_quantity,
s_data,
s_dist_01,
s_dist_02,
s_dist_03,
s_dist_04,
s_dist_05,
s_dist_06,
s_dist_07,
s_dist_08,
s_dist_09,
s_dist_10
FROM stok s, item i WHERE s.s_i_id = i.i_id;
explain update stock_item set s_order_cnt = s_order_cnt + 1 where s_w_id=2 ;
看执行计划
存储过程
OceanBase从2.0版本就开始支持自定义函数、存储过程和匿名块了。对ORACLE存储过程兼容的能力是逐步完善的。此次TPC-C
的核心业务逻辑全部是通过存储过程实现的。下面就选取其中几个存储过程示例看看OceanBase存储过程最新能力。
支持存储过程入参和出参
存储过程的参数可以是普通类型,也可以是表变量。
从上图还可以看出OceanBase存储过程支持自定义异常。-6235
这个异常是序列化异常,这点也是兼容ORACLE的。主要在隔离级别serializable
下才会出现。
支持 bulk collect into和returning子句
可以将更新或者查询的结果返回到表变量里。
支持隐式游
如 sql%rowcount
表示返回受影响的行数。sql%found
表示是否有行受影响等等。
支持自定义异常的捕获
异常的定义在存储过程前面声明了。
下面是查询订单状态的存储过程内容。
CREATE OR REPLACE PROCEDURE orderstatus (
ware_id INTEGER,
dist_id INTEGER,
cust_id IN OUT INTEGER,
bylastname BINARY_INTEGER,
cust_last IN OUT VARCHAR2,
cust_first OUT VARCHAR2,
cust_middle OUT VARCHAR2,
cust_balance OUT NUMBER,
ord_id IN OUT INTEGER,
ord_entry_d OUT VARCHAR2,
ord_carrier_id OUT INTEGER,
ord_ol_cnt OUT INTEGER,
oline_supply_w_id OUT intarray,
oline_i_id OUT intarray,
oline_quantity OUT intarray,
oline_amount OUT numarray,
oline_delivery_d OUT datarray
)
IS
TYPE number_array IS TABLE OF number(38);
rowArr number_array := number_array();
read_nothing EXCEPTION;
PRAGMA EXCEPTION_INIT(read_nothing,-4026);
BEGIN
IF bylastname != 0 THEN
SELECT c_id BULK COLLECT INTO rowArr
FROM cust
WHERE c_last = cust_last AND c_d_id = dist_id AND c_w_id = ware_id
ORDER BY c_first;
IF 0 = rowArr.COUNT THEN
RAISE read_nothing;
END IF;
cust_id := rowArr((rowArr.COUNT + 1) / 2);
END IF;
SELECT c_last, c_first, c_middle, c_balance
INTO cust_last, cust_first, cust_middle, cust_balance
FROM cust
WHERE c_w_id = ware_id AND c_d_id = dist_id AND c_id = cust_id;
/* Select the last ORDER for this customer. */
SELECT o_id, o_entry_d_c, o_carrier_id_n, o_ol_cnt
INTO ord_id, ord_entry_d, ord_carrier_id, ord_ol_cnt
FROM (
SELECT o_id, to_char(o_entry_d, 'DD-MM-YYYY.HH24:MI:SS') as o_entry_d_c, nvl(o_carrier_id,0) as o_carrier_id_n, o_ol_cnt
FROM ordr
WHERE o_w_id = ware_id AND o_d_id = dist_id AND o_c_id = cust_id
ORDER BY o_w_id, o_d_id, o_c_id, o_id DESC
) tmp
WHERE rownum <= 1;
SELECT nvl(ol_delivery_d, DATE'1911-09-15') del_date, ol_amount, ol_i_id, ol_supply_w_id, ol_quantity
BULK COLLECT INTO oline_delivery_d, oline_amount, oline_i_id, oline_supply_w_id, oline_quantity
FROM ordl
WHERE ol_w_id = ware_id AND ol_d_id = dist_id AND ol_o_id = ord_id;
COMMIT;
END orderstatus;
/
CREATE OR REPLACE PROCEDURE stocklevel (
ware_id INTEGER,
dist_id INTEGER,
threshold INTEGER,
low_stock OUT INTEGER
)
IS
BEGIN
SELECT count (DISTINCT s_i_id)
INTO low_stock
FROM ordl, stok, dist
WHERE ol_w_id = ware_id AND d_id = dist_id AND d_w_id = ware_id AND
d_id = ol_d_id AND d_w_id = ol_w_id AND
ol_i_id = s_i_id AND ol_w_id = s_w_id AND
s_quantity < threshold AND
ol_o_id BETWEEN (d_next_o_id - 20) AND (d_next_o_id - 1);
COMMIT;
END stocklevel;
/
下面是一个简单存储过程的PL/SQL
调用示例。
后记
本文只是从OceanBase TPC-C的报告里窥探OceanBase几个特殊的功能,尤其是ORACLE兼容性的能力。随着版本的升级,更多ORACLE的功能会在ORACLE租户里支持。敬请持续关注!
参考
以上是关于从OceanBase TPC-C测试报告看ORACLE兼容性进展的主要内容,如果未能解决你的问题,请参考以下文章
OceanBase 二次 TPC-C 测试性能提升 10 倍
OceanBase 二次 TPC-C 测试性能提升 10 倍
OceanBase 二次 TPC-C 测试性能提升 10 倍