【巨杉数据库SequoiaDB】巨杉 Tech | 并发性与锁机制解析与实践

01mysql

概述sql

数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的状况。若对并发操做不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并发控制的一个很是重要的技术。当事务在对某个数据对象进行操做前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了必定的控制,在该事务释放锁以前,其余的事务不能对此数据对象进行更新操做。shell

OLTP 场景下一般要求具备很高的并发性。并发事务实际上取决于资源的使用情况,原则上应尽可能减小对资源的锁定时间,减小对资源的锁定范围,从而可以尽可能增长并发事务的数量,那么影响并发的因素有哪些呢?本文将从巨杉分布式数据库自己的机制以及隔离级别、数据库锁、参数、及实际例子进行详解,读完本文将对巨杉数据库并发性与锁机制有一个初步的了解。数据库

02ubuntu

隔离级别与并发性服务器

在单用户环境中,每一个事务都是顺序执行的,而不会遇到与其余事务的冲突。可是,在多用户环境下,多个事务并发执行。所以每一个事务都有可能与其余正在运行的事务发生冲突。有可能与其余事务发生冲突的事务称为交错的或并行的事务,而相互隔离的事务称为串行化事务,这意味着同时运行它们的结果与一个接一个连续地运行它们的结果没有区别。在多用户环境下,在使用并行事务时,会发生四种现象:session

  • 丢失更新:这种状况发生在两个事务读取并尝试更新同一数据时,其中一个更新会丢失。例如:事务 1 和事务 2 读取同一行数据,并都根据所读取的数据计算出该行的新值。若是事务 1 用它的新值更新该行之后,事务 2 又更新了同一行,则事务 1 所执行的更新操做就丢失了。
  • 脏读:当事务读取还没有提交的数据时,就会发生这种状况。例如:事务 1 更改了一行数据,而事务 2 在事务1 提交更改以前读取了已更改的行。若是事务 1 回滚该更改,则事务 2 就会读取被认为是未曾存在的数据。
  • 不可重复的读:当一个事务两次读取同一行数据,但每次得到不一样的数据值时,就会发生这种状况。例如:事务 1 读取了一行数据,而事务 2 在更改或删除该行后提交了更改。当事务 1 尝试再次读取该行时,它会检索到不一样的数据值(若是该行已经被更新的话),或发现该行不复存在了(若是该行被删除的话)。
  • 幻像:当最初没有看到某个与搜索条件匹配的数据行,而在稍后的读操做中又看到该行时,就会发生这种状况。例如:事务 1 读取知足某个搜索条件的一组数据行,而事务 2 插入了与事务 1 的搜索条件匹配的新行。若是事务 1 再次执行产生原先行集的查询,就会检索到不一样的行集。

维护数据库的一致性和数据完整性,同时又容许多个应用程序同时访问同一数据,这样的特性称为并发性。巨杉数据库目前经过事务、隔离级别、锁等机制来对并发性进行控制,它决定在第一个事务访问数据时,如何对其余事务锁定或隔离该事务所使用的数据。目前巨杉数据库支持如下隔离级别来实现并发性:架构

  • 读未提交(ReadUncommitted):该隔离级别指即便一个事务的更新语句没有提交,可是别的事务能够读到这个改变,几种异常状况均可能出现。会出现读取的数据是不对的。
  • 读已提交(Read Committed):该隔离级别指一个事务只能看到其余事务的已经提交的更新,看不到未提交的更新,消除了脏读和第一类丢失更新,这是大多数数据库的默认隔离级别。保证了一个事务不会读到另外一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。
  • 读稳定性(RepeatableStability):该隔离级别指一个事务中进行两次或屡次一样的对于数据内容的查询,获得的结果是同样的。假设SQL语句中包括查询条件, 则会对所有符合条件的纪录加对应的锁。假设没有条件语句。也就是对表中的所有记录进行处理。则会对所有的纪录加锁。
  • 可重复读(Repeatable Read):REPEATABLE READ隔离级解决了READUNCOMMITTED隔离级致使的问题。它确保同一事务的多个实例在并发读取数据时,会“看到一样的”数据行。不过理论上,这会致使另外一个棘手问题:幻读(Phantom Read)。简单来讲,幻读指当用户读取某一范围的数据行时,另外一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行。数据库存储引擎能够经过多版本并发控制 (Multiversion Concurrency Control)机制解决了幻读问题,如MySQL的InnoDB和Falcon。巨杉数据库对于多版本控制(MVCC)技术是经过采用事务锁、内存老版本以及磁盘回滚段重建老版本的设计来实现。此架构设计的理论基础是经过对内存结构的合理利用,存储数据和索引的老版本信息,从而实现数据的快速的并发访问。

