JanusGraph的事务
处理数据库时,为了保持数据的一致性,必须关注事务。由于JanusGraph也支持事务,所以需要确认如何处理。
确认是否支持交易支持。
可以通过Graph.features()获取由TinkerPop规范支持的图数据库功能列表。可以在Gremlin控制台中输入并尝试。
gremlin> graph = JanusGraphFactory.open("conf/janusgraph-berkeleyje.properties")
==>standardjanusgraph[berkeleyje:C:\******\janusgraph-0.5.1\conf\../db/berkeley]
==>FEATURES
> GraphFeatures
>-- IoRead: true
>-- Computer: true
>-- IoWrite: true
>-- Transactions: true // ココ!
>-- Persistence: true
>-- ConcurrentAccess: true
>-- ThreadedTransactions: true // あとココ!
(略)
如果Transactions的项为true,则可以使用事务。例如,使用TinkerGraph.open()生成的图不支持事务。
gremlin> graph = TinkerGraph.open()
==>tinkergraph[vertices:0 edges:0]
gremlin> graph.features()
==>FEATURES
> GraphFeatures
>-- IoRead: true
>-- Computer: true
>-- IoWrite: true
>-- Transactions: false // ダメ!
>-- Persistence: true
>-- ConcurrentAccess: false
>-- ThreadedTransactions: false // ダメ!
如果ThreadedTransactions为true,则可以使用在多个线程之间共享一个事务的功能(具体用途虽然不确定,但可能会在分散重负载处理时使用)。
如果认为使用features()时日志的显示不清晰,可以使用以下代码逐个确认。
gremlin> graph.features().graph().supportsTransactions()
==>true
gremlin> graph.features().graph().supportsThreadedTransactions()
==>true
访问服务器时的交易限制
在之前的文章《JanusGraph的两种用法》中已经提到,如果要访问图形数据库服务器并进行查询(即远程情况),事务的使用会受到限制。
发送的查询将自动包装在一个事务中并执行。因此,无法将两个查询组合成一个事务。但是,在Gremlin中,可以编写复杂的查询,一次获取、添加、编辑或删除多个元素(顶点/边/属性),所以应该没有太大的不便。上面图中的两个查询可以像 g.addV(“person”).V().count() 这样合并。
如果你确实想要完全控制事务的话,建议你尝试上述文章中使用Cluster和Client的连接方法。虽然不是特别推荐这样做。
TinkerPop事务的基础
以下的描述是基于连接到嵌入式数据库(而非远程数据库)的前提。
交易接口
处理事务时,请使用graph.tx()或g.tx()。由于g.tx()被明确表示为graph.tx()的代理,所以这两者执行完全相同的操作。您可以使用任何一个喜欢的方式。返回一个Transaction作为结果,可以根据需要进行操作。
交易功能方法列表(部分)
开始交易
默认情况下,事务会自动开始。其触发时间是在首次对图进行操作(查询)的瞬间。例如,在执行g.V()、g.E()、graph.addVertex()、graph.addEdge()、graph.vertices()、graph.edges()等函数时。
可以通过graph.tx().isOpen来检查事务是否已经开始,可以尝试一下。
gremlin> graph = JanusGraphFactory.open("conf/janusgraph-berkeleyje.properties")
==>standardjanusgraph[berkeleyje:C:\******\janusgraph-0.5.1\conf\../db/berkeley]
gremlin> graph.tx().isOpen()
==>false // まだ開始されていない
gremlin> graph.vertices() // ここで開始される
gremlin> graph.tx().isOpen()
==>true
肯定已经开始了。
提交和回滚
如果只是进行只读查询,那么就没有必要特别担心,但如果对图进行了更改(写入),那么这些更改尚未确定。要确定这些更改,需要进行提交。或者在事务中出现了错误或其他不便时,需要进行回滚以取消所有已进行的更改。
回滚操作可以通过graph.tx().rollback()来执行。
gremlin> g.V().count()
==>0
gremlin> g.addV("person").iterate() // 頂点を追加した
gremlin> g.V().count()
==>1 // 現時点では反映されている(が、確定ではない)
gremlin> g.tx().rollback() // 取り消す
==>null
gremlin> g.V().count()
==>0 // ロールバックされた
提交由graph.tx().commit()完成。
gremlin> g.V().count()
==>0
gremlin> g.addV("person").iterate() // 頂点を追加した
gremlin> g.V().count()
==>1 // 現時点では反映されている(が、確定ではない)
gremlin> g.tx().commit() // 確定させる
==>null
gremlin> g.V().count()
==>1 // コミットされた
在编写程序时,如果发生错误,最后应执行回滚操作;如果没有错误,则应执行提交操作。
执行提交或回滚操作时,事务将被关闭。
明确执行交易的开始
也许有些人觉得自动开始事务有点不舒服。在这种情况下,可以使用onReadWrite来强制显式启动事务。
gremlin> graph.tx().onReadWrite(Transaction.READ_WRITE_BEHAVIOR.MANUAL)
==>org.janusgraph.graphdb.tinkerpop.JanusGraphBlueprintsGraph$GraphTransaction@e886caf
gremlin> g.addV("person").iterate()
Open a transaction before attempting to read/write the transaction
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> graph.tx().open()
==>null
gremlin> g.addV("person").iterate()
通过将onReadWrite设置为MANUAL来防止自动启动。默认情况下,为AUTO。
多线程事务
在多个线程中共享一个事务
使用createThreadedTx()方法。在主线程中生成事务,并将其引用传递给各个线程。等所有线程的处理完成后执行commit或rollback操作。
用Java的程序来举例子。
class AddVertexThread extends Thread
{
private Graph gx;
private String name;
public AddVertexThread(Graph gx, String name)
{
this.gx = gx;
this.name = name;
}
@Override
public void run()
{
GraphTraversalSource g = this.gx.traversal();
g.addV("person").property("name", this.name).iterate();
}
}
public class JanusExample {
public static void main(String args[])
{
Graph graph = JanusGraphFactory.open("conf/embedded.properties");
GraphTraversalSource g = graph.traversal();
// グラフを空にする
g.V().drop().iterate();
// コミット
g.tx().commit();
// 名前1つに対し、それぞれ別のスレッドで頂点を登録する
String names[] = {"bob", "alice", "ellie"};
List<Thread> threads = new ArrayList<Thread>();
Graph gx = graph.tx().createThreadedTx(); // 共有されるトランザクション
for(String name : names){
Thread t = new AddVertexThread(gx, name);
threads.add(t);
}
for(Thread t : threads) t.start(); // 全スレッド開始
for(Thread t : threads) t.join(); // 全スレッド完了待ち
gx.tx().commit(); // まとめてコミット
// 結果を確認
List<Object> result_names = g.V().values("name").toList();
for(Object obj_name : result_names){
String name = (String)obj_name;
System.out.println(name);
}
graph.close();
}
}
The result is…
bob
alice
ellie
在中文中拆分:「となる(登録の順番は保証されない)」。
可能的翻译:「并且将是这样(注册顺序不保证)」。
在多个线程中处理多个事务。
在我调查的情况下,没有找到这种方法。实际尝试也不行。
Could not commit transaction due to exception during persistence
由于出现了这个错误,所以无法正常工作。使用类似ConcurrentLinkedQueue的队列,在一个线程中依次处理事务似乎是一个可行的选择。
如果有任何能够成功的方法,请告诉我。
只能进行读取的交易
如果你想要防止意外地覆写数据或者破坏数据,有时候你可能需要进行限制。在这种情况下,你可以使用 JanusGraph.buildTransaction()。
由于实际上无法直接实例化JanusGraph,因此需要通过执行JanusGraphFactory.open()后获取的图(StandardJanusGraph)进行使用。由于StandardJanusGraph是JanusGraph的子类,所以可以使用buildTransaction方法。在Java中,需要将open方法的返回值进行向下转型后再使用。
gremlin> graph = JanusGraphFactory.open("conf/janusgraph-berkeleyje.properties")
==>standardjanusgraph[berkeleyje:C:\******\janusgraph-0.5.1\conf\../db/berkeley]
gremlin> readonly_graph = graph.buildTransaction().readOnly().start()
==>standardjanusgraphtx[0x1e12a5a6] // 読み取り専用グラフ
gremlin> readonly_graph.addVertex("person")
Cannot create new entities in read-only transaction
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> ro_g = ro_tx.traversal() // 読み取り専用トラバーサル
==>graphtraversalsource[standardjanusgraphtx[0x1e12a5a6], standard]
gremlin> ro_g.addV("person")
Cannot create new entities in read-only transaction
Type ':help' or ':h' for help.
Display stack trace? [yN]
当前正在进行的交易列表
使用getOpenTransactions()方法。如果由于某种原因事务仍然处于打开状态,可以使用closeTransaction()方法强制关闭它。
gremlin> tx = graph.tx().createThreadedTx()
==>standardjanusgraphtx[0x13213f26]
gremlin> graph.getOpenTransactions()
==>standardjanusgraphtx[0x13213f26]
// 1つだけ閉じたい場合は
gremlin> graph.closeTransaction(graph.getOpenTransactions().get(0)) // 0番目を閉じる
// 全部閉じたい場合は
gremlin> graph.getOpenTransactions().forEach { tx -> graph.closeTransaction(tx) }
关于酸的问题
根据官方文件,可以了解到事务在ACID方面的支持程度。
JanusGraph 的事务并不总是符合 ACID。尽管 BerkeleyDB 中是这样设计的,但是在底层存储系统 Cassandra 或 HBase 这样不提供可串行化隔离性和多行原子写入的情况下,并不满足 ACID。
所以,BerkeleyDB是否兼容?实际上我不知道,如果担心的话最好自行验证一下。