并发

OrientDB使用乐观锁的方式实现并发。乐观并发控制 OCC假设多个事务可以相互不干扰的频繁执行。

OrientDB乐观并发

乐观并发控制用在低数据竞争的场景。也就是,冲突很少,事务不需要管理锁,不需要等待锁释放。这意味着其他并发控制方法会大大降低吞吐量。

OrientDB使用OCC在原子操作事务.

原子操作

OrientDB在原子操作中支持多版本并发控制 MVCC。这就避免了服务端资源的加锁。同时,它校验数据库的版本,如果版本等于操作中的记录版本,操作成功。如果高于操作中的版本,说明另外一个线程或者用户已经更新了这条记录。这种情况下,OrientDB会报出一个异常 OConcurrentModificationException

考虑到在乐观并发控制的系统下,这种情况是正常的,开发人员需要编写并发验证的代码,程序在报出这个错误前,重试事务x次,如果捕获这个异常,重新加载影响的记录,尝试再次更新。例如, 考虑存储一个文档的代码如下,

int maxRetries = 10;
List<ODocument> result = db.query("SELECT FROM Client WHERE id = '39w39D32d2d'");
ODocument address = result.get(0);

for (int retry = 0; retry < maxRetries; ++retry) {
     try {
          // LOOKUP FOR THE INVOICE VERTEX
          address.field( "street", street );
          address.field( "zip", zip );
          address.field( "city", cityName );
          address.field( "country", countryName );

          address.save();

          // EXIT FROM RETRY LOOP
          break;
     }
     catch( ONeedRetryException e ) {
          // IF SOMEONE UPDATES THE ADDRESS DOCUMENT
          // AT THE SAME TIME, RETRY IT.
     }
}

事务

OrientDB支持乐观事务。数据库在事务运行的时候不加锁,但是当事务提交时,每条记录(文档或者图元素)都会校验版本来看是否被其他客户端更新了。所以,你需要在程序中处理并发的冲突问题。

乐观并发机制需要你在冲突的情况下进行重试。例如,当你创建一个新的顶点连接到一个已经存在的顶点:

int maxRetries = 10;
for (int retry = 0; retry < maxRetries; ++retry) {
     try {
          // LOOKUP FOR THE INVOICE VERTEX
          Vertex invoice = graph.getVertices("invoiceId", 2323);

          // CREATE A NEW ITEM
          Vertex invoiceItem = graph.addVertex("class:InvoiceItem");
          invoiceItem.field("price", 1000);

          // ADD IT TO THE INVOICE
          invoice.addEdge(invoiceItem);

          graph.commit();

          // EXIT FROM RETRY LOOP
          break;
     }
     catch( OConcurrentModificationException e ) {
          // SOMEONE HAS UPDATED THE INVOICE VERTEX
          // AT THE SAME TIME, RETRY IT
     }
}

并发级别

为了保证原子性和一致性,OrientDB在事务提交的时候使用排他锁。这意味着事务是依次提交的。

考虑到这个限制,OrientDB的开发人员需要通过优化内在的结构来避免排他锁,从而提高多核机器的并行度。

新增边时的并发控制

多个客户端尝试在同一个顶点上增加边时,OrientDB会抛出 OConcurrentModificationException异常。这是因为边的集合保存在顶点中,意味着,每次OrientDB增加或者一处边时,两边的顶点都要更新和递增版本。你可以使用RIDBAG Bonsai 结构来避免这个问题,这种结构中,边不是嵌入顶点中,所以边不会更新顶点。

在运行时使用RigBag的配置,需要在启动OrientDB前,加入如下代码:

OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.setValue(-1);

你也可以在JVM启动甚至运行而OrientDB未使用前,设置JVM参数:

$ java -DridBag.embeddedToSbtreeBonsaiThreshold=-1
注意 当运行分布式模式的SBTrees时不支持。如果使用分布式数据库,你必须设置
ridBag.embeddedToSbtreeBonsaiThreshold = Integer.MAX\_VALUE
来防止复制错误。

故障排除

避免大事务

有时,并发更新第一个元素时,OrientDB也会抛出OConcurrentModificationException异常。在特别大的事务中,当你在一个事务中涉及数千条记录,一个记录发生变更都会回滚整个过程,抛出OConcurrentModificationException异常。

为了避免这种情况,当你计划在高并发的情况下在同样的顶点上更新许多元素时,最佳实践是缩小事务。