03并发

数据库锁参数与并发性实践mvc

1. SequoiaDB的事务配置

事务做为一个完整的工做单元执行,事务中的操做要么所有执行成功要么所有执行失败。SequoiaDB事务中的操做只能是插入数据、修改数据以及删除数据,在事务过程当中执行的其它操做不会归入事务范畴,也就是说事务回滚时非事务操做不会被执行回滚。若是一个表或表空间中有数据涉及事务操做,则该表或表空间不容许被删除。

  • 事务开启、提交与回滚

在SDB中,关于事务启停的配置项以下:

​ 

默认状况下,SequoiaDB 全部节点的事务功能都是开启的。若用户不须要使用事务功能,可参考如下方法,关闭事务功能。

步骤1:经过sdb shell设置集群全部节点都关闭事务。

db.updateConf( { transactionon: false }, { Global: true } )

步骤2:在集群每台服务器上都重启 SequoiaDB 的全部节点。​​​​​​​

[sdbadmin@ubuntu-dev1 ~]$ /opt/sequoiadb/bin/sdbstop -t all

注意:

1. 开启及关闭节点的事务功能都要求重启该节点。

2. 在开启节点事务功能的状况下,节点的配置项logfilenum(该配置项默认值为20)的值不能小于 5。

SequoiaDB 事务支持的操做以下:

  • 写事务操做:INSERT、UPDATE、DELETE。
  • 读事务操做:QUERY。

SequoiaDB的其它操做(如:建立表、建立索引、建立并读写LOB等其它非 CRUD 操做)不在事务功能的考虑范围。 

支持隔离级别配置参数及取值以下:

能够经过如下方式修改:

db.updateConf( { transisolation: 1 }, { Global: true } )

注意:该参数在线生效,会在下一次事务中生效

经过 "transBegin"、"transCommit" 及"transRollback" 方法,用户能够在一个事务中,对若干个操做进行事务控制。其使用方式以下:

> db.transBegin()

在上述使用模式中,用户必须显式调用"transCommit" 及 "transRollback" 方法来结束当前事务。然而,对于写事务操做,若在操做过程发生错误,数据库配置中的 transautorollback 配置项能够决定当前会话全部未提交的写操做是否自动回滚。transautorollback 的描述以下:

*注意:该配置项只有在事务功能开启(即 transactionon 为 true )的状况下才生效。*

默认状况下,transautorollback 配置项的值为 true。因此,当写事务操做过程出现失败时,当前事务全部未提交的写操做都将被自动回滚。

  • 事务自动提交 

数据库配置中,关于事务自动提交的配置项以下:

事务自动提交功能默认状况下是关闭的。当transautocommit 设置为 true 时,事务自动提交功能将开启。此时,使用事务存在如下两点不一样:

  • 用户不须要显式调用 "transBegin" 和"transCommit" 或者 "transRollback" 方法来控制事务的开启、提交或者回滚。
  • 事务提交或者回滚的范围仅仅局限于单个操做。当单个操做成功时,该操做将被自动提交;当单个操做失败时,该操做将被自动回滚。

例如,以下操做中:

> /* transautocommit 设置为 true */

更新 一、更新 二、更新 3 分别为独立的操做。假设更新 1 和 更新 2 操做成功,而更新 3失败。那么更新 1 和 更新 2 修改的记录将所有被自动提交。而更新 3 修改的记录将所有被自动回滚。

  • 其它配置

数据库配置中,关于事务的其它主要配置项以下:

  • 调整设置

