佳实践| SequoiaDB巨杉数据库跨引擎事务
01 前言
在佳实践系列第六篇中,我们利用多副本,实现了OLTP、OLAP的负载隔离。那么,单就OLTP系统而言,能否实现跨引擎事务功能呢?比如我们有2套应用系统,一套基于MySQL开发,一套基于PosgreSQL开发,两个应用系统能否灵活地实现数据互相访问,并且实现事务隔离?
在以往,这是不可能完成的。如果非要这样做,则需要将其中一套数据库,进行异构数据迁移,应用程序要做改造(SQL语句、存储过程等)。为了改造后达到兼容,还伴有大量的功能测试、性能测试,这样庞大的工作量,往往令人望而却步,转而走向业务的重新开发。
如果现在有这样一种技术,能让你在一个数据库里实现2种SQL引擎的访问,更为不可思议的是,不同的SQL引擎还可以实现事务,并支持4种事务隔离级别,你会相信么?
是的,您没有听错,巨杉数据库SequoiaDB的跨引擎事务特性,将这个需求的实现变成了可能。本文将进行讲解,便于您充分了解该技术特性,进而把它运用到适合的业务场景中,如微服务改造等。
02 背景
目前,市面上主流的数据库,基本上都支持事务功能,网上也有很多技术资料,介绍数据库的事务功能及原理。
跨引擎事务则是一种新兴的技术概念。在这种技术下,同一个数据库,可以被不同的SQL引擎(如MySQL、PostgreSQL)访问,并且提供多种隔离级别、并发控制机制,实现事务的ACID。
目前,随着微服务架构的火热,各个应用系统都运行在相对独立的微服务中。这些服务是围绕业务功能构建的,因此,在软件开发阶段,软件厂商更倾向于采用自己惯用的SQL引擎。长此以往,就出现了多种数据库形态,如MySQL、PostgreSQL等,这导致不同的业务系统之间,不能够灵活的互相访问和数据共享。
巨杉数据库SequoiaDB,是一款多引擎数据库,提供一个统一的存储引擎作为底座,上层提供多种SQL引擎,如MySQL、PostgreSQL等,对数据进行访问。同时,为了支持更复杂的业务场景,提供了多种事务隔离级别。
结合巨杉数据库的高并发、在线弹性伸缩、海量数据存储等优势,可以很好地解决微服务架构下的数据碎片化、性能瓶颈和弹性扩张等问题。
03 SequoiaDB巨杉数据库跨引擎事务
3.1 并发事务的数据读取
在对数据库系统进行并发访问时,可能会出现几种情况:
1、 脏读
某事务,读取到了脏数据(正在被其它事务操作,但未提交的数据)。换句话说,数据查询结果,受到其他事务对数据的操作所影响,比如增、删、改等,尽管这些操作尚未提交。试想,一旦这些操作回滚,意味着读取到了数据。
例:情人节,小王的给他爱人转账520元(未提交),以表心意。小王老婆打开了账上银行APP,发现收到了520元的转账,喜笑颜开。但是,不料,小王提交转账订单时,网络中断,交易异常终止,转账操作被后台回滚,导致小王老婆空欢喜一场。在真实的业务场景中,在转账事务确认完成前,其他事务不应当感知到未提交的数据操作带来的影响,否则就会读取到数据,像本例中小王爱人收到的转账,就是数据。
为了避免这种情况,就需要读已提交隔离级别(Read-Committed)。
2、 不可重复读
一个事务中,对于相同的数据集,多次读取但得到的结果不一致(事务内,同一行数据多次查询不一致,因为被更新)。例:情人节,小王打算给老婆转520元,以表心意。他打开掌上银行APP,查询余额,发现账上还有1000元,因此发起转账,但操作时,APP却提醒他,余额不足,卡上只有400元。小王纳闷了,明明刚才还显示1000元,怎么余额不足了呢?打开账单明细发现,原来,不巧就在刚刚查询后到发起转账的短短的几秒钟,自动扣除了600元的自动还款,因时余额已经不足够完成此次的转账。
这就是一个不可重复读的例子。为了避免这种情况,就需要可重复读隔离级别(Read-Repeatable)。
3、 幻读
同一个事务中,多次查询返回的结果集数目不一致。例如:事务A新增了一条记录,事务B在事务A提交前、后读到的结果可能不一致,提交后多查到一条,就好像“幻象”一样,多出来一条记录。幻读是由其他事务插入(或删除)导致的。因为新增数据无法加行锁,所以无法通过MVCC或者锁表来解决幻读。
需要强调一下,不可重复度和幻读,描述有些相似,都是指在同一个事务中,对多次查询可能得不到一致的结果。区别是:不可重复读,主要是针对数据更新,幻读主要是针对数据结果集的变化,如增加、删除等。
3.2 事务隔离级别
在实际的业务系统中,不同的业务,对于事务并发访问的要求不同。为了达到其业务目的,避免上述的一种或多种问题,通常的做法,就是设置事务隔离级别。
巨杉分布式数据库SequoiaDB,提供了四种事务隔离级别。每种隔离级别,与其对应的并发问题处理能力如下:
- 读未提交(Read Uncommitted,简称RU):一个事务,能够访问到其他事务未提交的数据,这是低隔离级别。在这种隔离级别下,上述所有的问题都会发生,如脏读、不可重复读、幻读。
- 读已提交(Read Committed,简称RC):事务只会读取每条数据新被提交的状态,从而避免了脏读。但是,无法保证事务中多次查询数据得到一致的结果,也就是说,仍无法避免不可重复读、幻读的发生。
- 读稳定性(Read Stability,简称RS):事务中对同一份数据的多次读取,其结果与事务中读取到的数据一致,在该事务结束前,该查询结果保持不变。因此,在RC的基础上,进一步避免了不可重复读。
- 可重复读(Repeatable Read,简称RR):事务中对同一份数据的多次读取,其结果与事务中读取到的数据一致,在该事务结束前,该查询结果保持不变。且符合查询条件的结果集记录数,也不会因为其他事务对数据的操作而改变。在该隔离级别下,脏读、不可重复读、幻读的问题都得到了避免。
Note:
还有一种隔离级别经常被提到,那就是串行化读(Serialization)。这是一种十分严苛的隔离级别,解决了所有并发问题,如脏读、不可重复读、幻读等。但在实际业务系统中,这种隔离级别采用得很少,因为它要求事务必须串行化执行,不允许事务并发,这样等同于完全牺牲了系统的并发能力。
从以上可以看出,巨杉提供的四种隔离级别中,可重复读(RR)隔离级别高:
- 本身已包含读已提交(RC),避免了脏读;
- 当事务对数据进行读取时,其查询结果已经被确定,在该事务结束前查询到的结果,不会因其他事务对数据的修改而改变,避免了不可重复读;
- 不会因为其他事务对数据的新增和插入,而改变结果集的记录个数,避免了幻读。
3.3 SequoiaDB 跨引擎事务
巨杉数据库SequoiaDB,作为分布式数据库,将数据分布在多台服务器中的数据节点中。
- 由SequoiaDB分布式存储引擎和SQL实例构成,底层的存储引擎提供统一的数据存储,上层的SQL实例只完成SQL语法的解析;
- 由底层的SequoiaDB分布式存储引擎实现事务的并发控制,如事务隔离、锁机制等;
- 通过二阶段提交协议实现分布式事务,支持跨表跨节点的事务原子操作;
- 只需要适配对应SQL引擎的语法,即可达到跨引擎事务的效果。
SequoiaDB 巨杉数据库的可重复读级别(Repeatable Read,以下简称RR),通过多版本并发控制(MVCC,Multi-Version Convurrency Control)来实现。MVCC 是一种数据库常用的数据库并发控制机制,通过保存数据在某个事务时间点的快照来进行事务隔离控制。
RR级别使用STP(时间序列协议,Serial Time Protocol),为分布式事务分配全局时间,并使用全局事务一致性的仲裁机制,对分布式事务实现了因果排序,再结合 MVCC 的可见性算法,从而实现了分布式事务的全局一致性(可参考 时间序列服务)。
下文中,我们通过实验,来验证SequoiaDB的事务隔离级别,在并发场景中的表现。
04 分布式数据库HTAP佳实践
4.1 环境描述
巨杉分布式数据库SequoiaDB,能够提供RU、RC、RS、RR四种隔离级别。RR是SequoiaDB提供的4种隔离级别中,严格的一种,而且这也是MySQL的默认级别。
传统的关系型数据库,一般都只能实现RC级别。
因此,下文以RR、RC隔离级别来测试事务的隔离性。其他隔离级别的测试流程类似。
本文中,采用了2个SQL实例,一个MySQL实例,一个PostgreSQL实例,底层使用一个统一的巨杉分布式存储引擎。
4.2. 参数设置
在SequoiaDB中,为了实现RR级别,需要通过数据库参数,开启全局事务和MVCC。此外,还需要时间序列服务(STP)的支持。
- SequoiaDB数据库参数设置:
//开启全局事务
db.updateConf( { globtranson:true } );
//开启MVCC(需重启数据节点)
db.updateConf( { mvccon:1 } );
- 查看参数设置
db.snapshot(SDB_SNAP_CONFIGS, { Role: "data" }, { NodeName: "", transisolation: "" ,transactionon: "",globtranson:"",mvccon: "" });
参数解释:
Transisolation表示在开启事务的情况下,使用的事务隔离级别。
0:表示 RU,
1:表示 RC,
2:表示 RS,
3:表示 RR。
- 开启并检查STP服务
//开启全局事务
stpstart
sdblist -t all -l |grep stp
- 为了便于演示,本实验环境关闭了SQL客户端的自动提交
//MySQL
set AUTOCOMMIT=off
//PostgreSQL
\set AUTOCOMMIT off
4.3. 测试数据准备
- 登陆MySQL实例,创建bills数据库,创建一张orders表。
$ 登陆mysql实例
mysql -uroot -proot -h 127.0.0.1 -P 3306
//创建database
create database bills;
use bills;
//创建orders表
create table bills.orders (
order_id int,
p_date date,
location varchar(100) ,
primary key (order_id)
);
- 向bills.orders表中插入4条记录,并查询
insert into bills.orders values(10001,'2017-06-01','Beijing');
insert into bills.orders values(10002,'2018-06-01','Shanghai' );
insert into bills.orders values(10003,'2019-06-01','Guangzhou' );
insert into bills.orders values(10004,'2020-06-01','Shenzhen' );
commit;
select * from orders;
- 登陆PostgreSQL实例、创建bills数据库
$ psql -p 5432
create database bills;
\c bills
- 创建SequoiaDB的连接器sdb_server和外部表orders,其中连接器默认开启事务
#加载 SequoiaDB 连接驱动
create extension sdb_fdw;
#创建sdb_server连接器
create server sdb_server foreign data wrapper sdb_fdw options(address 'localhost', service '11810', transaction 'on');
#创建外部表order外表
create foreign table orders (
order_id int,
p_date date,
location varchar(100)
)
server sdb_server options ( collectionspace 'bills', collection 'orders', decimal 'on' ) ;
- 数据查询验证
在PostgreSQL中查询orders表的数据。
select * from orders;
结果显示,PostgreSQL中的数据,与MySQL中插入的数据一致,说明数据正确,PostgreSQL和MySQL使用相同的数据源。
4.4. 测试过程
4.4.1. RR模式测试
- 设置SequoiaDB存储引擎事务隔离级别为RR
db.updateConf( { transisolation:3 } )
- 修改MySQL的事务隔离级别(下一个事务开始生效)。
注:虽然在事务隔离级别SequoiaDB存储引擎中已经设置,但MySQL实例中仍需设置。PostgreSQL中因为使用了外部表,因此无需设置。
//MySQL中设置事务隔离级别为RR
set transaction_isolation='REPEATABLE-READ';
//查询
select @@tx_isolation;
- 在MySQL会话中开启事务,将order_id=10001的订单数据的location字段,由’Beijing’修改为’Nanjing’,暂不提交。
begin;
update orders set location='Nanjing' where order_id=10001;
- 在PostgreSQL会话中开启事务,查询数据。
begin;
select * from orders;
可以看到,在MySQL中未提交的数据,在PostgreSQL中查询不到。说明未发生脏读。
5. 在MySQL会话中,将会话提交。
commit;
- 在PostgreSQL中,再次查询数据。
select * from orders;
可以看到,即便MySQL中已将update操作提交,但在PostgreSQL中,查到的数据,仍然是事务中查询时的状态。说明未发生不可重复读。
7. 在MySQL会话中执行insert操作,插入一条order_id为10005的订单数据,并提交。
insert into bills.orders values(10005,'2021-01-01','Hangzhou' );
commit;
select * from orders;
- 在PostgreSQL会话中,再次查询
select * from orders;
可以看到,即便MySQL中插入了一条数据,且已提交,但在PostgreSQL中,查到的数据,仍然是事务中读取的状态,结果集的数量也没有发生改变。说明未发生幻读。
通过上述步骤可以验证,由两个不同SQL引擎发起的事务,能够达到RR(可重复读)隔离级别,避免了脏读、不可重复读、幻读的情况。
4.4.2. RC模式测试
- 在MySQL中,将测试数据初始化
delete from orders;
insert into bills.orders values(10001,'2017-06-01','Beijing');
insert into bills.orders values(10002,'2018-06-01','Shanghai' );
insert into bills.orders values(10003,'2019-06-01','Guangzhou' );
insert into bills.orders values(10004,'2020-06-01','Shenzhen' );
commit;
select * from orders;
- 设置SequoiaDB存储引擎的事务隔离级别为RC。
db.updateConf( { transisolation:1 } )
- 设置MySQL的事务隔离级别为RC(下一个事务开始)。
set transaction_isolation='READ-COMMITTED';
select @@tx_isolation;
4.在MySQL会话中开启事务,将order_id=10001的记录的location字段由’Beijing’修改为’Nanjing’,暂不提交;
begin;
update orders set location='Nanjing' where order_id=10001;
select * from orders;
- 在PostgreSQL会话中结束刚才的事务,并开启新的事务,查询数据。
rollback;
begin;
select * from orders;
可以看到,在MySQL中未提交的数据,在PostgreSQL中查询不到。说明未发生脏读。
6. 在MySQL会话中,将会话提交。
commit;
- 在PostgreSQL中,再次查询数据。
select * from orders;
可以看到,MySQL中将update操作提交后,在PostgreSQL中,可以查询到提交后的数据。说明实现了RC(读已提交)隔离级别。
05 总结
在本文中,我们为大家介绍了SequoiaDB巨杉数据库的事务隔离级别,以及每种事务隔离级别,对应的并发问题处理能力。SequoiaDB支持RU、RC、RS、RR四种事务隔离级别,本文重点对的RR、RC级别,进行了验证。
通过验证,SequoiaDB的RR级别,能够避免脏读、不可重复读、幻读等数据可见性问题;RC级别,能够避免脏读问题。其他的隔离级别,读者朋友们有兴趣的话可以自行验证一下~
相关文章