并发
OrientDB使用乐观锁的方式实现并发。乐观并发控制 OCC假设多个事务可以相互不干扰的频繁执行。
OrientDB乐观并发
乐观并发控制用在低数据竞争的场景。也就是,冲突很少,事务不需要管理锁,不需要等待锁释放。这意味着其他并发控制方法会大大降低吞吐量。
原子操作
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
异常。
为了避免这种情况,当你计划在高并发的情况下在同样的顶点上更新许多元素时,最佳实践是缩小事务。