当用户但愿调整事务的设置时(如:是否开启事务、调整事务配置项等),有以下 3 种方式供用户选择使用:

  1. 用户能够将数据库配置描述的事务配置项,配置到集群全部(或者部分)节点的配置文件中。若修改的配置项要求重启节点才能生效,用户需重启相应的节点。
  2. 使用 updateConf()命令在 sdb shell 中修改集群的事务配置项。若修改的配置项要求重启节点才能生效,用户需重启相应的节点。
  3. 使用 setSessionAttr()命令在会话中修改当前会话的事务配置项。该设置只在当前会话生效,并不影响其它会话的设置状况。

2. SequoiaDB并发与锁操做实践

示例:

创建数据库以及表

mysql> use company;

   1)事务提交与回滚

例子1:

使用事务回滚插入操做。事务回滚后,插入的记录将被回滚,集合中无记录:

mysql> begin;

例子2:

使用事务提交插入操做。提交事务后,插入的记录将被持久化到数据库:

mysql> begin;

2)隔离级别为RU并发与锁

例子3:

在隔离级别为RU(transisolation 设置为0)的状况下,设置当前会话级(会话1及会话2同时设置)隔离级别为read uncommitted :

​​​​​​​

mysql> SELECT @@tx_isolation;

在窗口1:会话1对该表写入数据,并不提交。

​​​​​​​

mysql> create table t3(a int,b int);

在窗口2:会话2对该表进行查询,查到的是未提交的数据。

mysql>  select * from t3;

小结:因为采用了隔离级别是RU,容许脏读,在第二个会话中不会产生锁等待,而直接会读到未提交的数据。该隔离级别建议设置在以读为主的历史数据平台应用中,在真实的OLTP环境中,不能知足业务需求。这样业务查询会读取到未提交事务的修改,若是事务发生回滚,那么读取的数据是错误的。不能知足一致性的要求。

3)隔离级别为RC并发与锁

RR隔离级别的实现概述:

巨杉数据库在RC隔离级别上除了支持传统关系型数据库的读已提交之外,经过MVCC多版本访问的方式支持读取最后一次提交的版本而不会产生锁等待,从而提升业务的并行处理能力。

例子4:在隔离级别为RC(transisolation 设置为1,translockwait为true)的状况下,看看并发状况:

首先,修改隔离级别为1,translockwait为true该修改将在下一次链接的时候生效。

> db.updateConf({transisolation:1},{Global:true});

备注:也能够经过mysql端进行当前session隔离级别参数的修改。

设置当前会话级(会话1及会话2同时设置)隔离级别为read committed :

​​​​​​​

mysql> set session transaction isolation level read committed;

在窗口1:事务1对该表写入数据,并不提交。

​​​​​​​

mysql> create table t4 (a int,b int);

在窗口2:事务2对该表进行查询,能够看到一直会处于等待锁的状态,直到锁超时(transactiontimeout设置为30秒)退出。

​​​​​​​

mysql> select * from t4;

经过捉取锁的快照,能够看到第一个事务持有锁的信息,持有该表上的IX,IS锁以及记录上的X锁。以下图所示:

 而第二个事务等待锁的状况,在等锁该表记录上的S锁。以下图所示:

​ 

小结:因为采用了隔离级别是RC 而且translockwait设置为true的状况下,在第二个事务中会产生锁等待,直到第一个事务释放该表上的行锁,第二个事务才能执行,不然会一直等待到锁超时退出为止。这也是大多数传统关系型数据库的默认隔离级别。

例子5:

在隔离级别为RC(transisolation 设置为1,translockwait为false)的状况下,看看并发状况:

咱们先来看看translockwait设置为false的说明: 不等待记录锁,直接从系统读取最后一次提交的版本。

设置SDB参数配置:

​​​​​​​

> db.updateConf({translockwait:false},{Global:true});

备注:能够经过mysql端进行当前session隔离级别参数的修改。

设置当前会话级(会话1及会话2同时设置)隔离级别为read committed :

​​​​​​​

mysql> set session transaction isolation level read committed;

在窗口1:事务1对该表写入数据,并不提交。

​​​​​​​

mysql> create table t5 (a int,b int);

在窗口2:事务2对该表进行查询,能够看到立刻返回,并无发生锁等待的状况。这时候查到的数据是最后一次提交的版本

