摘要:好消息!「 OceanBase 2.2 版本 」正式上线官网啦!(点击阅读原文即可直接下载)OceanBase 2.2版本是成功支撑2019年天猫双11大促的稳定版本,同时也是用于TPC-C测试且荣登TPC-C性能榜首的版本。
本文为 “OceanBase 2.2 解析系列” 第一篇,该系列将从事务、SQL、兼容性等维度为大家全面解读2.2版本的功能和特性。欢迎持续关注!
一路走来,OceanBase 始终坚持自主研发,去年双11正式对外发布的OceanBase 2.2 版本为内外部业务提供了很多新增的功能。与此同时,事务引擎也在持续迭代,推出了可串行化隔离级别、复制表、闪回等重要更新。本文将对OceanBase 2.2版本中事务引擎的三大重点功能分别进行介绍。
功能一:可串行化隔离级别
OceanBase 2.2 版本在之前 MySQL 兼容性模式的基础上推出了兼容 Oracle 的模式。事务处理能力是 Oracle 兼容性模式的重要组成部分。Oracle 事务引擎最有特点的功能是基于多版本控制的事务隔离能力,支持读已提交(Read Committed)和可串行化(Serializable)两种隔离级别。Oracle 两种隔离级别均支持读写分离能力,修改操作不会影响读取的进行,读取操作也不会阻塞修改。
OceanBase 2.2 版本的 Oracle 兼容模式目标是提供给用户方便的移植应用的能力。OceanBase 事务引擎完全兼容 Oracle 的 Read Committed 和 Serializable 隔离级别。OceanBase 的事务处理采用的也是多版本并发控制的方案,DML 语句的修改不是在数据项上直接生效,而是以一个新版本的形式写入,同时保留旧版本的数据。
OceanBase 中的每一个事务有两种版本号,一个是读取使用的版本号,另一个是提交版本号。读取使用的版本号叫做“读版本号”或者“快照版本号”,对于读取的每一行数据,即使行上有新的修改,读取操作依然要找到快照版本号所对应的数据项。事务的提交版本号是在事务提交时产生,本事务的所有修改数据都会被标记上这个“提交版本号”。
Read Committed 与 Serializable 两种隔离级别的不同在于“读版本号”的生成逻辑。Read Committed 为了实现读到已经提交的数据,事务内每条语句会取当前系统的最新版本号,保证读取操作不会遗漏读取所在事务开启后才提交的其他事务所做的修改。Serializable 隔离级别是在事务开始时获取了”读版本号“后,整个事务不会再变化,保证了整个事务都会见到数据库的同一个快照状态。
举个例子来说明两者的特性。下图演示在 Read Committed 隔离级别下,事务内可以见到别的事务修改完成的数据。
Session 1 |
Session 2 |
备注 |
CREATE TABLE A (pk INT PRIMARY KEY, val INT); INSERT INTO A VALUES(1, 1); INSERT INTO A VALUES(2, 2); COMMIT; |
准备数据 |
|
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
Session 1 开启 Read Committed 隔离级别事务 |
|
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ |
||
INSERT INTO A VALUES(3, 3); COMMIT; |
Session 2 插入一条数据并提交 |
|
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+------+ |
Session 1 的 SELECT 语句是 Read Committed 隔离级别,会重新获取“读快照版本” |
下图演示在 Serializable 隔离级别下,事务内不能见到别的事务修改完成的数据。
Session 1 |
Session 2 |
备注 |
CREATE TABLE A (pk INT PRIMARY KEY, val INT); INSERT INTO A VALUES(1, 1); INSERT INTO A VALUES(2, 2); COMMIT; |
准备数据 |
|
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
Session 1 开启 Serializable 隔离级别事务 |
|
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ |
||
INSERT INTO A VALUES(3, 3); COMMIT; |
Session 2 插入一条数据并提交 |
|
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ |
Session 1 的 SELECT 语句是 Serializable 隔离级别,使用事务开启时获取的“读版本号”,不会读到后续事务的修改(3,3) |
应用如何选择 Read Committed 还是 Serializable 隔离级别没有一招鲜的策略,需要从实际需要出发。通常来说,Read Committed 可以满足大部分应用场景的需求。同时,Serializable 隔离级别为了支持事务级别的快照能力,可能会引发更大概率事务调度异常,最终导致并发的事务执行无法满足可串行化隔离级别,出现回滚。下面的例子描述了一种无法串行化的报错。
Session 1 |
Session 2 |
备注 |
CREATE TABLE A (pk INT PRIMARY KEY, val INT); INSERT INTO A VALUES(1, 1); INSERT INTO A VALUES(2, 2); COMMIT; |
准备数据 |
|
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
Session 1 开启 Serializable 隔离级别事务 |
|
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ |
||
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 2 | +----+------+ UPDATE A SET VAL = VAL + 1 WHERE PK = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | | 2 | 3 | +----+------+ COMMIT; |
Session 2 开启 Serializable 隔离级别事务,更新 PK = 2 这一行数据,之后提交 |
|
UPDATE A SET VAL = VAL + 1 WHERE PK = 2; ERROR-08177: can't serialize access for this transaction |
Session 1 也更新 PK = 2 这一行数据,结果不是 VAL 值被更新为 4,而是会出现“can't serialize”报错 |
功能二:实时一致的复制表
OceanBase 的分布式架构从本质上决定了数据被分散在不同的机器上分别服务,SQL 语句执行时会根据所操作数据的位置生成最优的执行计划,或在一台机器或在多台机器运行 SQL 的执行计划。
虽然 SQL 引擎会根据最优的策略来优化执行的开销和执行的时间,但是涉及多台机器数据的操作时,跨机器之间的数据传输很多时候不可避免。例如很多业务中存储关键信息的配置表,还有金融业务中存储汇率信息的表。
这类表格内的数据被频繁访问,通过网络被反复的传输到各台机器上,带来的开销有时很大,而这种开销可以通过“复制表”功能来优化。复制表将需要被频繁访问的数据复制到集群的每一台机器上,反复的操作不需要再跨机传输数据。
TPC-C 测试模型中有一张商品(ITEM)表,这张表的内容是测试所模拟的销售公司所有售卖的商品信息,包含了商品的名字、价格等信息。“订单创建”事务执行中需要请求这张表内的数据来确定订单的价格信息,这样的商品表也是复制表可以优化的典型场景。
将数据复制到多机带来的最大挑战是对于数据的修改要在所有机器上实时生效,否则一部分机器读到新的数据另一部分读到旧的数据,这一定是不满足用户需求的功能。OceanBase 使用特殊的广播协议保证复制表的副本一致性,当复制表发生修改时,所有的副本会同时修改。并且,只有在所有机器上的副本都修改成功时,修改操作才会生效。
在分布式系统中,另一个棘手的问题又会出现,如果复制表修改的过程中有机器出现故障,故障机器上的复制表的副本无法修改,那么复制表就再也不能改了吗?如果不解决这个问题,复制表在出现机器故障时就会拖累用户的操作。
OceanBase 采用了租约机制来解决这个难题。每台机器上有效的复制表副本都会获得租约,当复制表修改时,修改要同步到所有有租约的副本。如果出现机器故障,故障机器上的复制表副本的租约会失效,失效的副本不会被同步新的修改,所以不会阻塞复制表后续的修改操作。失效的副本同时会拒绝读取操作,保证不会读到旧的数据。当失效副本恢复后,可以追赶遗漏的数据,等其追赶到最新状态后会被重新授予租约。
功能三:指定快照版本查询
OceanBase 事务引擎的多版本并发控制机制是通过修改操作产生新的数据版本来实现,这个机制除了可以解决事务并发控制的需求,还能够支持其他很多功能,比如指定快照版本查询的能力可以用于查看历史数据,兼容 Oracle 的 Flashback Query 功能。
用户可以在 SELECT 语句中使用 AS OF 关键字来指定需要读取的历史版本,支持使用 SCN 和 TIMESTAMP 两种方式。使用 SCN 指定事务提交版本号,使用 TIMESTAMP 指定一个具体的时间戳。而 OceanBase 允许用户回溯的历史时间通过 UNDO_RETENTION 变量来指定,OceanBase 会保留 UNDO_RETENTION 时间内的所有历史版本。
下面是一个使用方式的样例。
语句 |
备注 |
CREATE TABLE A (pk INT PRIMARY KEY, val INT); INSERT INTO A VALUES(1, 1); |
准备数据 |
SELECT * FROM A; +----+------+ | PK | VAL | +----+------+ | 1 | 1 | +----+------+ |
表 A 内的实际数据 |
SELECT * FROM A AS OF TIMESTAMP TO_TIMESTAMP('2010-05-11 22:00:00.000000','YYYY-MM-DD HH24:MI:SS.FF'); Empty set |
指定一个插入数据之前的时间戳不会读到数据 |
SELECT ORA_ROWSCN, PK, VAL FROM A; +------------------+----+------+ | ORA_ROWSCN | PK | VAL | +------------------+----+------+ | 1273590000000000 | 1 | 1 | +------------------+----+------+ |
使用 ORA_SCN 伪列可以查看行上事务的提交版本号,1273590000000000 是微秒级时间戳,对应 2010-05-11 23:00:00.000000 |
SELECT * FROM A AS OF TIMESTAMP TO_TIMESTAMP('2010-05-11 23:00:00.000000','YYYY-MM-DD HH24:MI:SS.FF'); +----+------+ | PK | VAL | +----+------+ | 1 | 1 | +----+------+ |
使用 2010-05-11 23:00:00.000000 这个时间戳就可以查询到数据 |
指定快照版本查询的功能在很多场景都有用,OceanBase 的数据导出工具内就使用了此功能来提供一致的快照导出能力。导出工具会在开始时确定导出事务版本号,在之后读取数据时均使用 SELECT AS OF 语句指定版本号读取,那么所有导出的数据都是数据库内的一个快照版本状态。
总结
OceanBase 2.2版本是兼容 Oracle 的重要里程碑版本,也是完成 TPC-C 测试的重要版本。事务引擎在功能和性能上都做了进一步的升级,可以更好地满足业务对于数据的需求。未来,随着商业化的进程不断加速,OceanBase 事务引擎还会在功能和性能两方面继续全速前进。