本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
Neptune 事务语义示例
以下示例说明了 Amazon Neptune 中事务语义的不同用例。
示例 1 – 仅在不存在属性时插入属性
假设您要确保某个属性仅设置一次。例如,假设有多个查询同时尝试为某人指定信用评分。您只希望插入该属性的一个实例,而其他查询因该属性已设置而失败。
# GREMLIN: g.V('person1').hasLabel('Person').coalesce(has('creditScore'), property('creditScore', 'AAA+')) # SPARQL: INSERT { :person1 :creditScore "AAA+" .} WHERE { :person1 rdf:type :Person . FILTER NOT EXISTS { :person1 :creditScore ?o .} }
Gremlin property()
步骤插入具有给定键和值的属性。coalesce()
步骤在第一步中执行第一个参数,如果失败,则执行第二步:
在为给定 person1
顶点的 creditScore
属性插入值之前,事务必须尝试读取 person1
可能不存在的 creditScore
值。此尝试的读取将锁定 SPOG
索引中 S=person1
和 P=creditScore
的 SP
范围,其中 creditScore
值要么存在,要么会被写入。
进行该范围锁定可防止任何并发事务同时插入 creditScore
值。当存在多个并行事务时,某一时刻它们中只有一个可以更新该值。这可排除创建多个 creditScore
属性的异常。
示例 2 – 断言某个属性值是全局唯一的
假设您要插入一个使用社会保险号作为主键的人员。您希望更改查询,以确保在全局范围内,数据库中没有其他人具有相同的社会保险号:
# GREMLIN: g.V().has('ssn', 123456789).fold() .coalesce(__.unfold(), __.addV('Person').property('name', 'John Doe').property('ssn', 123456789')) # SPARQL: INSERT { :person1 rdf:type :Person . :person1 :name "John Doe" . :person1 :ssn 123456789 .} WHERE { FILTER NOT EXISTS { ?person :ssn 123456789 } }
该示例与上一个示例相似。主要区别在于范围锁定是在 POGS
索引而不是 SPOG
索引上进行的。
执行查询的事务必须读取模式 ?person :ssn 123456789
,其中绑定了 P
和 O
位置。范围锁定是在 P=ssn
和 O=123456789
的 POGS
索引上进行的。
如果存在该模式,则不采取任何操作。
如果不存在该模式,锁将阻止任何并发事务也插入该社会保险号
示例 3 – 如果其它属性具有指定值,则更改属性
假设游戏中的各种事件将一个人从第一关移动到第二关,并为他们分配一个设置为零的新 level2Score
属性。您需要确保此类事务的多个并发实例无法创建第二关得分属性的多个实例。Gremlin 和 Gremlin 中的查询SPARQL可能如下所示。
# GREMLIN: g.V('person1').hasLabel('Person').has('level', 1) .property('level2Score', 0) .property(Cardinality.single, 'level', 2) # SPARQL: DELETE { :person1 :level 1 .} INSERT { :person1 :level2Score 0 . :person1 :level 2 .} WHERE { :person1 rdf:type :Person . :person1 :level 1 .}
在 Gremlin 中,指定了 Cardinality.single
时,property()
步骤将添加新属性,或将现有属性值替换为指定的新值。
对属性值的任何更新(例如将 level
从 1 增加到 2)都实现为删除当前记录并插入具有新属性值的新记录。在这种情况下,将删除第 1 关的记录,并重新插入第 2 关的记录。
在增加 level2Score
并将 level
从 1 更新为 2 之前,事务必须先验证 level
值当前等于 1。为此,它需要对 SPOG
索引中的 S=person1
、P=level
和 O=1
的 SPO
前缀进行范围锁定。该锁可防止并发事务删除版本 1 三元组,因此,不会发生冲突性的并发更新。
示例 4 – 替换现有属性
某些事件可能会将某人的信用评分更新为新值(此处为 BBB
)。但是,您想要确保这种类型的并发事件无法为某人创建多个信用评分属性。
# GREMLIN: g.V('person1').hasLabel('Person') .sideEffect(properties('creditScore').drop()) .property('creditScore', 'BBB') # SPARQL: DELETE { :person1 :creditScore ?o .} INSERT { :person1 :creditScore "BBB" .} WHERE { :person1 rdf:type :Person . :person1 :creditScore ?o .}
这种情况与示例 3 相似,不同之处在于,Neptune 不是锁定 SPO
前缀,而是仅使用 S=person1
和 P=creditScore
来锁定 SP
前缀。这可防止并发事务插入或删除 person1
对象具有 creditScore
属性的任何三元组。
示例 5 – 避免悬垂属性或边缘
对实体的更新不应造成悬垂元素,即与无类型的实体相关联的属性或边缘。这只是个问题SPARQL,因为 Gremlin 有内置约束来防止悬空元素。
# SPARQL: tx1: INSERT { :person1 :age 23 } WHERE { :person1 rdf:type :Person } tx2: DELETE { :person1 ?p ?o }
INSERT
查询必须读取并使用 SPOG
索引中的 S=person1
、P=rdf:type
和 O=Person
锁定 SPO
前缀。该锁可防止 DELETE
查询并行成功。
在 DELETE
查询尝试删除 :person1 rdf:type :Person
记录和 INSERT
查询读取该记录并在 SPOG
索引中该记录的 SPO
上创建范围锁的争用过程中,可能产生以下结果:
如果
INSERT
查询在DELETE
查询读取并删除:person1
的所有记录之前提交,则将从数据库中完全删除:person1
,包括新插入的记录。如果
DELETE
查询在INSERT
查询尝试读取:person1 rdf:type :Person
记录之前提交,则读取内容将包括已提交的更改。也就是说,它找不到任何:person1 rdf:type :Person
记录,因此成为一个空操作。如果
INSERT
查询在查询之前读取,则:person1 rdf:type :Person
三元组将被锁定,并且DELETE
查询会被阻止,直到INSERT查询提交,就像前面的第一种情况一样。DELETE
如果
DELETE
在INSERT
查询之前读取,并且INSERT
查询尝试读取并锁定该记录的SPO
前缀,则会检测到冲突。这是因为三元组已标记为等待删除,因此INSERT
将失败。
在上述所有不同的可能事件序列中,均未创建任何悬垂边缘。