mysql> select * from t5;

小结:因为采用了隔离级别是RC 而且translockwait设置为false的状况下,在第二个事务不会产生锁等待,而是会读到最后一次版本已提交的数据。经过锁快照也能够看到没有任何锁等待的状况出现。该隔离级别设置适用于绝大多数的OLTP场景。

   4)隔离级别为RS并发与锁

例子6:

在隔离级别为RS(transisolation 设置为2)的状况下,看看并发状况:

> db.updateConf({transisolation:1},{Global:true});

在窗口1:事务1对该表进行查询数据,不提交。

​​​​​​​

mysql> create table t6 (a int,b int,primary key(a));

在窗口2:事务2对该表进行更新,能够看处处于锁等待的状态。最终锁超时事务进行回滚:

​​​​​​​

mysql> egin;

经过捉取锁的快照,能够看到第一个事务持有锁的状况,查询拿到了该表上的S锁。以下图所示:

而事务2须要取到该表上的X锁而产生了等待,以下图所示: 

​ 

小结:因为采用了隔离级别是RS ,在第二个事务更新的事务会产生锁等待,任何事务查找的记录都不容许更新,直到该读取的表的锁被释放。经过锁快照也能够看到有锁等待的状况出现。RS场景并发性较差,通常适应于总账计算系统系统。查到的数据该事务不提交,侧不容许被修改。

5)隔离级别为RR并发与锁

RR隔离级别的实现概述:

在多版本控制技术的事务锁实现中,RR(可重复读)配置下的读操做能够在使用完记录以后当即释放锁,不须要一直持有,直到事务提交或者回滚。可是写事务操做则须要一直持有插入、更改和删除的锁,直到事务完成提交或者回滚。巨杉数据库锁的实现是采用悲观锁机制,与传统关系型数据库的采用的主流锁机制相似。

在多版本控制技术的实现中,除了引入悲观锁的机制之外,巨杉数据库还采用了内存老版本机制提高数据库并发访问及操做的能力。内存老版本是经过在记录锁上附加有一个存储原版本数据和索引相关的结构,于内存中存储了老版本的数据。

如下经过实例操做进行详解:

例子7:

在隔离级别为RR(transisolation 设置为3)的状况下,看看并发状况:

> db.updateConf({transisolation:3},{Global:true});

备注:打开RR隔离,除了transisolation 设置为3之外,须要修改以上多二个参数。mvccon及globtranson这二个参数为true。经过mysql端也能够直接进行设置。

经过mysql直接设置当前会话级(会话1及会话2同时设置)隔离级别为REPEATABLEREAD;

​​​​​​​

mysql> set session transaction isolation level REPEATABLE READ;

在窗口1:事务1对该表rr进行查询数据,不提交

mysql> create table rr (a int);

在窗口2:事务2对该表rr(事务1第一次查询后)进行数据更新后提交。

mysql> begin;

在窗口1:事务1对该表rr进行再次查询数据,查询到的数据能够看到不会因为事务2的更新提交而改变,而是读到事务开始前的版本数据。

mysql> select * from rr;

经过捉取锁的快照,能够看到第一个查询的事务1在整个事务的查询中没有持任何锁,而事务2更新的操做持用该表的行锁。以下所示:

事务1持有锁的状况(未持有锁):

 事务2持有锁的状况,持有该表的X锁,以下图所示:

​ 

小结:因为采用了隔离级别是RR ,任何查询都不会持用锁,也不会等锁,能够看到在第二个事务更新的操做不会影响事务1,任何更新的操做不会影响查询,因为事务1是在事务2以前执行查询,当前事务1始终查到的是rr表的事务2的更新前版本。该隔离级别适应于大并发查询的交易场景,能有效提升整个应用的并发性。

05

总结

巨杉数据库完整支持传统关系型数据库的几种经常使用隔离级别,可知足全部核心生产场景(OLTP及OLAP等场景)需求。创新性采用事务锁、内存老版本以及磁盘回滚段重建老版本的设计来实现了多版本并发控制技术。经过对内存结构的合理利用,存储数据和索引的老版本信息,从而实现多版本数据的快速的并发访问。

相关文章
相关标签/搜索